From d16db4f4646e3410fcff01fbbf3806c513d468c8 Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Sat, 21 Jun 2025 15:09:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B1=87=E7=8E=87=E8=AE=A1=E7=AE=97=E5=90=88?= =?UTF-8?q?=E5=90=8C=E9=87=91=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/config/RestTemplateConfig.java | 14 ++ .../oa/controller/SysOaProjectController.java | 6 +- .../oa/domain/vo/ProjectProfitLossVO.java | 9 +- .../ruoyi/oa/mapper/SysOaProjectMapper.java | 2 +- .../oa/service/IExchangeRateService.java | 23 +++ .../oa/service/ISysOaProjectService.java | 4 +- .../service/impl/ExchangeRateServiceImpl.java | 132 ++++++++++++++++++ .../service/impl/SysOaProjectServiceImpl.java | 86 ++++++++++-- .../mapper/oa/SysOaProjectMapper.xml | 78 +++++++---- 9 files changed, 301 insertions(+), 53 deletions(-) create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/RestTemplateConfig.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/service/IExchangeRateService.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/ExchangeRateServiceImpl.java diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RestTemplateConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RestTemplateConfig.java new file mode 100644 index 0000000..ac067dd --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package com.ruoyi.framework.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/SysOaProjectController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/SysOaProjectController.java index 56911fc..eb85a91 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/SysOaProjectController.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/SysOaProjectController.java @@ -236,7 +236,6 @@ public class SysOaProjectController extends BaseController { @RequestParam(required = false) String projectName, @RequestParam(required = false) String projectNum, @RequestParam(required = false) String projectStatus, - @RequestParam(required = false) String isDomestic, // 国内外 @RequestParam(required = false) BigDecimal minContractAmount, @RequestParam(required = false) BigDecimal maxContractAmount, @RequestParam(required = false) BigDecimal minProfitLoss, @@ -250,11 +249,12 @@ public class SysOaProjectController extends BaseController { ) { pageQuery.setOrderByColumn(sortField); pageQuery.setIsAsc(sortOrder); + System.out.println("收到"); return iSysOaProjectService.getProjectProfitLossList( - projectName, projectNum, projectStatus, isDomestic, + projectName, projectNum, projectStatus, minContractAmount, maxContractAmount, minProfitLoss, maxProfitLoss, - beginTimeStart, beginTimeEnd, profitType,sortField, sortOrder, pageQuery); + beginTimeStart, beginTimeEnd, profitType, pageQuery); } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/ProjectProfitLossVO.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/ProjectProfitLossVO.java index 14e5cb2..4f480c3 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/ProjectProfitLossVO.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/ProjectProfitLossVO.java @@ -12,10 +12,15 @@ public class ProjectProfitLossVO { private String projectNum; private Date beginTime; private String projectStatus; - private BigDecimal contractAmount; + private BigDecimal originalFunds; // 原始合同金额 + private String projectRemark; // 项目备注(用于判断币种) + private BigDecimal contractAmountUsd; // 美元合同金额 + private Integer isUsd; // 是否美元(1是,0否) + private BigDecimal exchangeRate; // 实时汇率 + private BigDecimal contractAmountCny; // 人民币合同金额 private BigDecimal warehouseCost; private BigDecimal hrCost; - private BigDecimal profitLoss; + private BigDecimal profitLoss; //盈亏金额 // ...其他字段 // getter/setter } \ No newline at end of file diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/SysOaProjectMapper.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/SysOaProjectMapper.java index b151579..5183a6e 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/SysOaProjectMapper.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/SysOaProjectMapper.java @@ -25,7 +25,7 @@ import java.util.Map; * @date 2024-01-11 */ public interface SysOaProjectMapper extends BaseMapperPlus { - Page selectProfitLossPage(@Param("page") Page page, @Param("ew") Wrapper wrapper); + Page selectProfitLossPage(@Param("page") Page page, @Param("ew") Wrapper wrapper,@Param("exchangeRate") BigDecimal exchangeRate); // 新增汇率参数 Page selectPageOutList(@Param("page") Page page,@Param(Constants.WRAPPER) Wrapper queryWrapper); diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IExchangeRateService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IExchangeRateService.java new file mode 100644 index 0000000..6323ad5 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IExchangeRateService.java @@ -0,0 +1,23 @@ +package com.ruoyi.oa.service; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 汇率服务接口 + */ +public interface IExchangeRateService { + + /** + * 获取当前日期的美元实时汇率 + * @return 美元兑人民币汇率 + */ + BigDecimal getCurrentUsdExchangeRate(); + + /** + * 获取指定日期的美元汇率 + * @param date 日期 + * @return 美元兑人民币汇率 + */ + BigDecimal getUsdExchangeRate(Date date); +} \ No newline at end of file diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/ISysOaProjectService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/ISysOaProjectService.java index c29f73d..3d31ba8 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/ISysOaProjectService.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/ISysOaProjectService.java @@ -24,10 +24,10 @@ public interface ISysOaProjectService { /** * 项目盈亏排序 */ - TableDataInfo getProjectProfitLossList(String projectName,String projectNum,String projectStatus,String isDomestic, + TableDataInfo getProjectProfitLossList(String projectName,String projectNum,String projectStatus, BigDecimal minContractAmount,BigDecimal maxContractAmount,BigDecimal minProfitLoss, BigDecimal maxProfitLoss,String beginTimeStart,String beginTimeEnd,String profitType, - String sortField, String sortOrder, PageQuery pageQuery); + PageQuery pageQuery); /** * 查询项目管理 */ diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/ExchangeRateServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/ExchangeRateServiceImpl.java new file mode 100644 index 0000000..c9d5982 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/ExchangeRateServiceImpl.java @@ -0,0 +1,132 @@ +package com.ruoyi.oa.service.impl; + +import com.ruoyi.oa.service.IExchangeRateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Service +public class ExchangeRateServiceImpl implements IExchangeRateService { + + @Autowired + private RestTemplate restTemplate; + + // 汇率缓存,避免重复调用API + private final ConcurrentHashMap rateCache = new ConcurrentHashMap<>(); + + // 缓存过期时间(1小时) + private static final long CACHE_EXPIRE_TIME = 60 * 60 * 1000; + + // 缓存时间记录 + private final ConcurrentHashMap cacheTimeMap = new ConcurrentHashMap<>(); + + @Override + public BigDecimal getCurrentUsdExchangeRate() { + String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + return getUsdExchangeRate(new Date()); + } + + @Override + public BigDecimal getUsdExchangeRate(Date date) { + String dateStr = new SimpleDateFormat("yyyy-MM-dd").format(date); + + // 检查缓存 + if (isCacheValid(dateStr)) { + return rateCache.get(dateStr); + } + + try { + // 调用汇率API + BigDecimal rate = callExchangeRateAPI(dateStr); + + // 更新缓存 + rateCache.put(dateStr, rate); + cacheTimeMap.put(dateStr, System.currentTimeMillis()); + + log.info("获取汇率成功,日期:{},汇率:{}", dateStr, rate); + return rate; + } catch (Exception e) { + log.error("获取汇率失败,日期:{},使用默认汇率", dateStr, e); + // 返回默认汇率 + return new BigDecimal("7.2"); + } + } + + /** + * 检查缓存是否有效 + */ + private boolean isCacheValid(String dateStr) { + Long cacheTime = cacheTimeMap.get(dateStr); + if (cacheTime == null) { + return false; + } + return (System.currentTimeMillis() - cacheTime) < CACHE_EXPIRE_TIME; + } + + /** + * 调用汇率API + */ + private BigDecimal callExchangeRateAPI(String dateStr) { + // 方式1:使用免费的汇率API + String url = "https://api.exchangerate-api.com/v4/latest/USD"; + + try { + String response = restTemplate.getForObject(url, String.class); + JSONObject json = JSON.parseObject(response); + + // 解析汇率数据 + JSONObject rates = json.getJSONObject("rates"); + BigDecimal rate = rates.getBigDecimal("CNY"); + + if (rate != null) { + return rate; + } + } catch (Exception e) { + log.warn("调用汇率API失败,尝试备用API", e); + } + + // 备用方案:使用其他免费API + try { + String backupUrl = "https://api.frankfurter.app/latest?from=USD&to=CNY"; + String response = restTemplate.getForObject(backupUrl, String.class); + JSONObject json = JSON.parseObject(response); + + JSONObject rates = json.getJSONObject("rates"); + BigDecimal rate = rates.getBigDecimal("CNY"); + + if (rate != null) { + return rate; + } + } catch (Exception e) { + log.warn("备用API也失败,使用默认汇率", e); + } + + // 最终备用方案:使用默认汇率 + return getDefaultExchangeRate(dateStr); + } + + /** + * 获取默认汇率 + */ + private BigDecimal getDefaultExchangeRate(String dateStr) { + // 根据当前年份返回不同的默认汇率 + if (dateStr.startsWith("2024")) { + return new BigDecimal("7.2"); + } else if (dateStr.startsWith("2023")) { + return new BigDecimal("7.1"); + } else if (dateStr.startsWith("2022")) { + return new BigDecimal("6.9"); + } else { + return new BigDecimal("7.0"); + } + } +} \ No newline at end of file diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaProjectServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaProjectServiceImpl.java index f9e02ed..b3d594d 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaProjectServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaProjectServiceImpl.java @@ -14,6 +14,7 @@ import com.ruoyi.oa.domain.dto.ProjectActivityDTO; import com.ruoyi.oa.domain.dto.ProjectDataDTO; import com.ruoyi.oa.domain.vo.*; import com.ruoyi.oa.service.CodeGeneratorService; +import com.ruoyi.oa.service.IExchangeRateService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,6 +42,8 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService { @Autowired private CodeGeneratorService codeGeneratorService; + @Autowired + private IExchangeRateService exchangeRateService; // 1. 定义常量列表(最好放到某个常量类里) @@ -49,44 +52,97 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService { ))); + /** + * 查询项目盈亏分页列表 + * + * @param projectName 项目名称 (筛选) + * @param projectNum 项目编号 (筛选) + * @param projectStatus 项目状态 (筛选) + * @param minContractAmount 最小合同额 (筛选) + * @param maxContractAmount 最大合同额 (筛选) + * @param minProfitLoss 最小盈亏 (筛选) + * @param maxProfitLoss 最大盈亏 (筛选) + * @param beginTimeStart 项目开始时间范围 (筛选) + * @param beginTimeEnd 项目结束时间范围 (筛选) + * @param profitType 盈亏类型 ("profit" 或 "loss") (筛选) + * @param pageQuery 分页及排序查询参数 + * @return 分页结果 + */ @Override public TableDataInfo getProjectProfitLossList( - String projectName, String projectNum, String projectStatus, String isDomestic, + String projectName, String projectNum, String projectStatus, BigDecimal minContractAmount, BigDecimal maxContractAmount, BigDecimal minProfitLoss, BigDecimal maxProfitLoss, String beginTimeStart, String beginTimeEnd, String profitType, - String sortField, String sortOrder, PageQuery pageQuery ) { - Page page = pageQuery.build(); - QueryWrapper wrapper = new QueryWrapper<>(); + // 1. 获取实时汇率,用于后续计算 + BigDecimal currentExchangeRate = exchangeRateService.getCurrentUsdExchangeRate(); - // 基础条件 + // 2. 构建基础查询条件 (WHERE子句) + QueryWrapper wrapper = new QueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(projectName), "p.project_name", projectName); wrapper.like(StringUtils.isNotBlank(projectNum), "p.project_num", projectNum); wrapper.eq(StringUtils.isNotBlank(projectStatus), "p.project_status", projectStatus); - wrapper.eq(StringUtils.isNotBlank(isDomestic), "p.is_domestic", isDomestic); + // wrapper.eq(StringUtils.isNotBlank(isDomestic), "p.is_domestic", isDomestic); // 如果需要,可以放开此注释 + + // 对原始合同金额进行筛选 wrapper.ge(minContractAmount != null, "p.funds", minContractAmount); wrapper.le(maxContractAmount != null, "p.funds", maxContractAmount); + + // 对项目时间进行筛选 wrapper.ge(StringUtils.isNotBlank(beginTimeStart), "p.begin_time", beginTimeStart); wrapper.le(StringUtils.isNotBlank(beginTimeEnd), "p.begin_time", beginTimeEnd); - // 盈亏区间 + // 3. 构建HAVING子句,用于筛选计算后的盈亏值 + // HAVING子句允许我们在GROUP BY或聚合函数计算后进行条件筛选 + StringBuilder havingClause = new StringBuilder(); + + // 盈亏类型筛选 + if ("profit".equals(profitType)) { + havingClause.append("profit_loss > 0"); + } else if ("loss".equals(profitType)) { + havingClause.append("profit_loss < 0"); + } + + // 盈亏区间筛选 if (minProfitLoss != null) { - wrapper.apply("p.funds - COALESCE(wd.total_warehouse_cost,0) - COALESCE(hr.total_hr_cost,0) >= {0}", minProfitLoss); + if (havingClause.length() > 0) { + havingClause.append(" AND "); + } + havingClause.append("profit_loss >= ").append(minProfitLoss); } if (maxProfitLoss != null) { - wrapper.apply("p.funds - COALESCE(wd.total_warehouse_cost,0) - COALESCE(hr.total_hr_cost,0) <= {0}", maxProfitLoss); + if (havingClause.length() > 0) { + havingClause.append(" AND "); + } + havingClause.append("profit_loss <= ").append(maxProfitLoss); } - // 盈利/亏损筛选 - if ("profit".equals(profitType)) { - wrapper.apply("p.funds - COALESCE(wd.total_warehouse_cost,0) - COALESCE(hr.total_hr_cost,0) > 0"); - } else if ("loss".equals(profitType)) { - wrapper.apply("p.funds - COALESCE(wd.total_warehouse_cost,0) - COALESCE(hr.total_hr_cost,0) < 0"); + // 4. 将HAVING子句应用到QueryWrapper + if (havingClause.length() > 0) { + wrapper.having(havingClause.toString()); + } + + // 5. 构建分页对象 (排序由PageQuery自动处理) + Page page = pageQuery.build(); + + // 6. 调用Mapper方法,传入汇率,让数据库完成计算、筛选和分页 + Page result = baseMapper.selectProfitLossPage(page, wrapper, currentExchangeRate); + + // 7. 再次填充VO中的展示字段,确保前端获取的数据是完整的 + // 虽然数据库已经计算了profit_loss,但在这里填充其他相关字段(如汇率、转换后的合同额)可以让API返回更丰富的信息 + for (ProjectProfitLossVO vo : result.getRecords()) { + vo.setExchangeRate(currentExchangeRate); // 填充实时汇率 + if (vo.getIsUsd() == 1) { + // 计算人民币合同额用于展示 + vo.setContractAmountCny(vo.getOriginalFunds().multiply(currentExchangeRate)); + } else { + vo.setContractAmountCny(vo.getOriginalFunds()); + } + // profit_loss 已经由数据库计算返回,无需再次计算 } - Page result = baseMapper.selectProfitLossPage(page, wrapper); return TableDataInfo.build(result); } diff --git a/ruoyi-oa/src/main/resources/mapper/oa/SysOaProjectMapper.xml b/ruoyi-oa/src/main/resources/mapper/oa/SysOaProjectMapper.xml index 073a5bc..8f467d0 100644 --- a/ruoyi-oa/src/main/resources/mapper/oa/SysOaProjectMapper.xml +++ b/ruoyi-oa/src/main/resources/mapper/oa/SysOaProjectMapper.xml @@ -813,6 +813,8 @@ ORDER BY TIMESTAMPDIFF(DAY, NOW(), p.finish_time) ASC + + -