8 天以前 69e0f44e279f7763fa9c9a4d105f154db39ee1d8
feat(sales): 添加每件数量字段支持及多业务流程优化

- 在销售台账产品中新增 singleQuantity 字段,用于计算实际发货数量
- 在销售报价产品中新增 singleQuantity 字段,完善报价管理功能
- 优化发货流程中的数量计算逻辑,支持数量乘以每件数量的转换
- 实现销售报价和发货流程的自动审批处理机制
- 添加发货明细验证,确保发货明细不能为空
- 更新数据库查询和更新逻辑以支持新的数量计算方式
- 移除审批流程中的冗余审批人ID字段传递
- 重构审核流程中的无审核人处理逻辑,支持多种业务类型
- 清理销售台账控制器中的冗余代码和未使用的变量
- 创建详细的前端联调文档说明字段变更和接口调整内容
已添加1个文件
已修改15个文件
317 ■■■■ 文件已修改
doc/20260522_sales_每件数量前端联调文档.md 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductImportDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesQuotationMapper.xml 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/销售台账导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_sales_ÿ¼þÊýÁ¿Ç°¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
# Sales æ¨¡å—「每件数量」前端联调文档
## 1. å˜æ›´èŒƒå›´
- é”€å”®å°è´¦ï¼šé”€å”®äº§å“æ–°å¢žå­—段 `singleQuantity`(每件数量)
- é”€å”®æŠ¥ä»·ï¼šæŠ¥ä»·äº§å“æ–°å¢žå­—段 `singleQuantity`(每件数量)
- å‘è´§/出库数量计算:按 `数量 * æ¯ä»¶æ•°é‡` è®¡ç®—并落库
## 1.1 è”调前置(数据库)
需保证以下两张表已新增列 `single_quantity`(建议默认值 `1`):
- `sales_ledger_product.single_quantity`
- `sales_quotation_product.single_quantity`
## 2. å­—段定义
- å­—段名:`singleQuantity`
- å«ä¹‰ï¼šæ¯ä»¶æ•°é‡
- ç±»åž‹ï¼š`number`(后端 `BigDecimal`)
- é»˜è®¤è§„则:未传、为 `null`、或 `<= 0` æ—¶ï¼ŒåŽç«¯æŒ‰ `1` å¤„理
## 3. æŽ¥å£å˜æ›´æ¸…单
### 3.1 é”€å”®å°è´¦
1. `POST /sales/ledger/addOrUpdateSalesLedger`
   - å…¥å‚ `productData[]` æ–°å¢žå­—段 `singleQuantity`
2. `POST /sales/product/addOrUpdateSalesLedgerProduct`
   - å…¥å‚新增字段 `singleQuantity`
3. `GET /sales/product/list`
   - è¿”回字段新增 `singleQuantity`
   - `noQuantity`(待发货数量)计算改为:
     - `noQuantity = quantity * singleQuantity - shippedQuantity`
   - å‘货状态计算同步基于上面公式
### 3.2 é”€å”®æŠ¥ä»·
1. `POST /sales/quotation/add`
   - å…¥å‚ `products[]` æ–°å¢žå­—段 `singleQuantity`
2. `POST /sales/quotation/update`
   - å…¥å‚ `products[]` æ–°å¢žå­—段 `singleQuantity`
3. `GET /sales/quotation/list`
   - è¿”回 `products[]` æ–°å¢žå­—段 `singleQuantity`
### 3.3 å‘货与出库
1. `POST /shippingInfo/add`
   - åŽç«¯ä¼šæŒ‰é”€å”®äº§å“ `singleQuantity` è‡ªåŠ¨æ¢ç®—å‘è´§æ˜Žç»†æ•°é‡ï¼š
   - `shipping_product_detail.quantity = å‰ç«¯ä¼ å…¥quantity * singleQuantity`
   - å‡ºåº“记录数量与发货明细一致(同样为乘积后的数量)
## 4. å‰ç«¯è”调要求
1. é”€å”®å°è´¦äº§å“è¡Œã€é”€å”®æŠ¥ä»·äº§å“è¡Œæ–°å¢žã€Œæ¯ä»¶æ•°é‡ã€è¾“入项,字段名 `singleQuantity`
2. `singleQuantity` å»ºè®®é™åˆ¶ä¸ºå¤§äºŽ `0` çš„æ•°å­—
3. å‘货提交流程中,`batchNoDetailList[].quantity` ä¼ â€œä»¶æ•°â€ï¼Œä¸è¦åœ¨å‰ç«¯å†ä¹˜ `singleQuantity`
   - åŽŸå› ï¼šåŽç«¯å·²ç»Ÿä¸€æ¢ç®—ï¼Œå‰ç«¯å†ä¹˜ä¼šå¯¼è‡´é‡å¤æ”¾å¤§
4. åˆ—表展示待发货数量时,直接使用接口返回的 `noQuantity`
## 5. è”调示例
### 5.1 é”€å”®å°è´¦æ–°å¢ž/编辑(产品片段)
```json
{
  "productData": [
    {
      "productModelId": 101,
      "quantity": 10,
      "singleQuantity": 12
    }
  ]
}
```
### 5.2 é”€å”®æŠ¥ä»·æ–°å¢ž/编辑(产品片段)
```json
{
  "products": [
    {
      "productModelId": 101,
      "quantity": 10,
      "singleQuantity": 12
    }
  ]
}
```
### 5.3 å‘货提交(明细片段)
```json
{
  "salesLedgerProductId": 2001,
  "batchNoDetailList": [
    {
      "productModelId": 101,
      "batchNo": "B20260522001",
      "quantity": 3
    }
  ]
}
```
说明:若该 `salesLedgerProductId` å¯¹åº” `singleQuantity=12`,则实际发货/出库数量为 `36`。
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -26,6 +26,7 @@
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.procurementrecord.utils.StockUtils;
@@ -39,9 +40,11 @@
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.RequiredArgsConstructor;
@@ -71,6 +74,7 @@
    private final ISysNoticeService sysNoticeService;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final SalesQuotationMapper salesQuotationMapper;
    private final StockUtils stockUtils;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ApproveNodeMapper approveNodeMapper;
@@ -89,7 +93,17 @@
                .collect(Collectors.toList());
        // æ— å®¡æ ¸äººé€»è¾‘添加
        if (CollectionUtils.isEmpty(nodeIds)) {
            autoPassPurchaseApproveIfNoApprover(approveProcessVO); // é‡‡è´­å•无审核人逻辑
            switch (approveProcessVO.getApproveType()){
                case 5:
                    autoPassPurchaseApproveIfNoApprover(approveProcessVO); // é‡‡è´­å•无审核人逻辑
                    break;
                case 6:
                    autoPassSalesApproveIfNoApprover(approveProcessVO); // é”€å”®æŠ¥ä»·æ— å®¡æ ¸äººé€»è¾‘
                    break;
                case 7:
                    autoShippingApproveIfNoApprover(approveProcessVO); // é”€å”®å‘货无审核人逻辑
                    break;
            }
            return;
        }
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(nodeIds);
@@ -154,10 +168,35 @@
        }
    }
    private void autoPassSalesApproveIfNoApprover(ApproveProcessVO approveProcessVO) {
        if (!StringUtils.hasText(approveProcessVO.getApproveReason())) {
            return;
        }
        salesQuotationMapper.update(null, new LambdaUpdateWrapper<SalesQuotation>()
                .eq(SalesQuotation::getQuotationNo, approveProcessVO.getApproveReason())
                .set(SalesQuotation::getStatus, "通过"));
    }
    private void autoShippingApproveIfNoApprover(ApproveProcessVO approveProcessVO) {
        if (!StringUtils.hasText(approveProcessVO.getApproveReason())) {
            return;
        }
        ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                .eq(ShippingInfo::getShippingNo, approveProcessVO.getApproveReason())
                .last("limit 1"));
        if(shippingInfo == null){
            return;
        }
        shippingInfoMapper.update(null, new LambdaUpdateWrapper<ShippingInfo>()
                .eq(ShippingInfo::getShippingNo, approveProcessVO.getApproveReason())
                .set(ShippingInfo::getStatus, "审核通过"));
        //更改出库审核状态(待确认改成待审核)
        stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
    }
    private void autoPassPurchaseApproveIfNoApprover(ApproveProcessVO approveProcessVO) {
        if (!Objects.equals(approveProcessVO.getApproveType(), 5)
                || !StringUtils.hasText(approveProcessVO.getApproveReason())) {
            throw new RuntimeException("审核用户不存在");
        if (!StringUtils.hasText(approveProcessVO.getApproveReason())) {
            return;
        }
        purchaseLedgerMapper.update(null, new LambdaUpdateWrapper<PurchaseLedger>()
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -237,42 +237,13 @@
    @GetMapping("/listPage")
    public IPage<SalesLedgerVo> listPage(Page page, SalesLedgerDto salesLedgerDto) {
        IPage<SalesLedgerVo> iPage = salesLedgerService.selectSalesLedgerListPage(page, salesLedgerDto);
        //  æŸ¥è¯¢ç»“果为空,直接返回
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            return iPage;
        }
        //  èŽ·å–å½“å‰é¡µæ‰€æœ‰å°è´¦è®°å½•çš„ ID é›†åˆ
        List<Long> salesLedgerIds = iPage.getRecords().stream().map(SalesLedger::getId).collect(Collectors.toList());
        //  è½¬æ¢å›žæ¬¾æ•°æ®, key ä¸ºå°è´¦ID, value ä¸ºè¯¥å°è´¦çš„æ€»å›žæ¬¾é‡‘额
        Map<Long, BigDecimal> receiptTotals = new HashMap<>();
        for (SalesLedgerVo salesLedgerVo : iPage.getRecords()) {
            Long ledgerId = salesLedgerVo.getId();
            // åˆåŒæ€»é‡‘额
            BigDecimal contractAmount = salesLedgerVo.getContractAmount() == null ? BigDecimal.ZERO : salesLedgerVo.getContractAmount();
            // å¼€ç¥¨æ€»é¢å’Œå›žæ¬¾æ€»é¢
            BigDecimal receiptPaymentAmountTotal = receiptTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
            //  å¦‚果已经有过开票或回款操作,则不允许编辑
            boolean hasReceiptOperation = receiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) > 0;
            salesLedgerVo.setIsEdit(hasReceiptOperation);
            salesLedgerVo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, ledgerId));
            salesLedgerVo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedgerVo.getId()));
        }
        if (ObjectUtils.isNotEmpty(salesLedgerDto.getStatus())) {
            if (salesLedgerDto.getStatus()) {
                iPage.setTotal(iPage.getRecords().size());
            }
        }
        return iPage;
    }
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
@@ -57,19 +57,18 @@
    public AjaxResult add(@RequestBody ShippingInfoDto req) throws Exception {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        String sh = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SH","shipping_no");
        // æ·»åŠ å‘è´§æ¶ˆæ¯
        req.setShippingNo(sh);
        req.setStatus("待审核");
        boolean save = shippingInfoService.add(req);
        // å‘货审批
        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
        approveProcessVO.setApproveType(7);
        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
        approveProcessVO.setApproveReason(sh);//发货编号
        approveProcessVO.setApproveUserIds(req.getApproveUserIds());
        approveProcessVO.setApproveUser(loginUser.getUserId());
        approveProcessVO.setApproveTime(LocalDate.now().toString());
        approveProcessService.addApprove(approveProcessVO);
        // æ·»åŠ å‘è´§æ¶ˆæ¯
        req.setShippingNo(sh);
        req.setStatus("待审核");
        boolean save = shippingInfoService.add(req);
        return save ? AjaxResult.success() : AjaxResult.error();
    }
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductImportDto.java
@@ -44,6 +44,12 @@
    private BigDecimal quantity;
    /**
     * æ¯ä»¶æ•°é‡
     */
    @Excel(name = "每件数量")
    private BigDecimal singleQuantity;
    /**
     * ç¨Žçއ
     */
    @Excel(name = "税率")
src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java
@@ -11,10 +11,4 @@
public class SalesQuotationDto extends SalesQuotation {
    @Schema(description = "报价商品")
    private List<SalesQuotationProduct> products;
    /**
     * å®¡æ‰¹äººid列表
     */
    // å®¡æ‰¹äºº
    private String approveUserIds;
}
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
@@ -18,11 +18,6 @@
@Data
public class ShippingInfoDto extends ShippingInfo {
    /**
     * å®¡æ‰¹äººid列表
     */
    // å®¡æ‰¹äºº
    private String approveUserIds;
    private String type; // å‘货类型
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -66,6 +66,9 @@
     */
    @Excel(name = "数量")
    private BigDecimal quantity;
    @TableField(value = "single_quantity")
    @Excel(name = "每件数量")
    private BigDecimal singleQuantity;
    @Excel(name = "最低库存数量")
    private BigDecimal minStock;
    /**
src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java
@@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@@ -30,6 +31,9 @@
    private Double unitPrice;
    @Schema(description = "数量")
    private Integer quantity;
    @TableField(value = "single_quantity")
    @Schema(description = "每件数量")
    private BigDecimal singleQuantity;
    @Schema(description = "金额")
    private Double amount;
    @Schema(description = "创建时间")
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -167,6 +167,7 @@
        int result;
        Long salesLedgerId = salesLedgerProduct.getSalesLedgerId();
        salesLedgerProduct.setSingleQuantity(normalizeSingleQuantity(salesLedgerProduct.getSingleQuantity()));
        if (salesLedgerProduct.getId() == null) {
            salesLedgerProduct.setRegisterDate(LocalDateTime.now());
            result = salesLedgerProductMapper.insert(salesLedgerProduct);
@@ -336,6 +337,13 @@
        return R.ok();
    }
    private BigDecimal normalizeSingleQuantity(BigDecimal singleQuantity) {
        if (singleQuantity == null || singleQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ONE;
        }
        return singleQuantity;
    }
    private String generateNextPlanNo(String datePrefix) {
        QueryWrapper<ProductionPlan> queryWrapper = new QueryWrapper<>();
        queryWrapper.likeRight("mps_no", "JH" + datePrefix);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -388,6 +388,7 @@
                for (SalesLedgerProductImportDto salesLedgerProductImportDto : salesLedgerProductImportDtos) {
                    SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct();
                    BeanUtils.copyProperties(salesLedgerProductImportDto, salesLedgerProduct);
                    salesLedgerProduct.setSingleQuantity(normalizeSingleQuantity(salesLedgerProduct.getSingleQuantity()));
                    salesLedgerProduct.setSalesLedgerId(salesLedger.getId());
                    salesLedgerProduct.setType(1);
                    // è®¡ç®—不含税总价
@@ -590,7 +591,10 @@
    public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
        // æŒ‰ID分组,区分新增和更新的记录
        Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream()
                .peek(p -> p.setSalesLedgerId(salesLedgerId))
                .peek(p -> {
                    p.setSalesLedgerId(salesLedgerId);
                    p.setSingleQuantity(normalizeSingleQuantity(p.getSingleQuantity()));
                })
                .collect(Collectors.partitioningBy(p -> p.getId() != null));
        List<SalesLedgerProduct> updateList = partitionedProducts.get(true);
@@ -620,6 +624,13 @@
        return entity;
    }
    private BigDecimal normalizeSingleQuantity(BigDecimal singleQuantity) {
        if (singleQuantity == null || singleQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ONE;
        }
        return singleQuantity;
    }
    @Transactional(readOnly = true)
    public String generateSalesContractNo() {
        LocalDate currentDate = LocalDate.now();
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -29,6 +29,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
@@ -78,6 +79,7 @@
        List<SalesQuotationProduct> products = salesQuotationDto.getProducts().stream().map(product -> {
            SalesQuotationProduct salesQuotationProduct = new SalesQuotationProduct();
            BeanUtils.copyProperties(product, salesQuotationProduct);
            salesQuotationProduct.setSingleQuantity(normalizeSingleQuantity(salesQuotationProduct.getSingleQuantity()));
            salesQuotationProduct.setSalesQuotationId(salesQuotation.getId());
            return salesQuotationProduct;
        }).collect(Collectors.toList());
@@ -87,7 +89,6 @@
        approveProcessVO.setApproveType(6);
        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
        approveProcessVO.setApproveReason(quotationNo);
        approveProcessVO.setApproveUserIds(salesQuotationDto.getApproveUserIds());
        approveProcessVO.setApproveUser(loginUser.getUserId());
        approveProcessVO.setApproveTime(LocalDate.now().toString());
        approveProcessVO.setPrice(salesQuotationDto.getTotalAmount());
@@ -95,7 +96,7 @@
            approveProcessService.addApprove(approveProcessVO);
        }catch (Exception e){
            log.error("SalesQuotationServiceImpl error:{}", e);
            throw                                new RuntimeException("审批失败");
            throw  new RuntimeException("审批失败");
        }
        return true;
    }
@@ -118,13 +119,13 @@
        List<SalesQuotationProduct> products = salesQuotationDto.getProducts().stream().map(product -> {
            SalesQuotationProduct salesQuotationProduct = new SalesQuotationProduct();
            BeanUtils.copyProperties(product, salesQuotationProduct);
            salesQuotationProduct.setSingleQuantity(normalizeSingleQuantity(salesQuotationProduct.getSingleQuantity()));
            salesQuotationProduct.setSalesQuotationId(salesQuotation.getId());
            return salesQuotationProduct;
        }).collect(Collectors.toList());
        salesQuotationProductService.saveBatch(products);
        // ä¿®æ”¹æŠ¥ä»·å®¡æ‰¹
        vo.setApproveUserIds(salesQuotationDto.getApproveUserIds());
        vo.setApproveType(6);
        vo.setApproveReason(salesQuotationDto.getQuotationNo());
        approveProcessService.updateApproveUser(vo);
@@ -147,5 +148,12 @@
        return true;
    }
    private BigDecimal normalizeSingleQuantity(BigDecimal singleQuantity) {
        if (singleQuantity == null || singleQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ONE;
        }
        return singleQuantity;
    }
}
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -19,6 +19,7 @@
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.mapper.ShippingProductDetailMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.pojo.ShippingProductDetail;
import com.ruoyi.sales.service.ShippingInfoService;
@@ -27,6 +28,7 @@
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -127,6 +129,10 @@
    @Override
    public boolean add(ShippingInfoDto req) {
        if (CollectionUtils.isEmpty(req.getBatchNoDetailList())) {
            throw new RuntimeException("发货明细不能为空");
        }
//        normalizeShippingQuantity(req.getBatchNoDetailList(), req.getSalesLedgerProductId());
        this.save(req);
        req.getBatchNoDetailList().forEach(item -> item.setShippingInfoId(req.getId()));
        shippingProductDetailMapper.insert(req.getBatchNoDetailList());
@@ -153,4 +159,31 @@
        shippingApproveDto.setShippingProductDetailDtoList(dateilByShippingNo);
        return shippingApproveDto;
    }
    /**
     * æ­£å¸¸åŒ–发货数量
     * @param shippingProductDetails
     * @param salesLedgerProductId
     */
    private void normalizeShippingQuantity(List<ShippingProductDetail> shippingProductDetails, Long salesLedgerProductId) {
        if (CollectionUtils.isEmpty(shippingProductDetails)) {
            return;
        }
        BigDecimal singleQuantity = BigDecimal.ONE;
        if (salesLedgerProductId != null) {
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(salesLedgerProductId);
            if (salesLedgerProduct != null && salesLedgerProduct.getSingleQuantity() != null
                    && salesLedgerProduct.getSingleQuantity().compareTo(BigDecimal.ZERO) > 0) {
                singleQuantity = salesLedgerProduct.getSingleQuantity();
            }
        }
        if (singleQuantity.compareTo(BigDecimal.ONE) == 0) {
            return;
        }
        for (ShippingProductDetail shippingProductDetail : shippingProductDetails) {
            if (shippingProductDetail.getQuantity() != null) {
                shippingProductDetail.setQuantity(shippingProductDetail.getQuantity().multiply(singleQuantity));
            }
        }
    }
}
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -10,6 +10,7 @@
        T1.sales_ledger_id,
        T1.warn_num,
        T1.quantity,
        T1.single_quantity,
        T1.min_stock,
        T1.tax_rate,
        T1.tax_inclusive_unit_price,
@@ -33,10 +34,10 @@
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >0 THEN 1
        ELSE 0
        END as has_sufficient_stock,
        (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) as no_quantity,
        (IFNULL(T1.quantity, 0) * IFNULL(NULLIF(T1.single_quantity, 0), 1) - IFNULL(t3.shipped_quantity, 0)) as no_quantity,
        CASE
         WHEN IFNULL(t3.shipped_quantity, 0) = 0 THEN '待发货'
         WHEN (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) > 0 THEN '部分发货'
         WHEN (IFNULL(T1.quantity, 0) * IFNULL(NULLIF(T1.single_quantity, 0), 1) - IFNULL(t3.shipped_quantity, 0)) > 0 THEN '部分发货'
        ELSE '已发货'
        END as shippingStatus,
        CASE
src/main/resources/mapper/sales/SalesQuotationMapper.xml
@@ -4,20 +4,19 @@
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sales.mapper.SalesQuotationMapper">
    <select id="listPage" resultType="com.ruoyi.sales.dto.SalesQuotationDto">
        SELECT t1.*,
               t2.approve_user_ids
        SELECT t1.*
        FROM sales_quotation t1
        LEFT JOIN approve_process t2 ON t1.quotation_no = t2.approve_reason and t2.approve_type = 6
        WHERE 1=1
          and t2.approve_deleted = 0
        <if test="salesQuotationDto.quotationNo != null and salesQuotationDto.quotationNo != '' ">
            AND t1.quotation_no LIKE CONCAT('%',#{salesQuotationDto.quotationNo},'%')
        </if>
        <if test="salesQuotationDto.customer != null and salesQuotationDto.customer != '' ">
            AND t1.customer = #{salesQuotationDto.customer}
        </if>
        <if test="salesQuotationDto.status != null and salesQuotationDto.status != '' ">
            AND t1.status = #{salesQuotationDto.status}
        </if>
        <where>
            <if test="salesQuotationDto.quotationNo != null and salesQuotationDto.quotationNo != '' ">
                AND t1.quotation_no LIKE CONCAT('%',#{salesQuotationDto.quotationNo},'%')
            </if>
            <if test="salesQuotationDto.customer != null and salesQuotationDto.customer != '' ">
                AND t1.customer = #{salesQuotationDto.customer}
            </if>
            <if test="salesQuotationDto.status != null and salesQuotationDto.status != '' ">
                AND t1.status = #{salesQuotationDto.status}
            </if>
        </where>
    </select>
</mapper>
src/main/resources/static/ÏúÊŲ̂Õ˵¼ÈëÄ£°å.xlsx
Binary files differ