docs/erp_purchase_invoice_multi_order_integration.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,107 @@ # éè´æ¥ç¥¨å ³èå¤å¼ éè´è®¢åï¼åæï¼ - å端èè°æ¹æ¡ ## èæ¯ å®å¤ä¸ééæ±ï¼éè´æ¥ç¥¨ä¸çãéè´è®¢åãç±åéæ¹ä¸ºå¤éï¼è¯å«å½å ¥åæ ·æ¯æãä¸å¼ æ¥ç¥¨å¯ä»¥åæå°å¤å¼ éè´è®¢åä¸ï¼æ¯å¼ 订åè®°å½æ¬æ¬¡åæéé¢ï¼è®¢åçãæªå¼ç¥¨éé¢ãæåææç»ç´¯è®¡ã ## æ¶åé¡µé¢ - éè´ç®¡ç - éè´æ¥ç¥¨ï¼æ°å¢ / ç¼è¾ / 详æ å¼¹çªãåè¡¨ï¼ - éè´ç®¡ç - éè´æ¥ç¥¨è¯å«å½å ¥ï¼è¯å«ç»æè¿å ¥åä¸ä¸ªæ°å¢å¼¹çªï¼ - éè´ç®¡ç - éè´è®¢åï¼åè¡¨ãæªå¼ç¥¨éé¢ãåã坿¥ç¥¨ç»è®°çéï¼ - éè´ç®¡ç - 仿¬¾åï¼æéè´è®¢åéæ©æ¥ç¥¨æ¶ï¼èµ°åææç»ï¼ ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å ¥ 1. æ°å¢æ¥ç¥¨ï¼éæ©éè´è®¢åï¼å¯å¤éï¼ä» å·²å®¡æ ¸éè¿ãæªå¼ç¥¨éé¢å¤§äº 0ãä¸ä¸é¦å¼ 订ååä¸ä¾åºåç订åï¼-> ç³»ç»ææé订åçæªå¼ç¥¨éé¢èªå¨åææ¥ç¥¨éé¢ï¼å¯æå·¥è°æ´ã 2. æ¥ç¥¨éé¢åå -> å·²é订åæé¡ºåºéæ°èªå¨åæï¼æ¯å¼ å 填满æªå¼ç¥¨éé¢ï¼å©ä½é¡ºå»¶ä¸ä¸å¼ ï¼ã 3. è¯å«å½å ¥ï¼OCRï¼-> è¯å«åºçéé¢å¸¦å ¥æ¥ç¥¨éé¢ï¼ä»éäººå·¥éæ©éè´è®¢åå¹¶åæã 4. ä¿åå -> éè´è®¢åçãæªå¼ç¥¨éé¢ã= 订åéé¢ â è¯¥è®¢åæææ¥ç¥¨åæå计ï¼åæå®ç订åä¸ååºç°å¨å¯éå表ã ## API | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | POST | /erp/purchase-invoice/create | æ°å¢æ¥ç¥¨ï¼æ¹ä¸ºä¼ åææç» orderAllocations | | PUT | /erp/purchase-invoice/update | ä¿®æ¹æ¥ç¥¨ï¼å¯æ´æ¢è®¢åãéè®¾åæ | | GET | /erp/purchase-invoice/get | 详æ ï¼è¿ååææç»åæ¯å¼ 订åçæªå¼ç¥¨éé¢ | | GET | /erp/purchase-invoice/page | åè¡¨ï¼æ¯ææ purchaseOrderId è¿æ»¤ï¼è¿å订åå·æ±æ» | | GET | /erp/purchase-order/page | éè´è®¢åå表ï¼remainInvoicePrice æ¹ä¸ºæåææç»è®¡ç®ï¼remainPriceEnable åæ¥ï¼æ°å¢ invoiceId åæ° | **create / update 请æ±åæ°ï¼ååé¨åï¼ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | orderAllocations | Array | æ¯ | éè´è®¢ååææç»ï¼è³å°ä¸è¡ | | orderAllocations[].purchaseOrderId | Long | æ¯ | éè´è®¢åç¼å· | | orderAllocations[].price | BigDecimal | æ¯ | æ¬å¼ æ¥ç¥¨åæå°è¯¥è®¢åçéé¢ï¼ä¸è½å°äº 0 | | purchaseOrderId | Long | å¦ | **ä¸åç±åç«¯ä¼ **ï¼å端ååææç»ç¬¬ä¸è¡åå¡« | | supplierId | Long | å¦ | **ä¸åç±åç«¯ä¼ **ï¼å端å第ä¸å¼ 订åçä¾åºååå¡« | | price | BigDecimal | æ¯ | æ¥ç¥¨éé¢ï¼å¿ é¡»çäºåè¡åæéé¢ä¹å | **page 请æ±åæ°æ°å¢ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | purchaseOrderId | Long | å¦ | æéè´è®¢åè¿æ»¤æ¥ç¥¨ï¼å½ä¸åææç»ï¼ | | invoiceId | Long | å¦ | ç¼è¾æ¥ç¥¨æ¶ä¼ æ¬å¼ æ¥ç¥¨ç¼å·ï¼æªå¼ç¥¨éé¢ä¸ remainPriceEnable è®¡ç®æ¶åé¤è¯¥æ¥ç¥¨èªèº«çåæï¼ç¸å½äºæå®å·²åæçéé¢éå订åï¼éè´æ¥ç¥¨é订åå¼¹çªä½¿ç¨ï¼ | **create / update ååºï¼** `{ "code": 0, "data": 1 }`ï¼æ¥ç¥¨ç¼å·ï¼ **page / get ååºæ°å¢å段ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | orderNos | String | å ³èéè´è®¢åå·ï¼å¤å¼ 以ãããæ¼æ¥ï¼å表å±ç¤ºï¼ | | orderCount | Integer | å ³èéè´è®¢åæ°é | | orderAllocations | Array | åææç»ï¼å段è§ä¸è¡¨ | **orderAllocations åæ®µï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | purchaseOrderId | Long | éè´è®¢åç¼å· | | orderNo | String | éè´è®¢åå· | | supplierId / supplierName | Long / String | 订åä¾åºå | | orderTotalPrice | BigDecimal | 订åéé¢ï¼å«ç¨ï¼ | | invoicedPrice | BigDecimal | 该订å**å ¶ä»**æ¥ç¥¨å·²å ç¨éé¢ï¼ä¸å«æ¬å¼ æ¥ç¥¨ï¼ | | remainPrice | BigDecimal | è¯¥è®¢åæªå¼ç¥¨éé¢ï¼ä¸å«æ¬å¼ æ¥ç¥¨ï¼ï¼å³æ¬æ¬¡æå¤å¯åæéé¢ | | price | BigDecimal | æ¬å¼ æ¥ç¥¨åæå°è¯¥è®¢åçéé¢ | **éè´è®¢åå表ï¼/erp/purchase-order/pageï¼å段å£å¾ååï¼** | åæ®µ | 说æ | |------|------| | remainInvoicePrice | 订åéé¢ â è¯¥è®¢åæææ¥ç¥¨åæå计ï¼å为订åéé¢ â åå¼ æ¥ç¥¨éé¢ï¼ | | remainPriceEnable=true | åªè¿åæªå¼ç¥¨éé¢ > 0 ç订åï¼å£å¾åæ¥æ¹ä¸ºæåææç»è®¡ç® | ## åæ®µå±ç¤ºè§å | åæ®µ | å±ç¤ºä½ç½® | 说æ | |------|----------|------| | éè´è®¢å | æ¥ç¥¨æ°å¢/ç¼è¾å¼¹çª | å¤éå¼¹çªï¼å·²å®¡æ ¸ + æªå¼ç¥¨éé¢å¤§äº 0ï¼ï¼è¡¨æ ¼åï¼è®¢åå·ãä¾åºåã订åéé¢ãæªå¼ç¥¨éé¢ãæ¬æ¬¡åæéé¢ï¼å¯ç¼è¾ï¼ãç§»é¤ | | åæå计 / å¾ åæå·®é¢ | åæè¡¨æ ¼ä¸æ¹ | 宿¶æ¾ç¤ºï¼å·®é¢ä¸ä¸º 0 æ¶ä¿åè¢«æ¦æª | | ä¾åºå | æ¥ç¥¨è¡¨å | ç±æé订åä¾åºå带åºï¼åä¸ä¾åºåï¼ï¼åªè¯» | | éè´è®¢åç¼å· | æ¥ç¥¨å表 | å±ç¤º orderNosï¼å岿°æ®åéå°ä¸»è®¢åå· | | æªå¼ç¥¨éé¢ | éè´è®¢åå表 / éæ©å¼¹çª | æåææç»å®æ¶è®¡ç® | ## ä¸å¡è§å说æ | åºæ¯ | è§å | |------|------| | åæå计 | å¿ é¡»çäºæ¥ç¥¨éé¢ï¼å¦åæ¥éå¹¶ååºæ¥ç¥¨éé¢ãåæå计åå·®é¢ | | å订åä¸é | æ¯è¡åæéé¢ä¸å¾è¶ è¿è¯¥è®¢åãæªå¼ç¥¨éé¢ãï¼è®¢åéé¢ â å ¶ä»æ¥ç¥¨å·²å ç¨ï¼ | | 订åéå¤ | åä¸å¼ æ¥ç¥¨å åä¸è®¢ååªè½åºç°ä¸æ¬¡ | | 订åç¶æ | åªæå·²å®¡æ ¸éè¿ï¼status=20ï¼çéè´è®¢åæè½ç»è®°æ¥ç¥¨ | | ä¾åºå | **å¿ é¡»åä¸ä¾åºå**ï¼æééè´è®¢åçä¾åºåä¸ä¸è´æ¶ä¿åè¢«æ¦æªï¼å¹¶ååºåä¾åºååå ¶è®¢åå· | | é¢åº¦å ç¨ | æ¥ç¥¨å建åå³å ç¨è®¢åæªå¼ç¥¨é¢åº¦ï¼æ å®¡æ ¸æµç¨ï¼ï¼å é¤åéæ¾ | | ç¼è¾ | å è®¸æ´æ¢è®¢åãè°æ´åæï¼æ ¡éªä¸å¼¹çªæªå¼ç¥¨éé¢è®¡ç®åæé¤æ¬å¼ æ¥ç¥¨èªèº«çåæï¼å·²åæéé¢éå订å | | å é¤ | æææä»æ¬¾åå ³èçæ¥ç¥¨ä¸å 许å é¤ï¼å é¤åæ¶éæ¾åæé¢åº¦ | ## 注æäºé¡¹ - æ°æ®åºéå æ§è¡ `docs/sql/erp_purchase_invoice_order_20261104.sql`ï¼æ°å»º `erp_purchase_invoice_order` åæè¡¨ï¼å¹¶åå¡«å岿¥ç¥¨ï¼ã - å岿¥ç¥¨è¥æ²¡æåææç»ï¼å表/è¯¦æ æä¸»è®¢åå åºå±ç¤ºï¼ç¼è¾ä¿åå伿æ°è§åéå»ºåææç»ã - 仿¬¾åæéè´è®¢åéæ©æ¥ç¥¨æ¶ï¼æ¹ä¸ºæåææç»å¹é 订åï¼ä¸å¼ æ¥ç¥¨å¯å½ä¸å¤å¼ 订åï¼ã - å端èªå¨åææãæé订åçå¾é顺åºã便¬¡å¡«æ»¡ï¼è°æ´æè¡éé¢åå·®é¢ä¼èªå¨é¡ºå»¶ç»å ¶ä»è®¢åï¼ä»ååèªä¸é约æã - å·²å¼ç¥¨éé¢å计ä¸ä¼è¶ è¿è®¢åéé¢ï¼è®¢åæªå¼ç¥¨éé¢ä¸ä¼ä¸ºè´ï¼ï¼æ¥ç¥¨éé¢å°äºè®¢åé颿¶ï¼å·®é¢ä¿çå¨è¯¥è®¢åçæªå¼ç¥¨éé¢ä¸ï¼ä¸æ¬¡å¼ç¥¨ä»å¯éæ©è¯¥è®¢åã docs/sql/erp_purchase_invoice_order_20261104.sql
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,34 @@ -- ===================================================================== -- å®å¤ä¸ééæ±ï¼éè´æ¥ç¥¨æ¯æå ³èå¤å¼ éè´è®¢åï¼åæéé¢ï¼ -- 2026-11-04 -- 说æï¼ -- 1. æ°å»ºæ¥ç¥¨-éè´è®¢ååæè¡¨ erp_purchase_invoice_orderï¼ -- 2. å岿¥ç¥¨æãpurchase_order_id + priceãè¡¥ä¸æ¡åæè®°å½ï¼çä»·äºå订åï¼ï¼ -- 3. erp_purchase_invoice.purchase_order_id ä¿çï¼ååææç»ç第ä¸å¼ 订åï¼ä¸»è®¢åï¼ã -- èæ¬å¯é夿§è¡ã -- ===================================================================== -- 1. åæè¡¨ CREATE TABLE IF NOT EXISTS `erp_purchase_invoice_order` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ç¼å·', `invoice_id` bigint NOT NULL COMMENT 'æ¥ç¥¨ç¼å·ï¼å ³è erp_purchase_invoice.id', `purchase_order_id` bigint NOT NULL COMMENT 'éè´è®¢åç¼å·ï¼å ³è erp_purchase_order.id', `price` decimal(24, 6) NOT NULL DEFAULT 0 COMMENT 'æ¬æ¬¡åæå°æ¥ç¥¨çéé¢ï¼åä½ï¼å ', `sort` int NOT NULL DEFAULT 0 COMMENT 'æåº', `creator` varchar(64) DEFAULT '' COMMENT 'å建è ', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', `updater` varchar(64) DEFAULT '' COMMENT 'æ´æ°è ', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', PRIMARY KEY (`id`), KEY `idx_invoice_id` (`invoice_id`), KEY `idx_purchase_order_id` (`purchase_order_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='ERP éè´æ¥ç¥¨-éè´è®¢ååæ'; -- 2. å岿°æ®åå¡«ï¼æ¯å¼ èæ¥ç¥¨è¡¥ä¸æ¡åæè®°å½ï¼å·²åå¡«çä¸éå¤æå ¥ï¼ INSERT INTO `erp_purchase_invoice_order` (`invoice_id`, `purchase_order_id`, `price`, `sort`, `creator`, `updater`) SELECT i.`id`, i.`purchase_order_id`, i.`price`, 0, i.`creator`, i.`updater` FROM `erp_purchase_invoice` i WHERE i.`deleted` = 0 AND i.`purchase_order_id` IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `erp_purchase_invoice_order` a WHERE a.`invoice_id` = i.`id`); yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java
@@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.erp.controller.admin.purchase; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; @@ -9,11 +10,13 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceOrderRespVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoicePageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceRespVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceSaveReqVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderRespVO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceOrderDO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; import cn.iocoder.yudao.module.erp.service.finance.ErpFinancePaymentService; import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceService; @@ -38,14 +41,17 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; @@ -138,10 +144,33 @@ Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList( convertSet(invoiceList, ErpPurchaseInvoiceDO::getSupplierId)).getCheckedData().stream() .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a)); // 1.2 è·åéè´è®¢åå表 // 1.2 è·åéè´è®¢ååææç»ï¼ä¸å¼ æ¥ç¥¨å¯å ³èå¤å¼ 订åï¼ Map<Long, List<ErpPurchaseInvoiceOrderDO>> allocationMap = convertMultiMap( invoiceService.getPurchaseInvoiceOrderListByInvoiceIds( convertSet(invoiceList, ErpPurchaseInvoiceDO::getId)), ErpPurchaseInvoiceOrderDO::getInvoiceId); // 1.2.1 å ¼å®¹å岿°æ®ï¼æ²¡æåææç»çæ¥ç¥¨ï¼æä¸»è®¢å + æ¥ç¥¨éé¢è§ä¸ºä¸æ¡åæ for (ErpPurchaseInvoiceDO invoice : invoiceList) { if (!allocationMap.containsKey(invoice.getId()) && invoice.getPurchaseOrderId() != null) { allocationMap.put(invoice.getId(), Collections.singletonList( ErpPurchaseInvoiceOrderDO.builder().invoiceId(invoice.getId()) .purchaseOrderId(invoice.getPurchaseOrderId()).price(invoice.getPrice()).sort(0).build())); } } // 1.2.2 è¿äºè®¢åçå ¨é¨åææç»ï¼ç¨äºè®¡ç®æ¯å¼ 订åãå ¶ä»æ¥ç¥¨å·²å ç¨éé¢ã Set<Long> orderIds = convertSet(allocationMap.values().stream().flatMap(List::stream).toList(), ErpPurchaseInvoiceOrderDO::getPurchaseOrderId); List<ErpPurchaseInvoiceOrderDO> orderAllocations = invoiceService.getPurchaseInvoiceOrderListByPurchaseOrderIds(orderIds); // 1.2.3 è·åéè´è®¢åå表ï¼å«ä¸»è®¢åï¼å ¼å®¹å岿°æ®ï¼ Set<Long> orderIdsAll = new HashSet<>(orderIds); invoiceList.stream().map(ErpPurchaseInvoiceDO::getPurchaseOrderId) .filter(java.util.Objects::nonNull).forEach(orderIdsAll::add); Map<Long, ErpPurchaseOrderDO> orderMap = convertMap( purchaseOrderService.getPurchaseOrderList(convertList(invoiceList, ErpPurchaseInvoiceDO::getPurchaseOrderId)), ErpPurchaseOrderDO::getId); purchaseOrderService.getPurchaseOrderList(new ArrayList<>(orderIdsAll)), ErpPurchaseOrderDO::getId); // 1.2.4 è·ååæè®¢åçä¾åºåå表ï¼ä¸å¼ æ¥ç¥¨å¯è½æ¶åå¤ä¸ªä¾åºåï¼ Map<Long, SrmSupplierRespDTO> orderSupplierMap = srmSupplierApi.getSupplierList( convertSet(orderMap.values(), ErpPurchaseOrderDO::getSupplierId)).getCheckedData().stream() .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a)); // 1.3 è·åå建人å表 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( convertSet(invoiceList, invoice -> NumberUtils.parseLong(invoice.getCreator()))); @@ -152,9 +181,37 @@ return BeanUtils.toBean(invoiceList, ErpPurchaseInvoiceRespVO.class, (invoiceVO) -> { // 2.1 æ¼æ¥ä¾åºååç§° findAndThen(supplierMap, invoiceVO.getSupplierId(), supplier -> invoiceVO.setSupplierName(supplier.getName())); // 2.2 æ¼æ¥éè´è®¢åä¿¡æ¯ // 2.2 æ¼æ¥éè´è®¢åä¿¡æ¯ï¼ä¸»è®¢åï¼ findAndThen(orderMap, invoiceVO.getPurchaseOrderId(), order -> invoiceVO.setOrder(BeanUtils.toBean(order, ErpPurchaseOrderRespVO.class))); // 2.2.1 æ¼æ¥éè´è®¢ååææç» List<ErpPurchaseInvoiceOrderDO> rows = allocationMap.getOrDefault(invoiceVO.getId(), Collections.emptyList()); List<ErpPurchaseInvoiceOrderRespVO> allocationVOs = new ArrayList<>(rows.size()); for (ErpPurchaseInvoiceOrderDO row : rows) { ErpPurchaseInvoiceOrderRespVO allocationVO = new ErpPurchaseInvoiceOrderRespVO() .setPurchaseOrderId(row.getPurchaseOrderId()).setPrice(row.getPrice()); ErpPurchaseOrderDO order = orderMap.get(row.getPurchaseOrderId()); if (order != null) { allocationVO.setOrderNo(order.getNo()).setSupplierId(order.getSupplierId()) .setOrderTotalPrice(order.getTotalPrice()); findAndThen(orderSupplierMap, order.getSupplierId(), supplier -> allocationVO.setSupplierName(supplier.getName())); } // 该订åå ¶ä»æ¥ç¥¨å·²å ç¨éé¢ = å ¨é¨åæ - æ¬å¼ æ¥ç¥¨çåæ BigDecimal otherInvoicedPrice = orderAllocations.stream() .filter(other -> ObjectUtil.equal(other.getPurchaseOrderId(), row.getPurchaseOrderId()) && !ObjectUtil.equal(other.getInvoiceId(), invoiceVO.getId())) .map(ErpPurchaseInvoiceOrderDO::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); allocationVO.setInvoicedPrice(otherInvoicedPrice); if (order != null) { allocationVO.setRemainPrice(order.getTotalPrice().subtract(otherInvoicedPrice)); } allocationVOs.add(allocationVO); } invoiceVO.setOrderAllocations(allocationVOs); invoiceVO.setOrderCount(allocationVOs.size()); invoiceVO.setOrderNos(CollUtil.join(allocationVOs, "ã", ErpPurchaseInvoiceOrderRespVO::getOrderNo)); // 2.3 æ¼æ¥å建人åç§° MapUtils.findAndThen(userMap, NumberUtils.parseLong(invoiceVO.getCreator()), user -> invoiceVO.setCreatorName(user.getNickname())); yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -157,7 +157,7 @@ @PreAuthorize("@ss.hasPermission('erp:purchase-order:query')") public CommonResult<PageResult<ErpPurchaseOrderRespVO>> getPurchaseOrderPage(@Valid ErpPurchaseOrderPageReqVO pageReqVO) { PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(pageReqVO); return success(buildPurchaseOrderVOPageResult(pageResult)); return success(buildPurchaseOrderVOPageResult(pageResult, pageReqVO.getInvoiceId())); } @GetMapping("/export-excel") @@ -167,7 +167,7 @@ public void exportPurchaseOrderExcel(@Valid ErpPurchaseOrderPageReqVO pageReqVO, HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List<ErpPurchaseOrderRespVO> list = buildPurchaseOrderVOPageResult(purchaseOrderService.getPurchaseOrderPage(pageReqVO)).getList(); List<ErpPurchaseOrderRespVO> list = buildPurchaseOrderVOPageResult(purchaseOrderService.getPurchaseOrderPage(pageReqVO), null).getList(); // å¯¼åº Excel ExcelUtils.write(response, "éè´è®¢å.xls", "æ°æ®", ErpPurchaseOrderRespVO.class, list); } @@ -220,7 +220,8 @@ return success(true); } private PageResult<ErpPurchaseOrderRespVO> buildPurchaseOrderVOPageResult(PageResult<ErpPurchaseOrderDO> pageResult) { private PageResult<ErpPurchaseOrderRespVO> buildPurchaseOrderVOPageResult(PageResult<ErpPurchaseOrderDO> pageResult, Long excludeInvoiceId) { if (CollUtil.isEmpty(pageResult.getList())) { return PageResult.empty(pageResult.getTotal()); } @@ -243,7 +244,7 @@ convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductUnitId)); // 1.6 å·²æ¥ç¥¨éé¢ï¼æéè´è®¢åç»è®¡ï¼ç¨äºè®¡ç®å©ä½å¯å¼ç¥¨éé¢ Map<Long, BigDecimal> invoicePriceSumMap = purchaseInvoiceService .getPurchaseInvoicePriceSumMapByOrderIds(convertSet(pageResult.getList(), ErpPurchaseOrderDO::getId)); .getPurchaseInvoicePriceSumMapByOrderIds(convertSet(pageResult.getList(), ErpPurchaseOrderDO::getId), excludeInvoiceId); // 2. å¼å§æ¼æ¥ return BeanUtils.toBean(pageResult, ErpPurchaseOrderRespVO.class, purchaseOrder -> { purchaseOrder.setItems(BeanUtils.toBean( yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceOrderReqVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,21 @@ package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.math.BigDecimal; @Schema(description = "管çåå° - ERP éè´æ¥ç¥¨çéè´è®¢ååæ Request VO") @Data public class ErpPurchaseInvoiceOrderReqVO { @Schema(description = "éè´è®¢åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") @NotNull(message = "éè´è®¢åç¼å·ä¸è½ä¸ºç©º") private Long purchaseOrderId; @Schema(description = "æ¬æ¬¡åæå°çæ¥ç¥¨éé¢ï¼åä½ï¼å ", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") @NotNull(message = "åæéé¢ä¸è½ä¸ºç©º") private BigDecimal price; } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceOrderRespVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; @Schema(description = "管çåå° - ERP éè´æ¥ç¥¨çéè´è®¢ååæ Response VO") @Data public class ErpPurchaseInvoiceOrderRespVO { @Schema(description = "éè´è®¢åç¼å·", example = "17386") private Long purchaseOrderId; @Schema(description = "éè´è®¢åå·", example = "CGDD20260101001") private String orderNo; @Schema(description = "ä¾åºåç¼å·", example = "29399") private Long supplierId; @Schema(description = "ä¾åºååç§°", example = "å°çªèå ¬å¸") private String supplierName; @Schema(description = "éè´è®¢åéé¢ï¼åä½ï¼å ", example = "20000.00") private BigDecimal orderTotalPrice; @Schema(description = "该订åå ¶ä»æ¥ç¥¨å·²å ç¨éé¢ï¼åä½ï¼å ", example = "8000.00") private BigDecimal invoicedPrice; @Schema(description = "该订åå©ä½æªå¼ç¥¨éé¢ï¼åä½ï¼å ", example = "12000.00") private BigDecimal remainPrice; @Schema(description = "æ¬å¼ æ¥ç¥¨æ¬æ¬¡åæå°è¯¥è®¢åçéé¢ï¼åä½ï¼å ", example = "9000.00") private BigDecimal price; } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceRespVO.java
@@ -30,12 +30,20 @@ @ExcelProperty("ä¾åºååç§°") private String supplierName; @Schema(description = "éè´è®¢åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") @Schema(description = "éè´è®¢åç¼å·ï¼ä¸»è®¢åï¼å³åææç»ç¬¬ä¸å¼ ï¼", example = "17386") @ExcelProperty("éè´è®¢åç¼å·") private Long purchaseOrderId; @Schema(description = "éè´è®¢åä¿¡æ¯") private ErpPurchaseOrderRespVO order; @Schema(description = "å ³èéè´è®¢åå·ï¼å¤å¼ æ¶ä»¥ ã æ¼æ¥ï¼", example = "CGDD20260101001ãCGDD20260101002") @ExcelProperty("éè´è®¢åå·") private String orderNos; @Schema(description = "å ³èéè´è®¢åæ°é", example = "2") private Integer orderCount; @Schema(description = "éè´è®¢ååææç»") private List<ErpPurchaseInvoiceOrderRespVO> orderAllocations; @Schema(description = "å票å·ç ", example = "02564178") @ExcelProperty("å票å·ç ") private String invoiceNo; yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceSaveReqVO.java
@@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -21,10 +22,14 @@ @Schema(description = "ä¾åºåç¼å·", example = "29399") private Long supplierId; // è¯¥åæ®µä¸éè¿åç«¯ä¼ éï¼èæ¯ purchaseOrderId æ¥è¯¢åºæ¥è®¾ç½®è¿å» @Schema(description = "éè´è®¢åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") @NotNull(message = "éè´è®¢åç¼å·ä¸è½ä¸ºç©º") @Schema(description = "éè´è®¢åç¼å·ï¼ä¸»è®¢åï¼ååææç»ä¸ç第ä¸å¼ ï¼ç±å端åå¡«ï¼", example = "17386") private Long purchaseOrderId; @Schema(description = "éè´è®¢ååææç»ï¼ä¸å¼ æ¥ç¥¨å¯å ³èå¤å¼ éè´è®¢åï¼åæéé¢åè®¡å¿ é¡»çäºæ¥ç¥¨éé¢", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "请è³å°éæ©ä¸å¼ éè´è®¢å") private List<ErpPurchaseInvoiceOrderReqVO> orderAllocations; @Schema(description = "å票å·ç ", example = "02564178") private String invoiceNo; yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderPageReqVO.java
@@ -86,4 +86,13 @@ @Schema(description = "æ¯å¦ä» è¿åå©ä½å¯å¼ç¥¨éé¢å¤§äº 0 ç订åï¼éè´æ¥ç¥¨ç»è®°ä½¿ç¨ï¼", example = "true") private Boolean remainPriceEnable; /** * éæé¤çæ¥ç¥¨ç¼å·ï¼ç¼è¾æ¥ç¥¨æ¶ä¼ å ¥ï¼ * * ç¼è¾æ¥ç¥¨éæ°éæ©éè´è®¢åæ¶ï¼ææ¬å¼ æ¥ç¥¨å·²åæçéé¢éå订åï¼æªå¼ç¥¨é颿ã订åéé¢ - å ¶ä»æ¥ç¥¨å·²åæã计ç®ï¼ * é¿å ææ¬å¼ æ¥ç¥¨èªèº«çåæå½æå·²å ç¨é¢åº¦ï¼å¯¼è´éæ°åææ¶é¢åº¦è¢«å空æ£åã */ @Schema(description = "æé¤çæ¥ç¥¨ç¼å·ï¼ç¼è¾æ¥ç¥¨æ¶ä¼ ï¼è®¡ç®æªå¼ç¥¨é颿¶åé¤è¯¥æ¥ç¥¨èªèº«çåæï¼", example = "1024") private Long invoiceId; } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInvoiceOrderDO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,54 @@ package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; import java.math.BigDecimal; /** * ERP éè´æ¥ç¥¨-éè´è®¢ååæ DO * * ä¸å¼ æ¥ç¥¨å¯ä»¥å ³èå¤å¼ éè´è®¢åï¼æ¬è¡¨è®°å½æ¯å¼ è®¢åæ¬æ¬¡åæå°çæ¥ç¥¨éé¢ã * * @author è¶ çº§ç®¡çå */ @TableName("erp_purchase_invoice_order") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor public class ErpPurchaseInvoiceOrderDO extends BaseDO { /** * ç¼å· */ @TableId private Long id; /** * æ¥ç¥¨ç¼å· * * å ³è {@link ErpPurchaseInvoiceDO#getId()} */ private Long invoiceId; /** * éè´è®¢åç¼å· * * å ³è {@link ErpPurchaseOrderDO#getId()} */ private Long purchaseOrderId; /** * æ¬æ¬¡åæå°æ¥ç¥¨çéé¢ï¼åä½ï¼å * * åä¸å¼ æ¥ç¥¨ä¸ææåæéé¢ä¹å = æ¥ç¥¨éé¢ */ private BigDecimal price; /** * æåº */ private Integer sort; } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java
@@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; import java.util.List; /** @@ -26,23 +25,16 @@ return selectPage(reqVO, new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>() .likeIfPresent(ErpPurchaseInvoiceDO::getNo, reqVO.getNo()) .eqIfPresent(ErpPurchaseInvoiceDO::getSupplierId, reqVO.getSupplierId()) .eqIfPresent(ErpPurchaseInvoiceDO::getPurchaseOrderId, reqVO.getPurchaseOrderId()) // æéè´è®¢åè¿æ»¤ï¼ä¸å¼ æ¥ç¥¨å¯åæå°å¤å¼ 订åï¼éæ¥åææç» .apply(reqVO.getPurchaseOrderId() != null, "erp_purchase_invoice.id IN (SELECT a.invoice_id FROM erp_purchase_invoice_order a " + "WHERE a.purchase_order_id = {0} AND a.deleted = 0)", reqVO.getPurchaseOrderId()) .orderByDesc(ErpPurchaseInvoiceDO::getId)); } default List<ErpPurchaseInvoiceDO> selectListByOrderId(Long orderId) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>() .eq(ErpPurchaseInvoiceDO::getPurchaseOrderId, orderId)); } default List<ErpPurchaseInvoiceDO> selectListBySupplierId(Long supplierId) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>() .eq(ErpPurchaseInvoiceDO::getSupplierId, supplierId)); } default List<ErpPurchaseInvoiceDO> selectListByOrderIds(Collection<Long> orderIds) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>() .in(ErpPurchaseInvoiceDO::getPurchaseOrderId, orderIds)); } } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceOrderMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,46 @@ package cn.iocoder.yudao.module.erp.dal.mysql.purchase; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceOrderDO; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; import java.util.List; /** * ERP éè´æ¥ç¥¨-éè´è®¢ååæ Mapper * * @author è¶ çº§ç®¡çå */ @Mapper public interface ErpPurchaseInvoiceOrderMapper extends BaseMapperX<ErpPurchaseInvoiceOrderDO> { default List<ErpPurchaseInvoiceOrderDO> selectListByInvoiceId(Long invoiceId) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceOrderDO>() .eq(ErpPurchaseInvoiceOrderDO::getInvoiceId, invoiceId) .orderByAsc(ErpPurchaseInvoiceOrderDO::getSort)); } default List<ErpPurchaseInvoiceOrderDO> selectListByInvoiceIds(Collection<Long> invoiceIds) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceOrderDO>() .in(ErpPurchaseInvoiceOrderDO::getInvoiceId, invoiceIds) .orderByAsc(ErpPurchaseInvoiceOrderDO::getSort)); } default List<ErpPurchaseInvoiceOrderDO> selectListByPurchaseOrderId(Long purchaseOrderId) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceOrderDO>() .eq(ErpPurchaseInvoiceOrderDO::getPurchaseOrderId, purchaseOrderId)); } default List<ErpPurchaseInvoiceOrderDO> selectListByPurchaseOrderIds(Collection<Long> purchaseOrderIds) { return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceOrderDO>() .in(ErpPurchaseInvoiceOrderDO::getPurchaseOrderId, purchaseOrderIds)); } default int deleteByInvoiceId(Long invoiceId) { return delete(new LambdaQueryWrapperX<ErpPurchaseInvoiceOrderDO>() .eq(ErpPurchaseInvoiceOrderDO::getInvoiceId, invoiceId)); } } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
@@ -59,8 +59,15 @@ } // 坿¥ç¥¨ç»è®°ï¼å©ä½å¯å¼ç¥¨éé¢ > 0ï¼ï¼éé¢å·²å¼å®ç订åä¸å±ç¤º if (Boolean.TRUE.equals(reqVO.getRemainPriceEnable())) { query.apply("t.total_price > (SELECT IFNULL(SUM(i.price), 0) FROM erp_purchase_invoice i " + "WHERE i.purchase_order_id = t.id AND i.deleted = 0)"); // ç¼è¾æ¥ç¥¨æ¶ï¼invoiceId é空ï¼å餿¬å¼ æ¥ç¥¨èªèº«çåæï¼ç¸å½äºæå®å·²åæçéé¢éå订å String sql = "t.total_price > (SELECT IFNULL(SUM(a.price), 0) FROM erp_purchase_invoice_order a " + "INNER JOIN erp_purchase_invoice i ON i.id = a.invoice_id AND i.deleted = 0 " + "WHERE a.purchase_order_id = t.id AND a.deleted = 0"; if (reqVO.getInvoiceId() != null) { query.apply(sql + " AND a.invoice_id <> {0})", reqVO.getInvoiceId()); } else { query.apply(sql + ")"); } } if (reqVO.getProductId() != null) { query.leftJoin(ErpPurchaseOrderItemDO.class, ErpPurchaseOrderItemDO::getOrderId, ErpPurchaseOrderDO::getId) yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -62,6 +62,11 @@ ErrorCode PURCHASE_INVOICE_CREATE_FAIL_ORDER_NOT_APPROVE = new ErrorCode(1_030_105_006, "å建æ¥ç¥¨å¤±è´¥ï¼åå ï¼éè´è®¢å䏿¯å®¡æ ¸éè¿ç¶æ"); ErrorCode PURCHASE_INVOICE_CREATE_FAIL_SUPPLIER_MISMATCH = new ErrorCode(1_030_105_007, "å建æ¥ç¥¨å¤±è´¥ï¼åå ï¼æ¥ç¥¨ä¾åºåä¸éè´è®¢åä¸ä¸è´"); ErrorCode PURCHASE_INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_030_105_008, "å建æ¥ç¥¨å¤±è´¥ï¼åå ï¼æ¥ç¥¨éé¢è¶ åºéè´è®¢åéé¢ï¼ç®åå¯å¼ç¥¨ï¼{} å "); ErrorCode PURCHASE_INVOICE_ALLOCATION_EMPTY = new ErrorCode(1_030_105_009, "请è³å°éæ©ä¸å¼ éè´è®¢åï¼å¹¶ææ¥ç¥¨éé¢åæå°è®¢åä¸"); ErrorCode PURCHASE_INVOICE_ALLOCATION_ORDER_DUPLICATE = new ErrorCode(1_030_105_010, "åä¸å¼ æ¥ç¥¨å çéè´è®¢åä¸è½éå¤éæ©"); ErrorCode PURCHASE_INVOICE_ALLOCATION_PRICE_INVALID = new ErrorCode(1_030_105_011, "åæéé¢å¿ 须填åä¸ä¸è½å°äº 0"); ErrorCode PURCHASE_INVOICE_ALLOCATION_SUM_MISMATCH = new ErrorCode(1_030_105_012, "åéè´è®¢åçåæéé¢åè®¡å¿ é¡»çäºæ¥ç¥¨éé¢"); ErrorCode PURCHASE_INVOICE_ALLOCATION_SUPPLIER_MISMATCH = new ErrorCode(1_030_105_013, "åä¸å¼ æ¥ç¥¨åªè½å ³èåä¸ä¾åºåçéè´è®¢å"); // ========== ERP éè´éè´§ï¼1-030-103-000ï¼ ========== ErrorCode PURCHASE_RETURN_NOT_EXISTS = new ErrorCode(1_030_103_000, "éè´éè´§åä¸åå¨"); yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceService.java
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoicePageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceSaveReqVO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceOrderDO; import jakarta.validation.Valid; import java.math.BigDecimal; @@ -98,6 +99,37 @@ * @param orderIds éè´è®¢åç¼å·å表 * @return Map<éè´è®¢åç¼å·, æ¥ç¥¨éé¢å计> */ Map<Long, BigDecimal> getPurchaseInvoicePriceSumMapByOrderIds(Collection<Long> orderIds); /** * è·å¾éè´è®¢åçå·²æ¥ç¥¨éé¢å计 * * @param orderIds éè´è®¢åç¼å·éå * @param excludeInvoiceId éæé¤çæ¥ç¥¨ç¼å·ï¼ç¼è¾æ¥ç¥¨æ¶ä¸ºè¯¥æ¥ç¥¨èªèº«ï¼å¯ä¸ºç©ºï¼ * @return éè´è®¢åç¼å· -> å·²æ¥ç¥¨éé¢ */ Map<Long, BigDecimal> getPurchaseInvoicePriceSumMapByOrderIds(Collection<Long> orderIds, Long excludeInvoiceId); /** * è·å¾æ¥ç¥¨çéè´è®¢ååææç» * * @param invoiceId æ¥ç¥¨ç¼å· * @return åææç»å表 */ List<ErpPurchaseInvoiceOrderDO> getPurchaseInvoiceOrderListByInvoiceId(Long invoiceId); /** * è·å¾æ¥ç¥¨çéè´è®¢ååææç»ï¼æ¹éï¼ * * @param invoiceIds æ¥ç¥¨ç¼å·å表 * @return åææç»å表 */ List<ErpPurchaseInvoiceOrderDO> getPurchaseInvoiceOrderListByInvoiceIds(Collection<Long> invoiceIds); /** * è·å¾éè´è®¢åçåææç»ï¼æ¹éï¼ç¨äºç»è®¡è®¢åå·²æ¥ç¥¨éé¢ï¼ * * @param purchaseOrderIds éè´è®¢åç¼å·å表 * @return åææç»å表 */ List<ErpPurchaseInvoiceOrderDO> getPurchaseInvoiceOrderListByPurchaseOrderIds(Collection<Long> purchaseOrderIds); } yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
@@ -4,15 +4,21 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceOrderReqVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoicePageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceSaveReqVO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceOrderDO; import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper; import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceMapper; import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceOrderMapper; import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -20,11 +26,16 @@ import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; @@ -48,6 +59,9 @@ private ErpPurchaseInvoiceMapper invoiceMapper; @Resource private ErpPurchaseInvoiceOrderMapper invoiceOrderMapper; @Resource private ErpFinancePaymentMapper financePaymentMapper; @Resource @@ -57,15 +71,18 @@ private ErpPurchaseOrderService purchaseOrderService; @Resource private SrmSupplierApi srmSupplierApi; @Resource private StorageAttachmentApi storageAttachmentApi; @Override @Transactional(rollbackFor = Exception.class) public Long createPurchaseInvoice(ErpPurchaseInvoiceSaveReqVO createReqVO) { // 1.1 æ ¡éªå ³èæ°æ®åå¨ validateRelationDataExists(createReqVO); // 1.2 æ ¡éªå¯å¼ç¥¨éé¢è¶ è¿ä¸é validatePurchaseInvoicePriceExceedsLimit(createReqVO); // 1.1 æ ¡éªéè´è®¢åãåææç»ï¼åæå计 = æ¥ç¥¨éé¢ï¼ List<ErpPurchaseOrderDO> orders = validateOrderAllocations(createReqVO); // 1.2 æ ¡éªæ¯å¼ 订åçåæéé¢ä¸è¶ è¿è¯¥è®¢åå©ä½æªå¼ç¥¨éé¢ validateAllocationPriceExceedsLimit(createReqVO, orders); // 1.3 ç¡®å®æ¥ç¥¨ç¼å·ï¼å端填ååç¨èªå®ä¹åå·ï¼ç空åèªå¨çæï¼ï¼å¹¶æ ¡éªå¯ä¸æ§ String customNo = StrUtil.trim(createReqVO.getNo()); String no = StrUtil.isNotBlank(customNo) ? customNo @@ -74,40 +91,122 @@ throw exception(PURCHASE_INVOICE_NO_EXISTS); } // 2.1 æå ¥æ¥ç¥¨ï¼å建åå³ä¸ºææç¶æï¼æ éå®¡æ ¸ï¼ // 2.1 æå ¥æ¥ç¥¨ï¼å建åå³ä¸ºææç¶æï¼æ éå®¡æ ¸ï¼ï¼ä¸»è®¢åãä¾åºåååææç»ç¬¬ä¸å¼ ErpPurchaseInvoiceDO invoice = BeanUtils.toBean(createReqVO, ErpPurchaseInvoiceDO.class).setNo(no); invoiceMapper.insert(invoice); // 2.2 ç»å®å票éä»¶ // 2.2 æå ¥éè´è®¢ååææç» insertOrderAllocations(invoice.getId(), createReqVO); // 2.3 ç»å®å票éä»¶ if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) { storageAttachmentApi.bindAttachments("file", PURCHASE_INVOICE_RECORD_TYPE, invoice.getId(), createReqVO.getBlobIds()); } return invoice.getId(); } private void validateRelationDataExists(ErpPurchaseInvoiceSaveReqVO reqVO) { // æ ¡éªéè´è®¢åï¼å¿ é¡»å®¡æ ¸éè¿ ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(reqVO.getPurchaseOrderId()); // æ ¡éªä¾åºåä¸éè´è®¢åä¸è´ï¼é²å¾¡èæ°æ®ï¼æ¥ç¥¨å¿ é¡»å±äºè¯¥éè´è®¢åçä¾åºåï¼ if (reqVO.getSupplierId() != null && ObjectUtil.notEqual(purchaseOrder.getSupplierId(), reqVO.getSupplierId())) { throw exception(PURCHASE_INVOICE_CREATE_FAIL_SUPPLIER_MISMATCH); /** * æ ¡éªéè´è®¢ååææç»ï¼å¹¶æä¸»è®¢åãä¾åºåååå° reqVO ä¸ * * @return åææç»ä¸çéè´è®¢åå表ï¼é¡ºåºä¸æç»ä¸è´ï¼ */ private List<ErpPurchaseOrderDO> validateOrderAllocations(ErpPurchaseInvoiceSaveReqVO reqVO) { List<ErpPurchaseInvoiceOrderReqVO> allocations = reqVO.getOrderAllocations(); if (CollUtil.isEmpty(allocations)) { throw exception(PURCHASE_INVOICE_ALLOCATION_EMPTY); } reqVO.setSupplierId(purchaseOrder.getSupplierId()); // ä¾åºå以éè´è®¢å为å // 1. æ ¡éªè®¢åä¸éå¤ãå¿ é¡»å®¡æ ¸éè¿ Set<Long> orderIds = new HashSet<>(); List<ErpPurchaseOrderDO> orders = new ArrayList<>(); for (int i = 0; i < allocations.size(); i++) { ErpPurchaseInvoiceOrderReqVO allocation = allocations.get(i); if (!orderIds.add(allocation.getPurchaseOrderId())) { throw new ServiceException(PURCHASE_INVOICE_ALLOCATION_ORDER_DUPLICATE.getCode(), StrUtil.format("第 {} è¡çéè´è®¢åéå¤éæ©ï¼åä¸å¼ æ¥ç¥¨å æ¯å¼ éè´è®¢ååªè½åºç°ä¸æ¬¡ï¼è¯·å é¤éå¤è¡åéè¯", i + 1)); } if (allocation.getPrice() == null || allocation.getPrice().compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException(PURCHASE_INVOICE_ALLOCATION_PRICE_INVALID.getCode(), StrUtil.format("第 {} è¡çåæéé¢ä¸è½ä¸ºç©ºä¸ä¸è½å°äº 0ï¼è¯·å¡«åè¯¥è®¢åæ¬æ¬¡åæå°çæ¥ç¥¨éé¢", i + 1)); } orders.add(purchaseOrderService.validatePurchaseOrder(allocation.getPurchaseOrderId())); } // 1.2 æ ¡éªææéè´è®¢åå¿ é¡»æ¯åä¸ä¾åºåï¼æ¥ç¥¨å¯¹åºä¸ä¸ªä¾åºåï¼ validateSameSupplier(orders); // 2. æ ¡éªåæå计 = æ¥ç¥¨éé¢ BigDecimal allocationTotal = allocations.stream().map(ErpPurchaseInvoiceOrderReqVO::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); if (allocationTotal.compareTo(reqVO.getPrice()) != 0) { BigDecimal diff = reqVO.getPrice().subtract(allocationTotal); String detail = diff.compareTo(BigDecimal.ZERO) > 0 ? StrUtil.format("è¿æ {} å æªåæå°è®¢åï¼è¯·ä¸ºè®¢åå¡«ååæé颿æ¹é订å", diff.stripTrailingZeros().toPlainString()) : StrUtil.format("åæåè®¡æ¯æ¥ç¥¨éé¢å¤ {} å ï¼è¯·åå°åæéé¢", diff.negate().stripTrailingZeros().toPlainString()); throw new ServiceException(PURCHASE_INVOICE_ALLOCATION_SUM_MISMATCH.getCode(), StrUtil.format("æ¥ç¥¨éé¢ {} å ï¼å订ååæå计 {} å ï¼ä¸¤è å¿ é¡»ç¸çï¼å·®é¢ï¼{}ï¼ã", reqVO.getPrice().stripTrailingZeros().toPlainString(), allocationTotal.stripTrailingZeros().toPlainString(), detail)); } // 3. 主订åãä¾åºåååææç»ç¬¬ä¸å¼ ï¼ä¸å¼ æ¥ç¥¨æ¶åå¤ä¸ªä¾åºåæ¶ï¼å表/仿¬¾æ²¿ç¨ç¬¬ä¸å®¶ï¼ reqVO.setPurchaseOrderId(allocations.get(0).getPurchaseOrderId()); reqVO.setSupplierId(orders.get(0).getSupplierId()); return orders; } private void validatePurchaseInvoicePriceExceedsLimit(ErpPurchaseInvoiceSaveReqVO reqVO) { // 1. 计ç®å©ä½å¯å¼ç¥¨éé¢ï¼ä¸å æ¬ reqVO èªèº«ï¼æææææ¥ç¥¨é½å ç¨é¢åº¦ï¼é²æ¢ç´¯è®¡æ¥ç¥¨è¶ è¿éè´è®¢åéé¢ï¼ ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(reqVO.getPurchaseOrderId()); List<ErpPurchaseInvoiceDO> invoices = invoiceMapper.selectListByOrderId(reqVO.getPurchaseOrderId()); if (reqVO.getId() != null) { invoices.removeIf(invoice -> ObjectUtil.equal(invoice.getId(), reqVO.getId())); /** * æ ¡éªåææç»ä¸çéè´è®¢åæ¯å¦å±äºåä¸ä¾åºåï¼æ¥ç¥¨åªè½å¯¹åºä¸ä¸ªä¾åºå */ private void validateSameSupplier(List<ErpPurchaseOrderDO> orders) { ErpPurchaseOrderDO firstOrder = orders.get(0); for (int i = 1; i < orders.size(); i++) { ErpPurchaseOrderDO order = orders.get(i); if (ObjectUtil.notEqual(order.getSupplierId(), firstOrder.getSupplierId())) { throw new ServiceException(PURCHASE_INVOICE_ALLOCATION_SUPPLIER_MISMATCH.getCode(), StrUtil.format("第 {} è¡çéè´è®¢å[{}]ä¾åºå为[{}]ï¼ä¸ç¬¬ 1 è¡éè´è®¢å[{}]çä¾åºå[{}]ä¸ä¸è´ã" + "ä¸å¼ æ¥ç¥¨åªè½å ³èåä¸ä¾åºåçéè´è®¢åï¼è¯·ç§»é¤è¯¥è®¢åï¼ææ¹é该ä¾åºåçå ¶ä»éè´è®¢ååéè¯ã", i + 1, order.getNo(), getSupplierName(order.getSupplierId()), firstOrder.getNo(), getSupplierName(firstOrder.getSupplierId()))); } } BigDecimal remainPrice = purchaseOrder.getTotalPrice().subtract( getSumValue(invoices, ErpPurchaseInvoiceDO::getPrice, BigDecimal::add, BigDecimal.ZERO)); // 2. æ ¡éªé颿¯å¦è¶ è¿ if (reqVO.getPrice().compareTo(remainPrice) > 0) { throw exception(PURCHASE_INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, remainPrice); } private String getSupplierName(Long supplierId) { SrmSupplierRespDTO supplier = srmSupplierApi.getSupplier(supplierId).getCheckedData(); return supplier == null ? "ID=" + supplierId : supplier.getName(); } private void validateAllocationPriceExceedsLimit(ErpPurchaseInvoiceSaveReqVO reqVO, List<ErpPurchaseOrderDO> orders) { List<ErpPurchaseInvoiceOrderReqVO> allocations = reqVO.getOrderAllocations(); for (int i = 0; i < allocations.size(); i++) { ErpPurchaseInvoiceOrderReqVO allocation = allocations.get(i); ErpPurchaseOrderDO order = orders.get(i); // 1. 该订åå ¶ä»æ¥ç¥¨å·²å ç¨çéé¢ï¼ç¼è¾æ¶æé¤æ¬å¼ æ¥ç¥¨èªèº«çåæï¼ List<ErpPurchaseInvoiceOrderDO> existed = invoiceOrderMapper.selectListByPurchaseOrderId(order.getId()); if (reqVO.getId() != null) { existed.removeIf(row -> ObjectUtil.equal(row.getInvoiceId(), reqVO.getId())); } BigDecimal invoicedPrice = getSumValue(existed, ErpPurchaseInvoiceOrderDO::getPrice, BigDecimal::add, BigDecimal.ZERO); BigDecimal remainPrice = order.getTotalPrice().subtract(invoicedPrice); // 2. æ ¡éªé颿¯å¦è¶ è¿ if (allocation.getPrice().compareTo(remainPrice) > 0) { throw new ServiceException(PURCHASE_INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT.getCode(), StrUtil.format("éè´è®¢å[{}]çåæéé¢è¶ åºè¯¥è®¢åæªå¼ç¥¨éé¢ï¼è®¢åéé¢ {} å ï¼å ¶ä»æ¥ç¥¨å·²å ç¨ {} å ï¼" + "å©ä½æªå¼ç¥¨ {} å ï¼æ¬æ¬¡åæ {} å ãè¯·è°æ´è¯¥è®¢åçåæéé¢ï¼ææ¹éå ¶ä»éè´è®¢ååéè¯ã", order.getNo(), order.getTotalPrice().stripTrailingZeros().toPlainString(), invoicedPrice.stripTrailingZeros().toPlainString(), remainPrice.stripTrailingZeros().toPlainString(), allocation.getPrice().stripTrailingZeros().toPlainString())); } } } private void insertOrderAllocations(Long invoiceId, ErpPurchaseInvoiceSaveReqVO reqVO) { List<ErpPurchaseInvoiceOrderDO> rows = new ArrayList<>(); for (int i = 0; i < reqVO.getOrderAllocations().size(); i++) { ErpPurchaseInvoiceOrderReqVO allocation = reqVO.getOrderAllocations().get(i); rows.add(ErpPurchaseInvoiceOrderDO.builder().invoiceId(invoiceId) .purchaseOrderId(allocation.getPurchaseOrderId()) .price(allocation.getPrice()).sort(i).build()); } rows.forEach(invoiceOrderMapper::insert); } @Override @@ -116,15 +215,19 @@ Assert.notNull(updateReqVO.getId(), "æ¥ç¥¨ç¼å·ä¸è½ä¸ºç©º"); // 1.1 æ ¡éªåå¨ validatePurchaseInvoiceExists(updateReqVO.getId()); // 1.2 æ ¡éªå ³èæ°æ® + å¯å¼ç¥¨éé¢ validateRelationDataExists(updateReqVO); validatePurchaseInvoicePriceExceedsLimit(updateReqVO); // 1.2 æ ¡éªéè´è®¢åãåææç»ãéé¢ä¸é List<ErpPurchaseOrderDO> orders = validateOrderAllocations(updateReqVO); validateAllocationPriceExceedsLimit(updateReqVO, orders); // 2. æ´æ°æ¥ç¥¨ ErpPurchaseInvoiceDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseInvoiceDO.class); invoiceMapper.updateById(updateObj); // 3. å¢éæ´æ°å票éä»¶ // 3. é建éè´è®¢ååææç» invoiceOrderMapper.deleteByInvoiceId(updateReqVO.getId()); insertOrderAllocations(updateReqVO.getId(), updateReqVO); // 4. å¢éæ´æ°å票éä»¶ storageAttachmentApi.updateAttachments("file", PURCHASE_INVOICE_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds()); } @@ -143,6 +246,8 @@ storageAttachmentApi.deleteAttachmentsByRecord(PURCHASE_INVOICE_RECORD_TYPE, id); // 2.2 å 餿¥ç¥¨ invoiceMapper.deleteById(id); // 2.3 å é¤éè´è®¢ååææç»ï¼éæ¾è®¢åçå·²æ¥ç¥¨éé¢ï¼ invoiceOrderMapper.deleteByInvoiceId(id); } private ErpPurchaseInvoiceDO validatePurchaseInvoiceExists(Long id) { @@ -173,7 +278,11 @@ @Override public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListByOrderId(Long orderId) { return invoiceMapper.selectListByOrderId(orderId); List<ErpPurchaseInvoiceOrderDO> allocations = invoiceOrderMapper.selectListByPurchaseOrderId(orderId); if (CollUtil.isEmpty(allocations)) { return Collections.emptyList(); } return invoiceMapper.selectByIds(convertSet(allocations, ErpPurchaseInvoiceOrderDO::getInvoiceId)); } @Override @@ -182,13 +291,38 @@ } @Override public Map<Long, BigDecimal> getPurchaseInvoicePriceSumMapByOrderIds(Collection<Long> orderIds) { public Map<Long, BigDecimal> getPurchaseInvoicePriceSumMapByOrderIds(Collection<Long> orderIds, Long excludeInvoiceId) { if (CollUtil.isEmpty(orderIds)) { return Collections.emptyMap(); } List<ErpPurchaseInvoiceDO> invoices = invoiceMapper.selectListByOrderIds(orderIds); return invoices.stream().collect(Collectors.groupingBy(ErpPurchaseInvoiceDO::getPurchaseOrderId, Collectors.mapping(ErpPurchaseInvoiceDO::getPrice, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)))); List<ErpPurchaseInvoiceOrderDO> allocations = invoiceOrderMapper.selectListByPurchaseOrderIds(orderIds); if (excludeInvoiceId != null) { // ç¼è¾æ¥ç¥¨æ¶ææ¬å¼ æ¥ç¥¨å·²åæçéé¢éå订å allocations.removeIf(row -> ObjectUtil.equal(row.getInvoiceId(), excludeInvoiceId)); } return allocations.stream().collect(Collectors.groupingBy(ErpPurchaseInvoiceOrderDO::getPurchaseOrderId, Collectors.mapping(ErpPurchaseInvoiceOrderDO::getPrice, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)))); } @Override public List<ErpPurchaseInvoiceOrderDO> getPurchaseInvoiceOrderListByInvoiceId(Long invoiceId) { return invoiceOrderMapper.selectListByInvoiceId(invoiceId); } @Override public List<ErpPurchaseInvoiceOrderDO> getPurchaseInvoiceOrderListByInvoiceIds(Collection<Long> invoiceIds) { if (CollUtil.isEmpty(invoiceIds)) { return Collections.emptyList(); } return invoiceOrderMapper.selectListByInvoiceIds(invoiceIds); } @Override public List<ErpPurchaseInvoiceOrderDO> getPurchaseInvoiceOrderListByPurchaseOrderIds(Collection<Long> purchaseOrderIds) { if (CollUtil.isEmpty(purchaseOrderIds)) { return Collections.emptyList(); } return invoiceOrderMapper.selectListByPurchaseOrderIds(purchaseOrderIds); } }