重新修改盈亏逻辑

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 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,
@@ -251,7 +252,7 @@ public class SysOaProjectController extends BaseController {
pageQuery.setIsAsc(sortOrder);
System.out.println("收到");
return iSysOaProjectService.getProjectProfitLossList(
projectName, projectNum, projectStatus,
projectName, projectNum, projectStatus,isDomestic,
minContractAmount, maxContractAmount,
minProfitLoss, maxProfitLoss,
beginTimeStart, beginTimeEnd, profitType, pageQuery);

View File

@@ -16,17 +16,19 @@ public class ProjectProfitLossVO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal originalFunds; // 原始合同金额
private String projectRemark; // 项目备注(用于判断币种)
private BigDecimal contractAmountUsd; // 美元合同金额
private Integer isUsd; // 是否美元1是0否
// 从财务明细中汇总的总收入(仅用于无合同额的项目)
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal detailIncome;
private BigDecimal exchangeRate; // 实时汇率
// 从财务明细中汇总的总支出(所有项目的成本来源)
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal contractAmountCny; // 人民币合同金额
private BigDecimal totalExpenditure;
// 最终计算出的总收入(人民币,展示用)
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal warehouseCost;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private BigDecimal hrCost;
private BigDecimal totalIncomeCny;
@JsonFormat(shape = JsonFormat.Shape.STRING)
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 maxProfitLoss,String beginTimeStart,String beginTimeEnd,String profitType,
PageQuery pageQuery);

View File

@@ -70,13 +70,13 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
*/
@Override
public TableDataInfo<ProjectProfitLossVO> getProjectProfitLossList(
String projectName, String projectNum, String projectStatus,
String projectName, String projectNum, String projectStatus, String isDomestic,
BigDecimal minContractAmount, BigDecimal maxContractAmount,
BigDecimal minProfitLoss, BigDecimal maxProfitLoss,
String beginTimeStart, String beginTimeEnd, String profitType,
PageQuery pageQuery
) {
// 1. 获取实时汇率,用于后续计算
// 1. 获取实时汇率
BigDecimal currentExchangeRate = exchangeRateService.getCurrentUsdExchangeRate();
// 2. 构建基础查询条件 (WHERE子句)
@@ -84,63 +84,51 @@ public class SysOaProjectServiceImpl implements ISysOaProjectService {
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.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或聚合函数计算后进行条件筛选
// 3. 构建HAVING子句, 用于筛选计算后的盈亏值
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) {
if (havingClause.length() > 0) {
havingClause.append(" AND ");
}
if (havingClause.length() > 0) havingClause.append(" AND ");
havingClause.append("profit_loss >= ").append(minProfitLoss);
}
if (maxProfitLoss != null) {
if (havingClause.length() > 0) {
havingClause.append(" AND ");
}
if (havingClause.length() > 0) havingClause.append(" AND ");
havingClause.append("profit_loss <= ").append(maxProfitLoss);
}
// 4. 将HAVING子句应用到QueryWrapper
if (havingClause.length() > 0) {
wrapper.having(havingClause.toString());
}
// 5. 构建分页对象 (排序由PageQuery自动处理)
// 4. 构建分页对象 (排序由PageQuery自动处理)
Page<ProjectProfitLossVO> page = pageQuery.build();
// 6. 调用Mapper方法传入汇率,让数据库完成计算、筛选和分页
// 5. 调用Mapper方法, 传入汇率
Page<ProjectProfitLossVO> result = baseMapper.selectProfitLossPage(page, wrapper, currentExchangeRate);
// 7. 再次填充VO中的展示字段确保前端获取的数据是完整的
// 虽然数据库已经计算了profit_loss但在这里填充其他相关字段如汇率、转换后的合同额可以让API返回更丰富的信息
// 6. 再次填充VO中的展示字段, 确保前端获取的数据是完整的
for (ProjectProfitLossVO vo : result.getRecords()) {
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 {
vo.setContractAmountCny(vo.getOriginalFunds());
vo.setTotalIncomeCny(vo.getDetailIncome());
}
// profit_loss 已经由数据库计算返回无需再次计算
// profit_loss 已经由数据库根据最新逻辑计算返回, 无需再次处理
}
return TableDataInfo.build(result);

View File

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