优化回款功能

This commit is contained in:
2026-09-13 14:09:34 +08:00
parent aea53e0766
commit 2f6cdbb3cd
13 changed files with 855 additions and 668 deletions
+12 -6
View File
@@ -531,7 +531,8 @@ func contractStatusName(s int8) string {
// ========================== 内部辅助 ==========================
// buildContractResp 组装响应:parties / products 透传 JSON 数组,summary 由金额字段组装。
// buildContractResp 组装响应:parties / products 透传 JSON 数组;
// 金额字段在返回前按 products 以当前口径重算(单价含税、不再叠加税率),summary 随之组装。
func buildContractResp(row *models.TenantCrmContract) contractResp {
parties := json.RawMessage("[]")
if strings.TrimSpace(row.Parties) != "" {
@@ -541,6 +542,11 @@ func buildContractResp(row *models.TenantCrmContract) contractResp {
if strings.TrimSpace(row.Products) != "" {
products = json.RawMessage(row.Products)
}
// 返回前按「单价含税、不再叠加税率」的口径重算金额,
// 保证列表/详情与产品清单一致,并自动修正历史数据按旧口径(叠加税率)存的金额
if strings.TrimSpace(row.Products) != "" && string(products) != "[]" {
applyContractAmounts(row)
}
return contractResp{
TenantCrmContract: *row,
Parties: parties,
@@ -584,9 +590,9 @@ func normalizeContractJSON(raw json.RawMessage) (string, error) {
}
// applyContractAmounts 按产品清单重算各部分金额(与前端 ProductList 汇总口径一致):
// 每行含税金额=数量×单价×(1+税率/100);
// 硬件部分=Σ硬件含税小计;软件部分=Σ软件含税小计;其他部分=Σ服务/开发/其他含税小计;
// 合同总金额(含税)=硬件+软件+其他;产品总成本=Σ(数量×成本单价)(成本通常不含税,原值汇总);
// 清单单价为含税价,每行小计=数量×单价(不再叠加税率,税率仅作开票记录);
// 硬件部分=Σ硬件小计;软件部分=Σ软件小计;其他部分=Σ服务/开发/其他小计;
// 合同总金额=硬件+软件+其他;产品总成本=Σ(数量×成本单价)(成本为不含税原值);
// 合同总利润=总金额-总成本。
// 归类口径:分类代码 1/2 或名称含「硬件」/「软件」分别计入硬件/软件,其余计入其他。
// contractTreeSellTotal 软件开发模块树售价合计:子节点汇总,叶子=人天×统一人天单价。
@@ -642,8 +648,8 @@ func applyContractAmounts(row *models.TenantCrmContract) {
qty := toFloat64(item["quantity"])
price := toFloat64(item["price"])
costPrice := toFloat64(item["cost_price"])
taxRate := toFloat64(item["tax_rate"])
amount := round2(qty * price * (1 + taxRate/100)) // 含税金额
// 清单单价为含税价:小计 = 数量 × 单价,不再叠加税率(税率仅作开票记录,不参与金额计算)
amount := round2(qty * price)
cost = round2(cost + round2(qty*costPrice))
cat := fmt.Sprintf("%v", item["category"])
switch {