重新修改盈亏逻辑

This commit is contained in:
2025-06-23 09:40:40 +08:00
parent f7c9ab66e0
commit ca2ea16d40
5 changed files with 55 additions and 71 deletions

View File

@@ -236,6 +236,7 @@ public class SysOaProjectController extends BaseController {
@RequestParam(required = false) String projectName, @RequestParam(required = false) String projectName,
@RequestParam(required = false) String projectNum, @RequestParam(required = false) String projectNum,
@RequestParam(required = false) String projectStatus, @RequestParam(required = false) String projectStatus,
@RequestParam(required = false) String isDomestic,
@RequestParam(required = false) BigDecimal minContractAmount, @RequestParam(required = false) BigDecimal minContractAmount,
@RequestParam(required = false) BigDecimal maxContractAmount, @RequestParam(required = false) BigDecimal maxContractAmount,
@RequestParam(required = false) BigDecimal minProfitLoss, @RequestParam(required = false) BigDecimal minProfitLoss,
@@ -251,7 +252,7 @@ public class SysOaProjectController extends BaseController {
pageQuery.setIsAsc(sortOrder); pageQuery.setIsAsc(sortOrder);
System.out.println("收到"); System.out.println("收到");
return iSysOaProjectService.getProjectProfitLossList( return iSysOaProjectService.getProjectProfitLossList(
projectName, projectNum, projectStatus, projectName, projectNum, projectStatus,isDomestic,
minContractAmount, maxContractAmount, minContractAmount, maxContractAmount,
minProfitLoss, maxProfitLoss, minProfitLoss, maxProfitLoss,
beginTimeStart, beginTimeEnd, profitType, pageQuery); beginTimeStart, beginTimeEnd, profitType, pageQuery);

View File

@@ -16,17 +16,19 @@ public class ProjectProfitLossVO {
@JsonFormat(shape = JsonFormat.Shape.STRING) @JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal originalFunds; // 原始合同金额 private BigDecimal originalFunds; // 原始合同金额
private String projectRemark; // 项目备注(用于判断币种) private String projectRemark; // 项目备注(用于判断币种)
private BigDecimal contractAmountUsd; // 美元合同金额
private Integer isUsd; // 是否美元1是0否 private Integer isUsd; // 是否美元1是0否
// 从财务明细中汇总的总收入(仅用于无合同额的项目)
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal detailIncome;
private BigDecimal exchangeRate; // 实时汇率 private BigDecimal exchangeRate; // 实时汇率
// 从财务明细中汇总的总支出(所有项目的成本来源)
@JsonFormat(shape = JsonFormat.Shape.STRING) @JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal contractAmountCny; // 人民币合同金额 private BigDecimal totalExpenditure;
// 最终计算出的总收入(人民币,展示用)
@JsonFormat(shape = JsonFormat.Shape.STRING) @JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal warehouseCost; private BigDecimal totalIncomeCny;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal hrCost;
@JsonFormat(shape = JsonFormat.Shape.STRING) @JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal profitLoss; //盈亏金额 private BigDecimal profitLoss; //盈亏金额
// ...其他字段 // ...其他字段
// getter/setter
} }

View File

@@ -24,7 +24,7 @@ public interface ISysOaProjectService {
/** /**
* 项目盈亏排序 * 项目盈亏排序
*/ */
TableDataInfo<ProjectProfitLossVO> getProjectProfitLossList(String projectName,String projectNum,String projectStatus, TableDataInfo<ProjectProfitLossVO> getProjectProfitLossList(String projectName,String projectNum,String projectStatus,String isisDomestic,
BigDecimal minContractAmount,BigDecimal maxContractAmount,BigDecimal minProfitLoss, BigDecimal minContractAmount,BigDecimal maxContractAmount,BigDecimal minProfitLoss,
BigDecimal maxProfitLoss,String beginTimeStart,String beginTimeEnd,String profitType, BigDecimal maxProfitLoss,String beginTimeStart,String beginTimeEnd,String profitType,
PageQuery pageQuery); PageQuery pageQuery);

View File

@@ -70,13 +70,13 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
*/ */
@Override @Override
public TableDataInfo<ProjectProfitLossVO> getProjectProfitLossList( public TableDataInfo<ProjectProfitLossVO> getProjectProfitLossList(
String projectName, String projectNum, String projectStatus, String projectName, String projectNum, String projectStatus, String isDomestic,
BigDecimal minContractAmount, BigDecimal maxContractAmount, BigDecimal minContractAmount, BigDecimal maxContractAmount,
BigDecimal minProfitLoss, BigDecimal maxProfitLoss, BigDecimal minProfitLoss, BigDecimal maxProfitLoss,
String beginTimeStart, String beginTimeEnd, String profitType, String beginTimeStart, String beginTimeEnd, String profitType,
PageQuery pageQuery PageQuery pageQuery
) { ) {
// 1. 获取实时汇率,用于后续计算 // 1. 获取实时汇率
BigDecimal currentExchangeRate = exchangeRateService.getCurrentUsdExchangeRate(); BigDecimal currentExchangeRate = exchangeRateService.getCurrentUsdExchangeRate();
// 2. 构建基础查询条件 (WHERE子句) // 2. 构建基础查询条件 (WHERE子句)
@@ -84,63 +84,51 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
wrapper.like(StringUtils.isNotBlank(projectName), "p.project_name", projectName); wrapper.like(StringUtils.isNotBlank(projectName), "p.project_name", projectName);
wrapper.like(StringUtils.isNotBlank(projectNum), "p.project_num", projectNum); wrapper.like(StringUtils.isNotBlank(projectNum), "p.project_num", projectNum);
wrapper.eq(StringUtils.isNotBlank(projectStatus), "p.project_status", projectStatus); wrapper.eq(StringUtils.isNotBlank(projectStatus), "p.project_status", projectStatus);
// wrapper.eq(StringUtils.isNotBlank(isDomestic), "p.is_domestic", isDomestic); // 如果需要,可以放开此注释
// 对原始合同金额进行筛选
wrapper.ge(minContractAmount != null, "p.funds", minContractAmount); wrapper.ge(minContractAmount != null, "p.funds", minContractAmount);
wrapper.le(maxContractAmount != null, "p.funds", maxContractAmount); wrapper.le(maxContractAmount != null, "p.funds", maxContractAmount);
// 对项目时间进行筛选
wrapper.ge(StringUtils.isNotBlank(beginTimeStart), "p.begin_time", beginTimeStart); wrapper.ge(StringUtils.isNotBlank(beginTimeStart), "p.begin_time", beginTimeStart);
wrapper.le(StringUtils.isNotBlank(beginTimeEnd), "p.begin_time", beginTimeEnd); wrapper.le(StringUtils.isNotBlank(beginTimeEnd), "p.begin_time", beginTimeEnd);
// 3. 构建HAVING子句用于筛选计算后的盈亏值 // 3. 构建HAVING子句, 用于筛选计算后的盈亏值
// HAVING子句允许我们在GROUP BY或聚合函数计算后进行条件筛选
StringBuilder havingClause = new StringBuilder(); StringBuilder havingClause = new StringBuilder();
// 盈亏类型筛选
if ("profit".equals(profitType)) { if ("profit".equals(profitType)) {
havingClause.append("profit_loss > 0"); havingClause.append("profit_loss > 0");
} else if ("loss".equals(profitType)) { } else if ("loss".equals(profitType)) {
havingClause.append("profit_loss < 0"); havingClause.append("profit_loss < 0");
} }
// 盈亏区间筛选
if (minProfitLoss != null) { if (minProfitLoss != null) {
if (havingClause.length() > 0) { if (havingClause.length() > 0) havingClause.append(" AND ");
havingClause.append(" AND ");
}
havingClause.append("profit_loss >= ").append(minProfitLoss); havingClause.append("profit_loss >= ").append(minProfitLoss);
} }
if (maxProfitLoss != null) { if (maxProfitLoss != null) {
if (havingClause.length() > 0) { if (havingClause.length() > 0) havingClause.append(" AND ");
havingClause.append(" AND ");
}
havingClause.append("profit_loss <= ").append(maxProfitLoss); havingClause.append("profit_loss <= ").append(maxProfitLoss);
} }
// 4. 将HAVING子句应用到QueryWrapper
if (havingClause.length() > 0) { if (havingClause.length() > 0) {
wrapper.having(havingClause.toString()); wrapper.having(havingClause.toString());
} }
// 5. 构建分页对象 (排序由PageQuery自动处理) // 4. 构建分页对象 (排序由PageQuery自动处理)
Page<ProjectProfitLossVO> page = pageQuery.build(); Page<ProjectProfitLossVO> page = pageQuery.build();
// 6. 调用Mapper方法传入汇率,让数据库完成计算、筛选和分页 // 5. 调用Mapper方法, 传入汇率
Page<ProjectProfitLossVO> result = baseMapper.selectProfitLossPage(page, wrapper, currentExchangeRate); Page<ProjectProfitLossVO> result = baseMapper.selectProfitLossPage(page, wrapper, currentExchangeRate);
// 7. 再次填充VO中的展示字段确保前端获取的数据是完整的 // 6. 再次填充VO中的展示字段, 确保前端获取的数据是完整的
// 虽然数据库已经计算了profit_loss但在这里填充其他相关字段如汇率、转换后的合同额可以让API返回更丰富的信息
for (ProjectProfitLossVO vo : result.getRecords()) { for (ProjectProfitLossVO vo : result.getRecords()) {
vo.setExchangeRate(currentExchangeRate); // 填充实时汇率 vo.setExchangeRate(currentExchangeRate); // 填充实时汇率
if (vo.getIsUsd() == 1) {
// 计算人民币合同额用于展示 // 计算最终的总收入(人民币)用于展示
vo.setContractAmountCny(vo.getOriginalFunds().multiply(currentExchangeRate)); if (vo.getOriginalFunds() != null && vo.getOriginalFunds().compareTo(BigDecimal.ZERO) > 0) {
if (vo.getIsUsd() == 1) {
vo.setTotalIncomeCny(vo.getOriginalFunds().multiply(currentExchangeRate));
} else {
vo.setTotalIncomeCny(vo.getOriginalFunds());
}
} else { } else {
vo.setContractAmountCny(vo.getOriginalFunds()); vo.setTotalIncomeCny(vo.getDetailIncome());
} }
// profit_loss 已经由数据库计算返回无需再次计算 // profit_loss 已经由数据库根据最新逻辑计算返回, 无需再次处理
} }
return TableDataInfo.build(result); return TableDataInfo.build(result);

View File

@@ -815,6 +815,7 @@
</select> </select>
<!-- SysOaProjectMapper.xml --> <!-- SysOaProjectMapper.xml -->
<!-- SysOaProjectMapper.xml -->
<select id="selectProfitLossPage" resultType="com.ruoyi.oa.domain.vo.ProjectProfitLossVO"> <select id="selectProfitLossPage" resultType="com.ruoyi.oa.domain.vo.ProjectProfitLossVO">
SELECT SELECT
p.project_id, p.project_id,
@@ -824,54 +825,46 @@
p.project_status, p.project_status,
p.funds AS original_funds, p.funds AS original_funds,
p.remark AS project_remark, p.remark AS project_remark,
COALESCE(wd.total_warehouse_cost, 0) AS warehouse_cost, COALESCE(finance_details.total_income, 0) AS detail_income,
COALESCE(hr.total_hr_cost, 0) AS hr_cost, COALESCE(finance_details.total_expenditure, 0) AS total_expenditure,
CASE CASE
WHEN p.remark LIKE '%美元%' OR p.remark LIKE '%美金%' THEN 1 WHEN p.remark LIKE '%美元%' OR p.remark LIKE '%美金%' THEN 1
ELSE 0 ELSE 0
END AS is_usd, END AS is_usd,
-- 核心盈亏计算逻辑
( (
-- 首先计算总收入
CASE CASE
WHEN p.remark LIKE '%美元%' OR p.remark LIKE '%美金%' -- 场景1: 如果项目有合同额, 则总收入为合同额 (需考虑汇率)
THEN p.funds * #{exchangeRate} WHEN p.funds IS NOT NULL AND p.funds > 0
ELSE p.funds THEN
CASE
WHEN p.remark LIKE '%美元%' OR p.remark LIKE '%美金%'
THEN p.funds * #{exchangeRate}
ELSE p.funds
END
-- 场景2: 如果项目没有合同额, 则总收入为财务明细中的入账总和
ELSE COALESCE(finance_details.total_income, 0)
END END
- COALESCE(wd.total_warehouse_cost, 0) -- 减去总支出
- COALESCE(hr.total_hr_cost, 0) - COALESCE(finance_details.total_expenditure, 0)
) AS profit_loss ) AS profit_loss
FROM FROM
sys_oa_project p sys_oa_project p
LEFT JOIN ( LEFT JOIN (
-- 将财务主表和明细表关联,一次性计算出每个项目的总收入和总支出
SELECT SELECT
project_id, f.project_id,
SUM(amount * sign_price) AS total_warehouse_cost SUM(CASE WHEN f.finance_type = '1' THEN d.price ELSE 0 END) AS total_income,
SUM(CASE WHEN f.finance_type = '0' THEN d.price ELSE 0 END) AS total_expenditure
FROM FROM
sys_oa_warehouse_detail sys_oa_finance f
WHERE
del_flag = 0
GROUP BY
project_id
) wd ON p.project_id = wd.project_id
LEFT JOIN (
SELECT
a.project_id,
SUM(
CASE
-- 当小时数存在时,使用日薪/8计算小时薪资
WHEN a.hour > 0 THEN a.hour * (COALESCE(u.labor_cost, 0) DIV 8)
-- 当日数存在时,使用日薪×天数
ELSE a.day_length * COALESCE(u.labor_cost, 0)
END
) AS total_hr_cost
FROM
sys_oa_attendance a
JOIN JOIN
sys_user u ON a.user_id = u.user_id sys_oa_detail d ON f.finance_id = d.finance_id
WHERE WHERE f.project_id > 0
a.del_flag = 0
GROUP BY GROUP BY
a.project_id f.project_id
) hr ON p.project_id = hr.project_id ) AS finance_details ON p.project_id = finance_details.project_id
${ew.customSqlSegment} ${ew.customSqlSegment}
</select> </select>
</mapper> </mapper>