liyong
4 天以前 b874abdeead386d24e0939b2e99e6d3bbbd4ec46
商机金额修改
已修改2个文件
已添加1个文件
147 ■■■■■ 文件已修改
docs/business_price_field_unification.md 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/business_price_field_unification.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
# å•†æœº price å­—段统一为 businessPrice â€” å‰ç«¯è”调方案
## é—®é¢˜èƒŒæ™¯
前端产品编辑表使用 `contractPrice` ä½œä¸ºä»·æ ¼å­—段发送给后端,但后端使用的是 `businessPrice`(商机价格)。两者是同一个概念,需要统一为 `businessPrice`。
## æ¶‰åŠé¡µé¢
- å•†æœºæ–°å¢ž/编辑弹窗(`src/views/crm/business/modules/form.vue`)
- äº§å“ç¼–辑表组件(`src/views/crm/product/components/edit-table.vue`)
- äº§å“ç¼–辑表列定义(`src/views/crm/product/components/data.ts`)
## API è¯´æ˜Ž
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| PUT | `/crm/business/update` | æ›´æ–°å•†æœº |
| POST | `/crm/business/create` | æ–°å¢žå•†æœº |
**请求体字段变更(`products[]` å†…):**
| å­—段 | å˜æ›´ | è¯´æ˜Ž |
|------|------|------|
| `contractPrice` | âŒ ç§»é™¤ | æ”¹ä¸º `businessPrice` |
| `businessPrice` | âœ… ç»Ÿä¸€ä½¿ç”¨ | å•†æœºä»·æ ¼ |
| `totalProductPrice` | âœ… å·²æœ‰ | äº§å“æ€»ä»·ï¼ˆåŽç«¯å·²æ–°å¢žæ”¯æŒï¼‰ |
| `totalPrice` | âœ… å·²æœ‰ | å•†æœºæ€»ä»·ï¼ˆæŠ˜åŽï¼‰ï¼ˆåŽç«¯å·²æ–°å¢žæ”¯æŒï¼‰ |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. `edit-table.vue` â€” å­—段名 `contractPrice` â†’ `businessPrice`
文件:`src/views/crm/product/components/edit-table.vue`
涉及 6 å¤„:
| è¡Œå· | ä¿®æ”¹ |
|------|------|
| 55 | `row.contractPrice` â†’ `row.businessPrice` |
| 57 | `row.contractPrice` â†’ `row.businessPrice` |
| 63 | `row.contractPrice` â†’ `row.businessPrice` |
| 70 | `row.contractPrice` â†’ `row.businessPrice` |
| 132 | `contractPrice` â†’ `businessPrice`(template slot name) |
| 134 | `row.contractPrice` â†’ `row.businessPrice`(v-model) |
### 2. `data.ts` â€” ç¼–辑表列定义
文件:`src/views/crm/product/components/data.ts`
第 91-95 è¡Œï¼Œç¼–辑表(非 business è§†å›¾ï¼‰çš„列定义:
```
field: 'contractPrice'  â†’  field: 'businessPrice'
title: '合同价(元)'    â†’  title: '商机价格(元)'
slots: { default: 'contractPrice' }  â†’  slots: { default: 'businessPrice' }
```
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| æ›´æ–°å•†æœºæ—¶äº§å“ `businessPrice` ä¸º null | åŽç«¯è‡ªåŠ¨ç”¨æ—§æ•°æ®çš„ `businessPrice` merge(无需前端处理) |
| æ–°å¢žå•†æœºæ—¶ | `businessPrice` å¿…填,不可为 null |
| äº§å“ `totalPrice` | å‰ç«¯å¯ç›´æŽ¥ä¼ è®¡ç®—好的值,后端优先使用;未传时后端用 `businessPrice * count` è‡ªåŠ¨è®¡ç®— |
| å¤´çº§ `totalProductPrice` / `totalPrice` | å‰ç«¯å¯ç›´æŽ¥ä¼ ï¼ŒåŽç«¯ä¸å†è¦†ç›– |
## æ³¨æ„äº‹é¡¹
- **不涉及的文件**:`contract`、`statistics`、`report`、`saleTrace` ä¸­çš„ `contractPrice` æ˜¯å…¶ä»–业务概念,不用改
- äº§å“ `id` å­—段后端已新增支持,更新时传 `id` å³å¯æ ‡è¯†å·²æœ‰äº§å“
- å‰ç«¯ `businessPrice` çš„初始化值来自 `product.salesPrice`(选中产品时的默认价)
- æ›´æ–°æ—¶åªéœ€ä¼ è¦ä¿®æ”¹çš„字段,未传字段后端自动从旧数据继承
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
@@ -58,6 +58,12 @@
    @NotNull(message = "整单折扣不能为空")
    private BigDecimal discountPercent;
    @Schema(description = "产品总价", example = "12000.00")
    private BigDecimal totalProductPrice;
    @Schema(description = "商机总价(折后)", example = "11280.00")
    private BigDecimal totalPrice;
    @Schema(description = "备注", example = "随便")
    @DiffLogField(name = "备注")
    private String remark;
@@ -73,6 +79,9 @@
    @NoArgsConstructor
    @AllArgsConstructor
    public static class BusinessProduct {
        @Schema(description = "产品关联编号", example = "5")
        private Long id;
        @Schema(description = "MDM ç‰©æ–™ç¼–号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
        @NotNull(message = "物料编号不能为空")
@@ -94,6 +103,9 @@
        @NotNull(message = "数量不能为空")
        private BigDecimal count;
        @Schema(description = "总计价格(businessPrice * count)", example = "12000.00")
        private BigDecimal totalPrice;
    }
}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -134,8 +134,9 @@
        updateReqVO.setOwnerUserId(null).setStatusTypeId(null); // ä¸å…è®¸æ›´æ–°çš„字段
        // 1.1 æ ¡éªŒå­˜åœ¨
        CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
        // 1.2 æ ¡éªŒäº§å“é¡¹çš„æœ‰æ•ˆæ€§
        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
        // 1.2 æ ¡éªŒäº§å“é¡¹çš„æœ‰æ•ˆæ€§ï¼ˆä¼ å…¥æ—§äº§å“åˆ—表用于合并 null å­—段)
        List<CrmBusinessProductDO> oldProducts = businessProductMapper.selectListByBusinessId(updateReqVO.getId());
        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts(), oldProducts);
        // 1.3 æ ¡éªŒå…³è”字段
        validateRelationDataExists(updateReqVO);
@@ -147,7 +148,7 @@
        updateBusinessProduct(updateObj.getId(), businessProducts);
        // 3. è®°å½•操作日志上下文
        updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // é¿å…æ“ä½œæ—¥å¿—出现“删除负责人”的情况
        updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // é¿å…æ“ä½œæ—¥å¿—出现"删除负责人"的情况
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
        LogRecordContext.putVariable("businessName", oldBusiness.getName());
    }
@@ -216,6 +217,18 @@
    }
    private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list) {
        return validateBusinessProducts(list, Collections.emptyList());
    }
    /**
     * æ ¡éªŒäº§å“é¡¹çš„æœ‰æ•ˆæ€§ï¼ˆæ›´æ–°åœºæ™¯åˆå¹¶æ—§æ•°æ®ï¼‰
     *
     * @param list       å‰ç«¯ä¼ å…¥çš„产品列表
     * @param oldProducts å·²æœ‰äº§å“åˆ—表(用于合并 null å­—段)
     * @return å¤„理后的产品 DO åˆ—表
     */
    private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list,
                                                                  List<CrmBusinessProductDO> oldProducts) {
        // 1. æ ¡éªŒç‰©æ–™å­˜åœ¨
        Set<Long> itemIds = convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getItemId);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds);
@@ -224,15 +237,47 @@
                throw exception(PRODUCT_NOT_EXISTS);
            }
        }
        // 2. è½¬åŒ–为 CrmBusinessProductDO åˆ—表
        return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
                item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
        // 2. æž„建旧产品 Map(按 id ç´¢å¼•)
        Map<Long, CrmBusinessProductDO> oldProductMap = convertMap(oldProducts, CrmBusinessProductDO::getId);
        // 3. è½¬åŒ–为 CrmBusinessProductDO åˆ—表(合并旧数据)
        return convertList(list, o -> {
            CrmBusinessProductDO product = BeanUtils.toBean(o, CrmBusinessProductDO.class);
            // æ›´æ–°åœºæ™¯ï¼šå¦‚果产品已有 id ä¸”有旧数据,则合并 null å­—段
            if (o.getId() != null && oldProductMap.containsKey(o.getId())) {
                CrmBusinessProductDO old = oldProductMap.get(o.getId());
                if (product.getBusinessPrice() == null) {
                    product.setBusinessPrice(old.getBusinessPrice());
                }
                if (product.getItemPrice() == null) {
                    product.setItemPrice(old.getItemPrice());
                }
                if (product.getCount() == null) {
                    product.setCount(old.getCount());
                }
                if (product.getItemUnitId() == null) {
                    product.setItemUnitId(old.getItemUnitId());
                }
            }
            // ä¼˜å…ˆä½¿ç”¨å‰ç«¯ä¼ å…¥çš„ totalPrice,否则通过 businessPrice * count è®¡ç®—
            if (o.getTotalPrice() != null) {
                product.setTotalPrice(o.getTotalPrice());
            } else {
                product.setTotalPrice(MoneyUtils.priceMultiply(product.getBusinessPrice(), product.getCount()));
            }
            return product;
        });
    }
    private void calculateTotalPrice(CrmBusinessDO business, List<CrmBusinessProductDO> businessProducts) {
        business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
        business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
        // totalProductPrice:优先使用前端传入值,否则从产品汇总
        if (business.getTotalProductPrice() == null) {
            business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        }
        // totalPrice:优先使用前端传入值,否则按折扣计算
        if (business.getTotalPrice() == null) {
            BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
            business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
        }
    }
    @Override