gaoluyang
6 天以前 006020bc9a87dc40c2e73bc50f244b348b831de1
恢复 ERP 采购入库/退货后端,补齐采购统计接口

- 恢复 8c14b507 删除的采购入库、采购退货 Controller/Service/DO/Mapper,
物料与单位改走 MDM API,库存同步改走 MES WMS API,供应商名称改走 SRM
- WmsStockApi 及请求/响应 DTO 移入 yudao-module-mes-api,供 ERP 依赖
- 采购统计新增 /supplier、/price、/uncompleted 三个分页接口,
/summary 在原有首页金额字段上补充订单/入库/退货的总数与总金额
- 修复采购订单回写入库/退货数量时,历史数据 in_count/return_count 为 NULL 导致的 NPE
- 新增 CFWJ 建表脚本 erp_purchase_in / erp_purchase_return 及默认结算账户

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加25个文件
已重命名5个文件
已修改8个文件
3158 ■■■■■ 文件已修改
docs/sql/cfwj_erp_purchase_in_return_20261103.sql 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseReturnController.java 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInPageReqVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInSaveReqVO.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnPageReqVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnSaveReqVO.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/ErpPurchaseStatisticsController.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchasePriceAnalysisRespVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseStatisticsReqVO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseSummaryRespVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseSupplierStatisticsRespVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseUncompletedOrderRespVO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInItemDO.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnDO.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnItemDO.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInItemMapper.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInMapper.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnItemMapper.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnMapper.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/statistics/ErpPurchaseStatisticsMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java 352 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnService.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/statistics/ErpPurchaseStatisticsService.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/statistics/ErpPurchaseStatisticsServiceImpl.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsPurchaseInboundReqDTO.java 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsSalesOutboundReqDTO.java 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsStockDetailRespDTO.java 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsStockQueryReqDTO.java 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/stock/WmsStockApi.java 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/cfwj_erp_purchase_in_return_20261103.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,120 @@
-- CFWJ(诚发五金)ERP é‡‡è´­å…¥åº“ / é‡‡è´­é€€è´§è¡¨ç»“æž„ 2026-11-03
-- èƒŒæ™¯ï¼šERP é‡‡è´­å…¥åº“、采购退货代码在 8c14b507 è¢«åˆ é™¤åŽé‡æ–°æ¢å¤ï¼Œ
--       ç»Ÿè®¡é¡µï¼ˆerp/purchase-statistics)依赖 erp_purchase_in / erp_purchase_return ä¸¤å¼ è¡¨ã€‚
-- è¯´æ˜Žï¼šè¡¨ç»“构对齐现有 erp_purchase_order(无 tenant_id åˆ—,deleted ä½¿ç”¨ bit(1))。
-- å¹‚等:CREATE TABLE IF NOT EXISTS,可重复执行。
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `erp_purchase_in` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(255) NOT NULL COMMENT '采购入库单号',
  `status` int NOT NULL COMMENT '入库状态',
  `supplier_id` bigint DEFAULT NULL COMMENT '供应商编号',
  `account_id` bigint DEFAULT NULL COMMENT '结算账户编号',
  `in_time` datetime NOT NULL COMMENT '入库时间',
  `order_id` bigint DEFAULT NULL COMMENT '采购订单编号',
  `order_no` varchar(255) DEFAULT NULL COMMENT '采购订单号',
  `total_count` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '合计数量',
  `total_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '最终合计价格,单位:元',
  `payment_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '已支付金额,单位:元',
  `total_product_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '合计产品价格,单位:元',
  `total_tax_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '合计税额,单位:元',
  `discount_percent` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '优惠率,百分比',
  `discount_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '优惠金额,单位:元',
  `other_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '其它金额,单位:元',
  `remark` varchar(255) DEFAULT NULL 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`) USING BTREE,
  KEY `idx_no` (`no`) USING BTREE,
  KEY `idx_order_id` (`order_id`) USING BTREE,
  KEY `idx_in_time` (`in_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='ERP é‡‡è´­å…¥åº“';
CREATE TABLE IF NOT EXISTS `erp_purchase_in_items` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `in_id` bigint NOT NULL COMMENT '采购入库编号',
  `order_item_id` bigint DEFAULT NULL COMMENT '采购订单项编号',
  `warehouse_id` bigint DEFAULT NULL COMMENT '仓库编号(MES ä»“库)',
  `product_id` bigint DEFAULT NULL COMMENT '物料编号(MDM ç‰©æ–™ï¼‰',
  `product_unit_id` bigint DEFAULT NULL COMMENT '产品单位编号',
  `product_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '产品单价,单位:元',
  `count` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '数量',
  `total_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '总价,单位:元',
  `tax_percent` decimal(24,6) DEFAULT NULL COMMENT '税率,百分比',
  `tax_price` decimal(24,6) DEFAULT NULL COMMENT '税额,单位:元',
  `remark` varchar(255) DEFAULT NULL 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`) USING BTREE,
  KEY `idx_in_id` (`in_id`) USING BTREE,
  KEY `idx_order_item_id` (`order_item_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='ERP é‡‡è´­å…¥åº“项';
CREATE TABLE IF NOT EXISTS `erp_purchase_return` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(255) NOT NULL COMMENT '采购退货号',
  `status` int NOT NULL COMMENT '退货状态',
  `supplier_id` bigint DEFAULT NULL COMMENT '供应商编号',
  `account_id` bigint DEFAULT NULL COMMENT '结算账户编号',
  `return_time` datetime NOT NULL COMMENT '退货时间',
  `order_id` bigint DEFAULT NULL COMMENT '采购订单编号',
  `order_no` varchar(255) DEFAULT NULL COMMENT '采购订单号',
  `total_count` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '合计数量',
  `total_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '最终合计价格,单位:元',
  `refund_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '已退款金额,单位:元',
  `total_product_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '合计产品价格,单位:元',
  `total_tax_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '合计税额,单位:元',
  `discount_percent` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '优惠率,百分比',
  `discount_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '优惠金额,单位:元',
  `other_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '其它金额,单位:元',
  `remark` varchar(255) DEFAULT NULL 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`) USING BTREE,
  KEY `idx_no` (`no`) USING BTREE,
  KEY `idx_order_id` (`order_id`) USING BTREE,
  KEY `idx_return_time` (`return_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='ERP é‡‡è´­é€€è´§';
CREATE TABLE IF NOT EXISTS `erp_purchase_return_items` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `return_id` bigint NOT NULL COMMENT '采购退货编号',
  `order_item_id` bigint DEFAULT NULL COMMENT '采购订单项编号',
  `warehouse_id` bigint DEFAULT NULL COMMENT '仓库编号(MES ä»“库)',
  `product_id` bigint DEFAULT NULL COMMENT '物料编号(MDM ç‰©æ–™ï¼‰',
  `product_unit_id` bigint DEFAULT NULL COMMENT '产品单位编号',
  `product_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '产品单价,单位:元',
  `count` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '数量',
  `total_price` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '总价,单位:元',
  `tax_percent` decimal(24,6) DEFAULT NULL COMMENT '税率,百分比',
  `tax_price` decimal(24,6) DEFAULT NULL COMMENT '税额,单位:元',
  `remark` varchar(255) DEFAULT NULL 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`) USING BTREE,
  KEY `idx_return_id` (`return_id`) USING BTREE,
  KEY `idx_order_item_id` (`order_item_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='ERP é‡‡è´­é€€è´§é¡¹';
-- ---------------------------------------------------------------------------
-- ç»“算账户兜底:采购入库 / é‡‡è´­é€€è´§çš„创建都校验结算账户(erp_account),
-- è€Œ CFWJ åº“此前没有账户数据、也没有账户维护菜单,会导致“结算账户”下拉为空、单据建不出来。
-- è¿™é‡Œè¡¥ä¸€æ¡é»˜è®¤è´¦æˆ·ï¼Œå¹‚等(按 no åˆ¤æ–­ï¼‰ã€‚
-- ---------------------------------------------------------------------------
INSERT INTO `erp_account` (`name`, `no`, `remark`, `status`, `sort`, `default_status`, `creator`, `create_time`, `updater`, `update_time`)
SELECT '现金账户', 'CASH', '系统默认结算账户,采购入库/退货、财务收付款使用', 0, 0, 1, '1', NOW(), '1', NOW()
WHERE NOT EXISTS (SELECT 1 FROM `erp_account` WHERE `no` = 'CASH' AND `deleted` = 0);
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,178 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase;
import cn.hutool.core.collection.CollUtil;
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;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.mes.api.wms.stock.WmsStockApi;
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.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
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.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - ERP é‡‡è´­å…¥åº“")
@RestController
@RequestMapping("/erp/purchase-in")
@Validated
public class ErpPurchaseInController {
    @Resource
    private ErpPurchaseInService purchaseInService;
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private WmsStockApi wmsStockApi;
    @Resource
    private AdminUserApi adminUserApi;
    @PostMapping("/create")
    @Operation(summary = "创建采购入库")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:create')")
    public CommonResult<Long> createPurchaseIn(@Valid @RequestBody ErpPurchaseInSaveReqVO createReqVO) {
        return success(purchaseInService.createPurchaseIn(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新采购入库")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:update')")
    public CommonResult<Boolean> updatePurchaseIn(@Valid @RequestBody ErpPurchaseInSaveReqVO updateReqVO) {
        purchaseInService.updatePurchaseIn(updateReqVO);
        return success(true);
    }
    @PutMapping("/update-status")
    @Operation(summary = "更新采购入库的状态")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:update-status')")
    public CommonResult<Boolean> updatePurchaseInStatus(@RequestParam("id") Long id,
                                                      @RequestParam("status") Integer status) {
        purchaseInService.updatePurchaseInStatus(id, status);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除采购入库")
    @Parameter(name = "ids", description = "编号数组", required = true)
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:delete')")
    public CommonResult<Boolean> deletePurchaseIn(@RequestParam("ids") List<Long> ids) {
        purchaseInService.deletePurchaseIn(ids);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得采购入库")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:query')")
    public CommonResult<ErpPurchaseInRespVO> getPurchaseIn(@RequestParam("id") Long id) {
        ErpPurchaseInDO purchaseIn = purchaseInService.getPurchaseIn(id);
        if (purchaseIn == null) {
            return success(null);
        }
        List<ErpPurchaseInItemDO> purchaseInItemList = purchaseInService.getPurchaseInItemListByInId(id);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductId));
        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductUnitId));
        return success(BeanUtils.toBean(purchaseIn, ErpPurchaseInRespVO.class, purchaseInVO ->
                purchaseInVO.setItems(BeanUtils.toBean(purchaseInItemList, ErpPurchaseInRespVO.Item.class, item -> {
                    BigDecimal stockCount = wmsStockApi.getStockQuantity(item.getProductId(), item.getWarehouseId());
                    item.setStockCount(stockCount != null ? stockCount : BigDecimal.ZERO);
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                    MapUtils.findAndThen(unitMap, item.getProductUnitId(), unit -> item.setProductUnitName(unit.getName()));
                }))));
    }
    @GetMapping("/page")
    @Operation(summary = "获得采购入库分页")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:query')")
    public CommonResult<PageResult<ErpPurchaseInRespVO>> getPurchaseInPage(@Valid ErpPurchaseInPageReqVO pageReqVO) {
        PageResult<ErpPurchaseInDO> pageResult = purchaseInService.getPurchaseInPage(pageReqVO);
        return success(buildPurchaseInVOPageResult(pageResult));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出采购入库 Excel")
    @PreAuthorize("@ss.hasPermission('erp:purchase-in:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportPurchaseInExcel(@Valid ErpPurchaseInPageReqVO pageReqVO,
                                    HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<ErpPurchaseInRespVO> list = buildPurchaseInVOPageResult(purchaseInService.getPurchaseInPage(pageReqVO)).getList();
        // å¯¼å‡º Excel
        ExcelUtils.write(response, "采购入库.xls", "数据", ErpPurchaseInRespVO.class, list);
    }
    private PageResult<ErpPurchaseInRespVO> buildPurchaseInVOPageResult(PageResult<ErpPurchaseInDO> pageResult) {
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty(pageResult.getTotal());
        }
        // 1.1 å…¥åº“项
        List<ErpPurchaseInItemDO> purchaseInItemList = purchaseInService.getPurchaseInItemListByInIds(
                convertSet(pageResult.getList(), ErpPurchaseInDO::getId));
        Map<Long, List<ErpPurchaseInItemDO>> purchaseInItemMap = convertMultiMap(purchaseInItemList, ErpPurchaseInItemDO::getInId);
        // 1.2 ç‰©æ–™ä¿¡æ¯
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductId));
        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductUnitId));
        // 1.3 ä¾›åº”商信息(供应商主数据已迁移到 SRM)
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(pageResult.getList(), ErpPurchaseInDO::getSupplierId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 1.4 ç®¡ç†å‘˜ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), purchaseIn -> Long.parseLong(purchaseIn.getCreator())));
        // 2. å¼€å§‹æ‹¼æŽ¥
        return BeanUtils.toBean(pageResult, ErpPurchaseInRespVO.class, purchaseIn -> {
            purchaseIn.setItems(BeanUtils.toBean(purchaseInItemMap.get(purchaseIn.getId()), ErpPurchaseInRespVO.Item.class,
                    item -> {
                        MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                                .setProductBarCode(mdmItem.getBarCode()));
                        MapUtils.findAndThen(unitMap, item.getProductUnitId(), unit -> item.setProductUnitName(unit.getName()));
                    }));
            purchaseIn.setProductNames(CollUtil.join(purchaseIn.getItems(), ",", ErpPurchaseInRespVO.Item::getProductName));
            MapUtils.findAndThen(supplierMap, purchaseIn.getSupplierId(), supplier -> purchaseIn.setSupplierName(supplier.getName()));
            MapUtils.findAndThen(userMap, Long.parseLong(purchaseIn.getCreator()), user -> purchaseIn.setCreatorName(user.getNickname()));
        });
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseReturnController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,178 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase;
import cn.hutool.core.collection.CollUtil;
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;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.returns.ErpPurchaseReturnPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseReturnService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.mes.api.wms.stock.WmsStockApi;
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.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
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.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - ERP é‡‡è´­é€€è´§")
@RestController
@RequestMapping("/erp/purchase-return")
@Validated
public class ErpPurchaseReturnController {
    @Resource
    private ErpPurchaseReturnService purchaseReturnService;
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private WmsStockApi wmsStockApi;
    @Resource
    private AdminUserApi adminUserApi;
    @PostMapping("/create")
    @Operation(summary = "创建采购退货")
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:create')")
    public CommonResult<Long> createPurchaseReturn(@Valid @RequestBody ErpPurchaseReturnSaveReqVO createReqVO) {
        return success(purchaseReturnService.createPurchaseReturn(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新采购退货")
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:update')")
    public CommonResult<Boolean> updatePurchaseReturn(@Valid @RequestBody ErpPurchaseReturnSaveReqVO updateReqVO) {
        purchaseReturnService.updatePurchaseReturn(updateReqVO);
        return success(true);
    }
    @PutMapping("/update-status")
    @Operation(summary = "更新采购退货的状态")
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:update-status')")
    public CommonResult<Boolean> updatePurchaseReturnStatus(@RequestParam("id") Long id,
                                                      @RequestParam("status") Integer status) {
        purchaseReturnService.updatePurchaseReturnStatus(id, status);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除采购退货")
    @Parameter(name = "ids", description = "编号数组", required = true)
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:delete')")
    public CommonResult<Boolean> deletePurchaseReturn(@RequestParam("ids") List<Long> ids) {
        purchaseReturnService.deletePurchaseReturn(ids);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得采购退货")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:query')")
    public CommonResult<ErpPurchaseReturnRespVO> getPurchaseReturn(@RequestParam("id") Long id) {
        ErpPurchaseReturnDO purchaseReturn = purchaseReturnService.getPurchaseReturn(id);
        if (purchaseReturn == null) {
            return success(null);
        }
        List<ErpPurchaseReturnItemDO> purchaseReturnItemList = purchaseReturnService.getPurchaseReturnItemListByReturnId(id);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseReturnItemList, ErpPurchaseReturnItemDO::getProductId));
        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseReturnItemList, ErpPurchaseReturnItemDO::getProductUnitId));
        return success(BeanUtils.toBean(purchaseReturn, ErpPurchaseReturnRespVO.class, purchaseReturnVO ->
                purchaseReturnVO.setItems(BeanUtils.toBean(purchaseReturnItemList, ErpPurchaseReturnRespVO.Item.class, item -> {
                    BigDecimal stockCount = wmsStockApi.getStockQuantity(item.getProductId(), item.getWarehouseId());
                    item.setStockCount(stockCount != null ? stockCount : BigDecimal.ZERO);
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                    MapUtils.findAndThen(unitMap, item.getProductUnitId(), unit -> item.setProductUnitName(unit.getName()));
                }))));
    }
    @GetMapping("/page")
    @Operation(summary = "获得采购退货分页")
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:query')")
    public CommonResult<PageResult<ErpPurchaseReturnRespVO>> getPurchaseReturnPage(@Valid ErpPurchaseReturnPageReqVO pageReqVO) {
        PageResult<ErpPurchaseReturnDO> pageResult = purchaseReturnService.getPurchaseReturnPage(pageReqVO);
        return success(buildPurchaseReturnVOPageResult(pageResult));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出采购退货 Excel")
    @PreAuthorize("@ss.hasPermission('erp:purchase-return:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportPurchaseReturnExcel(@Valid ErpPurchaseReturnPageReqVO pageReqVO,
                                    HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<ErpPurchaseReturnRespVO> list = buildPurchaseReturnVOPageResult(purchaseReturnService.getPurchaseReturnPage(pageReqVO)).getList();
        // å¯¼å‡º Excel
        ExcelUtils.write(response, "采购退货.xls", "数据", ErpPurchaseReturnRespVO.class, list);
    }
    private PageResult<ErpPurchaseReturnRespVO> buildPurchaseReturnVOPageResult(PageResult<ErpPurchaseReturnDO> pageResult) {
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty(pageResult.getTotal());
        }
        // 1.1 é€€è´§é¡¹
        List<ErpPurchaseReturnItemDO> purchaseReturnItemList = purchaseReturnService.getPurchaseReturnItemListByReturnIds(
                convertSet(pageResult.getList(), ErpPurchaseReturnDO::getId));
        Map<Long, List<ErpPurchaseReturnItemDO>> purchaseReturnItemMap = convertMultiMap(purchaseReturnItemList, ErpPurchaseReturnItemDO::getReturnId);
        // 1.2 ç‰©æ–™ä¿¡æ¯
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseReturnItemList, ErpPurchaseReturnItemDO::getProductId));
        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseReturnItemList, ErpPurchaseReturnItemDO::getProductUnitId));
        // 1.3 ä¾›åº”商信息(供应商主数据已迁移到 SRM)
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(pageResult.getList(), ErpPurchaseReturnDO::getSupplierId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 1.4 ç®¡ç†å‘˜ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), purchaseReturn -> Long.parseLong(purchaseReturn.getCreator())));
        // 2. å¼€å§‹æ‹¼æŽ¥
        return BeanUtils.toBean(pageResult, ErpPurchaseReturnRespVO.class, purchaseReturn -> {
            purchaseReturn.setItems(BeanUtils.toBean(purchaseReturnItemMap.get(purchaseReturn.getId()), ErpPurchaseReturnRespVO.Item.class,
                    item -> {
                        MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                                .setProductBarCode(mdmItem.getBarCode()));
                        MapUtils.findAndThen(unitMap, item.getProductUnitId(), unit -> item.setProductUnitName(unit.getName()));
                    }));
            purchaseReturn.setProductNames(CollUtil.join(purchaseReturn.getItems(), ",", ErpPurchaseReturnRespVO.Item::getProductName));
            MapUtils.findAndThen(supplierMap, purchaseReturn.getSupplierId(), supplier -> purchaseReturn.setSupplierName(supplier.getName()));
            MapUtils.findAndThen(userMap, Long.parseLong(purchaseReturn.getCreator()), user -> purchaseReturn.setCreatorName(user.getNickname()));
        });
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - ERP é‡‡è´­å…¥åº“分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ErpPurchaseInPageReqVO extends PageParam {
    public static final Integer PAYMENT_STATUS_NONE = 0;
    public static final Integer PAYMENT_STATUS_PART = 1;
    public static final Integer PAYMENT_STATUS_ALL = 2;
    @Schema(description = "采购单编号", example = "XS001")
    private String no;
    @Schema(description = "供应商编号", example = "1724")
    private Long supplierId;
    @Schema(description = "入库时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] inTime;
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "入库状态", example = "2")
    private Integer status;
    @Schema(description = "创建者")
    private String creator;
    @Schema(description = "产品编号", example = "1")
    private Long productId;
    @Schema(description = "仓库编号", example = "1")
    private Long warehouseId;
    @Schema(description = "结算账号编号", example = "1")
    private Long accountId;
    @Schema(description = "付款状态", example = "1")
    private Integer paymentStatus;
    @Schema(description = "是否可付款", example = "true")
    private Boolean paymentEnable; // å¯¹åº” paymentStatus = [0, 1]
    @Schema(description = "采购单号", example = "1")
    private String orderNo;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,142 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ERP é‡‡è´­å…¥åº“ Response VO")
@Data
@ExcelIgnoreUnannotated
public class ErpPurchaseInRespVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    @ExcelProperty("编号")
    private Long id;
    @Schema(description = "入库单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
    @ExcelProperty("入库单编号")
    private String no;
    @Schema(description = "入库状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    @ExcelProperty("入库状态")
    private Integer status;
    @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "芋道")
    @ExcelProperty("供应商名称")
    private String supplierName;
    @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89")
    @ExcelProperty("结算账户编号")
    private Long accountId;
    @Schema(description = "入库时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("入库时间")
    private LocalDateTime inTime;
    @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    private Long orderId;
    @Schema(description = "采购订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
    private String orderNo;
    @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663")
    @ExcelProperty("合计数量")
    private BigDecimal totalCount;
    @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906")
    @ExcelProperty("最终合计价格")
    private BigDecimal totalPrice;
    @Schema(description = "已付款金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal paymentPrice;
    @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal totalProductPrice;
    @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal totalTaxPrice;
    @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
    private BigDecimal discountPercent;
    @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal discountPrice;
    @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal otherPrice;
    @Schema(description = "备注", example = "你猜")
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "创建人", example = "芋道")
    private String creator;
    @Schema(description = "创建人名称", example = "芋道")
    private String creatorName;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
    @Schema(description = "入库项列表", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<Item> items;
    @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("产品信息")
    private String productNames;
    @Data
    public static class Item {
        @Schema(description = "入库项编号", example = "11756")
        private Long id;
        @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756")
        private Long orderItemId;
        @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        private Long warehouseId;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        private Long productId;
        @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        private Long productUnitId;
        @Schema(description = "产品单价", example = "100.00")
        private BigDecimal productPrice;
        @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        @NotNull(message = "产品数量不能为空")
        private BigDecimal count;
        @Schema(description = "税率,百分比", example = "99.88")
        private BigDecimal taxPercent;
        @Schema(description = "税额,单位:元", example = "100.00")
        private BigDecimal taxPrice;
        @Schema(description = "备注", example = "随便")
        private String remark;
        // ========== å…³è”字段 ==========
        @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
        private String productName;
        @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
        private String productBarCode;
        @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒")
        private String productUnitName;
        @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        private BigDecimal stockCount; // è¯¥å­—段仅仅在“详情”和“编辑”时使用
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ERP é‡‡è´­å…¥åº“新增/修改 Request VO")
@Data
public class ErpPurchaseInSaveReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    private Long id;
    @Schema(description = "结算账户编号", example = "31189")
    private Long accountId;
    @Schema(description = "入库时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "入库时间不能为空")
    private LocalDateTime inTime;
    @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    @NotNull(message = "采购订单编号不能为空")
    private Long orderId;
    @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
    private BigDecimal discountPercent;
    @Schema(description = "其它金额,单位:元", example = "7127")
    private BigDecimal otherPrice;
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "入库清单列表")
    private List<Item> items;
    @Data
    public static class Item {
        @Schema(description = "入库项编号", example = "11756")
        private Long id;
        @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756")
        @NotNull(message = "采购订单项编号不能为空")
        private Long orderItemId;
        @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        @NotNull(message = "仓库编号不能为空")
        private Long warehouseId;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        @NotNull(message = "产品编号不能为空")
        private Long productId;
        @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        @NotNull(message = "产品单位单位不能为空")
        private Long productUnitId;
        @Schema(description = "产品单价", example = "100.00")
        private BigDecimal productPrice;
        @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        @NotNull(message = "产品数量不能为空")
        private BigDecimal count;
        @Schema(description = "税率,百分比", example = "99.88")
        private BigDecimal taxPercent;
        @Schema(description = "备注", example = "随便")
        private String remark;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - ERP é‡‡è´­é€€è´§åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ErpPurchaseReturnPageReqVO extends PageParam {
    public static final Integer REFUND_STATUS_NONE = 0;
    public static final Integer REFUND_STATUS_PART = 1;
    public static final Integer REFUND_STATUS_ALL = 2;
    @Schema(description = "采购单编号", example = "XS001")
    private String no;
    @Schema(description = "供应商编号", example = "1724")
    private Long supplierId;
    @Schema(description = "退货时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] returnTime;
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "退货状态", example = "2")
    private Integer status;
    @Schema(description = "创建者")
    private String creator;
    @Schema(description = "产品编号", example = "1")
    private Long productId;
    @Schema(description = "仓库编号", example = "1")
    private Long warehouseId;
    @Schema(description = "结算账号编号", example = "1")
    private Long accountId;
    @Schema(description = "采购单号", example = "1")
    private String orderNo;
    @Schema(description = "退款状态", example = "1")
    private Integer refundStatus;
    @Schema(description = "是否可退款", example = "true")
    private Boolean refundEnable; // å¯¹åº” refundStatus = [0, 1]
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,142 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ERP é‡‡è´­é€€è´§ Response VO")
@Data
@ExcelIgnoreUnannotated
public class ErpPurchaseReturnRespVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    @ExcelProperty("编号")
    private Long id;
    @Schema(description = "退货单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
    @ExcelProperty("退货单编号")
    private String no;
    @Schema(description = "退货状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    @ExcelProperty("退货状态")
    private Integer status;
    @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "芋道")
    @ExcelProperty("供应商名称")
    private String supplierName;
    @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89")
    @ExcelProperty("结算账户编号")
    private Long accountId;
    @Schema(description = "退货时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("退货时间")
    private LocalDateTime returnTime;
    @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    private Long orderId;
    @Schema(description = "采购订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
    private String orderNo;
    @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663")
    @ExcelProperty("合计数量")
    private BigDecimal totalCount;
    @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906")
    @ExcelProperty("最终合计价格")
    private BigDecimal totalPrice;
    @Schema(description = "已退款金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal refundPrice;
    @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal totalProductPrice;
    @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal totalTaxPrice;
    @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
    private BigDecimal discountPercent;
    @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal discountPrice;
    @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
    private BigDecimal otherPrice;
    @Schema(description = "备注", example = "你猜")
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "创建人", example = "芋道")
    private String creator;
    @Schema(description = "创建人名称", example = "芋道")
    private String creatorName;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
    @Schema(description = "退货项列表", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<Item> items;
    @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("产品信息")
    private String productNames;
    @Data
    public static class Item {
        @Schema(description = "退货项编号", example = "11756")
        private Long id;
        @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756")
        private Long orderItemId;
        @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        private Long warehouseId;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        private Long productId;
        @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        private Long productUnitId;
        @Schema(description = "产品单价", example = "100.00")
        private BigDecimal productPrice;
        @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        @NotNull(message = "产品数量不能为空")
        private BigDecimal count;
        @Schema(description = "税率,百分比", example = "99.88")
        private BigDecimal taxPercent;
        @Schema(description = "税额,单位:元", example = "100.00")
        private BigDecimal taxPrice;
        @Schema(description = "备注", example = "随便")
        private String remark;
        // ========== å…³è”字段 ==========
        @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
        private String productName;
        @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
        private String productBarCode;
        @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒")
        private String productUnitName;
        @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        private BigDecimal stockCount; // è¯¥å­—段仅仅在“详情”和“编辑”时使用
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ERP é‡‡è´­é€€è´§æ–°å¢ž/修改 Request VO")
@Data
public class ErpPurchaseReturnSaveReqVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    private Long id;
    @Schema(description = "结算账户编号", example = "31189")
    private Long accountId;
    @Schema(description = "退货时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "退货时间不能为空")
    private LocalDateTime returnTime;
    @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
    @NotNull(message = "采购订单编号不能为空")
    private Long orderId;
    @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
    private BigDecimal discountPercent;
    @Schema(description = "其它金额,单位:元", example = "7127")
    private BigDecimal otherPrice;
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "退货清单列表")
    private List<Item> items;
    @Data
    public static class Item {
        @Schema(description = "退货项编号", example = "11756")
        private Long id;
        @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756")
        @NotNull(message = "采购订单项编号不能为空")
        private Long orderItemId;
        @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        @NotNull(message = "仓库编号不能为空")
        private Long warehouseId;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        @NotNull(message = "产品编号不能为空")
        private Long productId;
        @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
        @NotNull(message = "产品单位单位不能为空")
        private Long productUnitId;
        @Schema(description = "产品单价", example = "100.00")
        private BigDecimal productPrice;
        @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        @NotNull(message = "产品数量不能为空")
        private BigDecimal count;
        @Schema(description = "税率,百分比", example = "99.88")
        private BigDecimal taxPercent;
        @Schema(description = "备注", example = "随便")
        private String remark;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/ErpPurchaseStatisticsController.java
@@ -2,14 +2,20 @@
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceAnalysisRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseStatisticsReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSummaryRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSupplierStatisticsRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseTimeSummaryRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseUncompletedOrderRespVO;
import cn.iocoder.yudao.module.erp.service.statistics.ErpPurchaseStatisticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@@ -40,12 +46,14 @@
        LocalDateTime yesterday = LocalDateTimeUtils.getYesterday();
        LocalDateTime month = LocalDateTimeUtils.getMonth();
        LocalDateTime year = LocalDateTimeUtils.getYear();
        ErpPurchaseSummaryRespVO summary = new ErpPurchaseSummaryRespVO()
        // 1.1 æ¦‚览:订单 / å…¥åº“ / é€€è´§çš„æ€»æ•°ä¸Žæ€»é‡‘额
        ErpPurchaseSummaryRespVO summary = purchaseStatisticsService.getPurchaseSummary();
        // 1.2 é¦–页看板:今日 / æ˜¨æ—¥ / æœ¬æœˆ / ä»Šå¹´é‡‡è´­é‡‘额(复用同一响应,避免两套接口)
        return success(summary
                .setTodayPrice(purchaseStatisticsService.getPurchasePrice(today, null))
                .setYesterdayPrice(purchaseStatisticsService.getPurchasePrice(yesterday, today))
                .setMonthPrice(purchaseStatisticsService.getPurchasePrice(month, null))
                .setYearPrice(purchaseStatisticsService.getPurchasePrice(year, null));
        return success(summary);
                .setYearPrice(purchaseStatisticsService.getPurchasePrice(year, null)));
    }
    @GetMapping("/time-summary")
@@ -64,4 +72,28 @@
        return success(summaryList);
    }
    @GetMapping("/supplier")
    @Operation(summary = "获得供应商供货统计")
    @PreAuthorize("@ss.hasPermission('erp:purchase-statistics:query')")
    public CommonResult<PageResult<ErpPurchaseSupplierStatisticsRespVO>> getSupplierStatistics(
            @Valid ErpPurchaseStatisticsReqVO reqVO) {
        return success(purchaseStatisticsService.getSupplierStatistics(reqVO));
    }
    @GetMapping("/price")
    @Operation(summary = "获得采购价格分析")
    @PreAuthorize("@ss.hasPermission('erp:purchase-statistics:query')")
    public CommonResult<PageResult<ErpPurchasePriceAnalysisRespVO>> getPriceAnalysis(
            @Valid ErpPurchaseStatisticsReqVO reqVO) {
        return success(purchaseStatisticsService.getPriceAnalysis(reqVO));
    }
    @GetMapping("/uncompleted")
    @Operation(summary = "获得未完成采购订单跟踪")
    @PreAuthorize("@ss.hasPermission('erp:purchase-statistics:query')")
    public CommonResult<PageResult<ErpPurchaseUncompletedOrderRespVO>> getUncompletedOrders(
            @Valid ErpPurchaseStatisticsReqVO reqVO) {
        return success(purchaseStatisticsService.getUncompletedOrders(reqVO));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchasePriceAnalysisRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - ERP é‡‡è´­ä»·æ ¼åˆ†æž Response VO")
@Data
public class ErpPurchasePriceAnalysisRespVO {
    @Schema(description = "产品编号(MDM ç‰©æ–™ç¼–号)", example = "1024")
    private Long productId;
    @Schema(description = "产品名称", example = "六角螺栓")
    private String productName;
    @Schema(description = "产品条码", example = "6901234567890")
    private String productBarCode;
    @Schema(description = "平均价格", example = "12.50")
    private BigDecimal avgPrice;
    @Schema(description = "最低价格", example = "10.00")
    private BigDecimal minPrice;
    @Schema(description = "最高价格", example = "15.00")
    private BigDecimal maxPrice;
    @Schema(description = "最近一次采购价格", example = "13.00")
    private BigDecimal latestPrice;
    @Schema(description = "价格变化率(最近价格相对平均价格,百分比)", example = "4.00")
    private BigDecimal priceChangeRate;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseStatisticsReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
 * ERP é‡‡è´­ç»Ÿè®¡æŸ¥è¯¢ Request VO
 *
 * ä¾›åº”商供货统计、价格分析、未完成订单跟踪三个页面共用
 *
 * @author è¶…级管理员
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ErpPurchaseStatisticsReqVO extends PageParam {
    /**
     * ä¾›åº”商编号
     */
    private Long supplierId;
    /**
     * äº§å“ç¼–号(MDM ç‰©æ–™ç¼–号)
     */
    private Long productId;
    /**
     * è®¢å•号(模糊匹配)
     */
    private String orderNo;
    /**
     * è®¢å•时间范围
     */
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] orderTime;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseSummaryRespVO.java
@@ -21,4 +21,24 @@
    @Schema(description = "今年采购金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "88888")
    private BigDecimal yearPrice;
    // ==================== é‡‡è´­ç»Ÿè®¡é¡µï¼ˆæ±‡æ€»å¡ç‰‡ï¼‰ ====================
    @Schema(description = "总订单数", example = "10")
    private Long totalOrderCount;
    @Schema(description = "总订单金额", example = "1024.00")
    private BigDecimal totalOrderAmount;
    @Schema(description = "总入库数", example = "8")
    private Long totalInCount;
    @Schema(description = "总入库金额", example = "888.00")
    private BigDecimal totalInAmount;
    @Schema(description = "总退货数", example = "1")
    private Long totalReturnCount;
    @Schema(description = "总退货金额", example = "100.00")
    private BigDecimal totalReturnAmount;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseSupplierStatisticsRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - ERP ä¾›åº”商供货统计 Response VO")
@Data
public class ErpPurchaseSupplierStatisticsRespVO {
    @Schema(description = "供应商编号", example = "1024")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "诚发五金")
    private String supplierName;
    @Schema(description = "订单数", example = "10")
    private Long orderCount;
    @Schema(description = "订单金额", example = "1024.00")
    private BigDecimal orderAmount;
    @Schema(description = "入库单数", example = "8")
    private Long inCount;
    @Schema(description = "入库金额", example = "888.00")
    private BigDecimal inAmount;
    @Schema(description = "退货单数", example = "1")
    private Long returnCount;
    @Schema(description = "退货金额", example = "100.00")
    private BigDecimal returnAmount;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/vo/purchase/ErpPurchaseUncompletedOrderRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - ERP æœªå®Œæˆé‡‡è´­è®¢å•跟踪 Response VO")
@Data
public class ErpPurchaseUncompletedOrderRespVO {
    @Schema(description = "订单编号", example = "1024")
    private Long orderId;
    @Schema(description = "订单号", example = "CGDD20261103000001")
    private String orderNo;
    @Schema(description = "供应商名称", example = "诚发五金")
    private String supplierName;
    @Schema(description = "订单时间")
    private LocalDateTime orderTime;
    @Schema(description = "产品名称(多个用逗号拼接)", example = "六角螺栓,垫片")
    private String productNames;
    @Schema(description = "订单金额", example = "1024.00")
    private BigDecimal orderAmount;
    @Schema(description = "已入库金额", example = "512.00")
    private BigDecimal inAmount;
    @Schema(description = "未完成金额", example = "512.00")
    private BigDecimal uncompletedAmount;
    @Schema(description = "超期天数(按下单后 30 å¤©äº¤æœŸä¼°ç®—,未超期为 0)", example = "3")
    private Integer daysOverdue;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * ERP é‡‡è´­å…¥åº“ DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName(value = "erp_purchase_in")
@KeySequence("erp_purchase_in_seq") // ç”¨äºŽ Oracle、PostgreSQL、Kingbase、DB2、H2 æ•°æ®åº“的主键自增。如果是 MySQL ç­‰æ•°æ®åº“,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpPurchaseInDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é‡‡è´­å…¥åº“单号
     */
    private String no;
    /**
     * å…¥åº“状态
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus}
     */
    private Integer status;
    /**
     * ä¾›åº”商编号
     *
     * å…³è” {@link ErpSupplierDO#getId()}
     */
    private Long supplierId;
    /**
     * ç»“算账户编号
     *
     * å…³è” {@link ErpAccountDO#getId()}
     */
    private Long accountId;
    /**
     * å…¥åº“æ—¶é—´
     */
    private LocalDateTime inTime;
    /**
     * é‡‡è´­è®¢å•编号
     *
     * å…³è” {@link ErpPurchaseOrderDO#getId()}
     */
    private Long orderId;
    /**
     * é‡‡è´­è®¢å•号
     *
     * å†—ä½™ {@link ErpPurchaseOrderDO#getNo()}
     */
    private String orderNo;
    /**
     * åˆè®¡æ•°é‡
     */
    private BigDecimal totalCount;
    /**
     * æœ€ç»ˆåˆè®¡ä»·æ ¼ï¼Œå•位:元
     *
     * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + otherPrice
     */
    private BigDecimal totalPrice;
    /**
     * å·²æ”¯ä»˜é‡‘额,单位:元
     *
     * ç›®çš„:和 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO} ç»“合,记录已支付金额
     */
    private BigDecimal paymentPrice;
    /**
     * åˆè®¡äº§å“ä»·æ ¼ï¼Œå•位:元
     */
    private BigDecimal totalProductPrice;
    /**
     * åˆè®¡ç¨Žé¢ï¼Œå•位:元
     */
    private BigDecimal totalTaxPrice;
    /**
     * ä¼˜æƒ çŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal discountPercent;
    /**
     * ä¼˜æƒ é‡‘额,单位:元
     *
     * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent
     */
    private BigDecimal discountPrice;
    /**
     * å…¶å®ƒé‡‘额,单位:元
     */
    private BigDecimal otherPrice;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInItemDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * ERP é‡‡è´­å…¥åº“项 DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("erp_purchase_in_items")
@KeySequence("erp_purchase_in_items_seq") // ç”¨äºŽ Oracle、PostgreSQL、Kingbase、DB2、H2 æ•°æ®åº“的主键自增。如果是 MySQL ç­‰æ•°æ®åº“,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpPurchaseInItemDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é‡‡è´­å…¥åº“编号
     *
     * å…³è” {@link ErpPurchaseInDO##getId()}
     */
    private Long inId;
    /**
     * é‡‡è´­è®¢å•项编号
     *
     * å…³è” {@link ErpPurchaseOrderItemDO#getId()}
     * ç›®çš„:方便更新关联的采购订单项的入库数量
     */
    private Long orderItemId;
    /**
     * ä»“库编号
     *
     * å…³è” MES ä»“库编号
     */
    private Long warehouseId;
    /**
     * äº§å“ç¼–号
     *
     * å…³è” MDM ç‰©æ–™ç¼–号
     */
    private Long productId;
    /**
     * äº§å“å•位单位
     *
     * å†—ä½™ MDM ç‰©æ–™è®¡é‡å•位编号
     */
    private Long productUnitId;
    /**
     * äº§å“å•位单价,单位:元
     */
    private BigDecimal productPrice;
    /**
     * æ•°é‡
     */
    private BigDecimal count;
    /**
     * æ€»ä»·ï¼Œå•位:元
     *
     * totalPrice = productPrice * count
     */
    private BigDecimal totalPrice;
    /**
     * ç¨ŽçŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal taxPercent;
    /**
     * ç¨Žé¢ï¼Œå•位:元
     *
     * taxPrice = totalPrice * taxPercent
     */
    private BigDecimal taxPrice;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * ERP é‡‡è´­é€€è´§ DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName(value = "erp_purchase_return")
@KeySequence("erp_purchase_return_seq") // ç”¨äºŽ Oracle、PostgreSQL、Kingbase、DB2、H2 æ•°æ®åº“的主键自增。如果是 MySQL ç­‰æ•°æ®åº“,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpPurchaseReturnDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é‡‡è´­é€€è´§å•号
     */
    private String no;
    /**
     * é€€è´§çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus}
     */
    private Integer status;
    /**
     * ä¾›åº”商编号
     *
     * å…³è” {@link ErpSupplierDO#getId()}
     */
    private Long supplierId;
    /**
     * ç»“算账户编号
     *
     * å…³è” {@link ErpAccountDO#getId()}
     */
    private Long accountId;
    /**
     * é€€è´§æ—¶é—´
     */
    private LocalDateTime returnTime;
    /**
     * é‡‡è´­è®¢å•编号
     *
     * å…³è” {@link ErpPurchaseOrderDO#getId()}
     */
    private Long orderId;
    /**
     * é‡‡è´­è®¢å•号
     *
     * å†—ä½™ {@link ErpPurchaseOrderDO#getNo()}
     */
    private String orderNo;
    /**
     * åˆè®¡æ•°é‡
     */
    private BigDecimal totalCount;
    /**
     * æœ€ç»ˆåˆè®¡ä»·æ ¼ï¼Œå•位:元
     *
     * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + otherPrice
     */
    private BigDecimal totalPrice;
    /**
     * å·²é€€æ¬¾é‡‘额,单位:元
     *
     * ç›®çš„:和 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO} ç»“合,记录已支付金额
     */
    private BigDecimal refundPrice;
    /**
     * åˆè®¡äº§å“ä»·æ ¼ï¼Œå•位:元
     */
    private BigDecimal totalProductPrice;
    /**
     * åˆè®¡ç¨Žé¢ï¼Œå•位:元
     */
    private BigDecimal totalTaxPrice;
    /**
     * ä¼˜æƒ çŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal discountPercent;
    /**
     * ä¼˜æƒ é‡‘额,单位:元
     *
     * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent
     */
    private BigDecimal discountPrice;
    /**
     * å…¶å®ƒé‡‘额,单位:元
     */
    private BigDecimal otherPrice;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnItemDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * ERP é‡‡è´­é€€è´§é¡¹ DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("erp_purchase_return_items")
@KeySequence("erp_purchase_return_items_seq") // ç”¨äºŽ Oracle、PostgreSQL、Kingbase、DB2、H2 æ•°æ®åº“的主键自增。如果是 MySQL ç­‰æ•°æ®åº“,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpPurchaseReturnItemDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é‡‡è´­é€€è´§ç¼–号
     *
     * å…³è” {@link ErpPurchaseReturnDO##getId()}
     */
    private Long returnId;
    /**
     * é‡‡è´­è®¢å•项编号
     *
     * å…³è” {@link ErpPurchaseOrderItemDO#getId()}
     * ç›®çš„:方便更新关联的采购订单项的退货数量
     */
    private Long orderItemId;
    /**
     * ä»“库编号
     *
     * å…³è” MES ä»“库编号
     */
    private Long warehouseId;
    /**
     * äº§å“ç¼–号
     *
     * å…³è” MDM ç‰©æ–™ç¼–号
     */
    private Long productId;
    /**
     * äº§å“å•位单位
     *
     * å†—ä½™ MDM ç‰©æ–™è®¡é‡å•位编号
     */
    private Long productUnitId;
    /**
     * äº§å“å•位单价,单位:元
     */
    private BigDecimal productPrice;
    /**
     * æ•°é‡
     */
    private BigDecimal count;
    /**
     * æ€»ä»·ï¼Œå•位:元
     *
     * totalPrice = productPrice * count
     */
    private BigDecimal totalPrice;
    /**
     * ç¨ŽçŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal taxPercent;
    /**
     * ç¨Žé¢ï¼Œå•位:元
     *
     * taxPrice = totalPrice * taxPercent
     */
    private BigDecimal taxPrice;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInItemMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
 * ERP é‡‡è´­å…¥åº“项 Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface ErpPurchaseInItemMapper extends BaseMapperX<ErpPurchaseInItemDO> {
    default List<ErpPurchaseInItemDO> selectListByInId(Long inId) {
        return selectList(ErpPurchaseInItemDO::getInId, inId);
    }
    default List<ErpPurchaseInItemDO> selectListByInIds(Collection<Long> inIds) {
        return selectList(ErpPurchaseInItemDO::getInId, inIds);
    }
    default int deleteByInId(Long inId) {
        return delete(ErpPurchaseInItemDO::getInId, inId);
    }
    /**
     * åŸºäºŽé‡‡è´­è®¢å•编号,查询每个采购订单项的入库数量之和
     *
     * @param inIds å…¥åº“订单项编号数组
     * @return key:采购订单项编号;value:入库数量之和
     */
    default Map<Long, BigDecimal> selectOrderItemCountSumMapByInIds(Collection<Long> inIds) {
        if (CollUtil.isEmpty(inIds)) {
            return Collections.emptyMap();
        }
        // SQL sum æŸ¥è¯¢
        List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpPurchaseInItemDO>()
                .select("order_item_id, SUM(count) AS sum_count")
                .groupBy("order_item_id")
                .in("in_id", inIds));
        // èŽ·å¾—æ•°é‡
        return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sum_count"));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Objects;
/**
 * ERP é‡‡è´­å…¥åº“ Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface ErpPurchaseInMapper extends BaseMapperX<ErpPurchaseInDO> {
    default PageResult<ErpPurchaseInDO> selectPage(ErpPurchaseInPageReqVO reqVO) {
        MPJLambdaWrapperX<ErpPurchaseInDO> query = new MPJLambdaWrapperX<ErpPurchaseInDO>()
                .likeIfPresent(ErpPurchaseInDO::getNo, reqVO.getNo())
                .eqIfPresent(ErpPurchaseInDO::getSupplierId, reqVO.getSupplierId())
                .betweenIfPresent(ErpPurchaseInDO::getInTime, reqVO.getInTime())
                .eqIfPresent(ErpPurchaseInDO::getStatus, reqVO.getStatus())
                .likeIfPresent(ErpPurchaseInDO::getRemark, reqVO.getRemark())
                .eqIfPresent(ErpPurchaseInDO::getCreator, reqVO.getCreator())
                .eqIfPresent(ErpPurchaseInDO::getAccountId, reqVO.getAccountId())
                .likeIfPresent(ErpPurchaseInDO::getOrderNo, reqVO.getOrderNo())
                .orderByDesc(ErpPurchaseInDO::getId);
        // ä»˜æ¬¾çŠ¶æ€ã€‚ä¸ºä»€ä¹ˆéœ€è¦ t. çš„原因,是因为联表查询时,需要指定表名,不然会报字段不存在的错误
        if (Objects.equals(reqVO.getPaymentStatus(), ErpPurchaseInPageReqVO.PAYMENT_STATUS_NONE)) {
            query.eq(ErpPurchaseInDO::getPaymentPrice, 0);
        } else if (Objects.equals(reqVO.getPaymentStatus(), ErpPurchaseInPageReqVO.PAYMENT_STATUS_PART)) {
            query.gt(ErpPurchaseInDO::getPaymentPrice, 0).apply("t.payment_price < t.total_price");
        } else if (Objects.equals(reqVO.getPaymentStatus(), ErpPurchaseInPageReqVO.PAYMENT_STATUS_ALL)) {
            query.apply("t.payment_price = t.total_price");
        }
        if (Boolean.TRUE.equals(reqVO.getPaymentEnable())) {
            query.eq(ErpPurchaseInDO::getStatus, ErpAuditStatus.APPROVE.getStatus())
                    .apply("t.payment_price < t.total_price");
        }
        if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) {
            query.leftJoin(ErpPurchaseInItemDO.class, ErpPurchaseInItemDO::getInId, ErpPurchaseInDO::getId)
                    .eq(reqVO.getWarehouseId() != null, ErpPurchaseInItemDO::getWarehouseId, reqVO.getWarehouseId())
                    .eq(reqVO.getProductId() != null, ErpPurchaseInItemDO::getProductId, reqVO.getProductId())
                    .groupBy(ErpPurchaseInDO::getId); // é¿å… 1 å¯¹å¤šæŸ¥è¯¢ï¼Œäº§ç”Ÿç›¸åŒçš„ 1
        }
        return selectJoinPage(reqVO, ErpPurchaseInDO.class, query);
    }
    default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseInDO updateObj) {
        return update(updateObj, new LambdaUpdateWrapper<ErpPurchaseInDO>()
                .eq(ErpPurchaseInDO::getId, id).eq(ErpPurchaseInDO::getStatus, status));
    }
    default ErpPurchaseInDO selectByNo(String no) {
        return selectOne(ErpPurchaseInDO::getNo, no);
    }
    default List<ErpPurchaseInDO> selectListByOrderId(Long orderId) {
        return selectList(ErpPurchaseInDO::getOrderId, orderId);
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnItemMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
 * ERP é‡‡è´­é€€è´§é¡¹ Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface ErpPurchaseReturnItemMapper extends BaseMapperX<ErpPurchaseReturnItemDO> {
    default List<ErpPurchaseReturnItemDO> selectListByReturnId(Long returnId) {
        return selectList(ErpPurchaseReturnItemDO::getReturnId, returnId);
    }
    default List<ErpPurchaseReturnItemDO> selectListByReturnIds(Collection<Long> returnIds) {
        return selectList(ErpPurchaseReturnItemDO::getReturnId, returnIds);
    }
    default int deleteByReturnId(Long returnId) {
        return delete(ErpPurchaseReturnItemDO::getReturnId, returnId);
    }
    /**
     * åŸºäºŽé‡‡è´­è®¢å•编号,查询每个采购订单项的退货数量之和
     *
     * @param returnIds å…¥åº“订单项编号数组
     * @return key:采购订单项编号;value:退货数量之和
     */
    default Map<Long, BigDecimal> selectOrderItemCountSumMapByReturnIds(Collection<Long> returnIds) {
        if (CollUtil.isEmpty(returnIds)) {
            return Collections.emptyMap();
        }
        // SQL sum æŸ¥è¯¢
        List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpPurchaseReturnItemDO>()
                .select("order_item_id, SUM(count) AS sum_count")
                .groupBy("order_item_id")
                .in("return_id", returnIds));
        // èŽ·å¾—æ•°é‡
        return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sum_count"));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Objects;
/**
 * ERP é‡‡è´­é€€è´§ Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface ErpPurchaseReturnMapper extends BaseMapperX<ErpPurchaseReturnDO> {
    default PageResult<ErpPurchaseReturnDO> selectPage(ErpPurchaseReturnPageReqVO reqVO) {
        MPJLambdaWrapperX<ErpPurchaseReturnDO> query = new MPJLambdaWrapperX<ErpPurchaseReturnDO>()
                .likeIfPresent(ErpPurchaseReturnDO::getNo, reqVO.getNo())
                .eqIfPresent(ErpPurchaseReturnDO::getSupplierId, reqVO.getSupplierId())
                .betweenIfPresent(ErpPurchaseReturnDO::getReturnTime, reqVO.getReturnTime())
                .eqIfPresent(ErpPurchaseReturnDO::getStatus, reqVO.getStatus())
                .likeIfPresent(ErpPurchaseReturnDO::getRemark, reqVO.getRemark())
                .eqIfPresent(ErpPurchaseReturnDO::getCreator, reqVO.getCreator())
                .eqIfPresent(ErpPurchaseReturnDO::getAccountId, reqVO.getAccountId())
                .likeIfPresent(ErpPurchaseReturnDO::getOrderNo, reqVO.getOrderNo())
                .orderByDesc(ErpPurchaseReturnDO::getId);
        // é€€æ¬¾çŠ¶æ€ã€‚ä¸ºä»€ä¹ˆéœ€è¦ t. çš„原因,是因为联表查询时,需要指定表名,不然会报字段不存在的错误
        if (Objects.equals(reqVO.getRefundStatus(), ErpPurchaseReturnPageReqVO.REFUND_STATUS_NONE)) {
            query.eq(ErpPurchaseReturnDO::getRefundPrice, 0);
        } else if (Objects.equals(reqVO.getRefundStatus(), ErpPurchaseReturnPageReqVO.REFUND_STATUS_PART)) {
            query.gt(ErpPurchaseReturnDO::getRefundPrice, 0).apply("t.refund_price < t.total_price");
        } else if (Objects.equals(reqVO.getRefundStatus(), ErpPurchaseReturnPageReqVO.REFUND_STATUS_ALL)) {
            query.apply("t.refund_price = t.total_price");
        }
        if (Boolean.TRUE.equals(reqVO.getRefundEnable())) {
            query.eq(ErpPurchaseInDO::getStatus, ErpAuditStatus.APPROVE.getStatus())
                    .apply("t.refund_price < t.total_price");
        }
        if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) {
            query.leftJoin(ErpPurchaseReturnItemDO.class, ErpPurchaseReturnItemDO::getReturnId, ErpPurchaseReturnDO::getId)
                    .eq(reqVO.getWarehouseId() != null, ErpPurchaseReturnItemDO::getWarehouseId, reqVO.getWarehouseId())
                    .eq(reqVO.getProductId() != null, ErpPurchaseReturnItemDO::getProductId, reqVO.getProductId())
                    .groupBy(ErpPurchaseReturnDO::getId); // é¿å… 1 å¯¹å¤šæŸ¥è¯¢ï¼Œäº§ç”Ÿç›¸åŒçš„ 1
        }
        return selectJoinPage(reqVO, ErpPurchaseReturnDO.class, query);
    }
    default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseReturnDO updateObj) {
        return update(updateObj, new LambdaUpdateWrapper<ErpPurchaseReturnDO>()
                .eq(ErpPurchaseReturnDO::getId, id).eq(ErpPurchaseReturnDO::getStatus, status));
    }
    default ErpPurchaseReturnDO selectByNo(String no) {
        return selectOne(ErpPurchaseReturnDO::getNo, no);
    }
    default List<ErpPurchaseReturnDO> selectListByOrderId(Long orderId) {
        return selectList(ErpPurchaseReturnDO::getOrderId, orderId);
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/statistics/ErpPurchaseStatisticsMapper.java
@@ -1,5 +1,11 @@
package cn.iocoder.yudao.module.erp.dal.mysql.statistics;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceAnalysisRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseStatisticsReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSummaryRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSupplierStatisticsRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseUncompletedOrderRespVO;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -17,4 +23,44 @@
    BigDecimal getPurchasePrice(@Param("beginTime") LocalDateTime beginTime,
                                @Param("endTime") LocalDateTime endTime);
    /**
     * é‡‡è´­æ±‡æ€»ç»Ÿè®¡ï¼ˆè®¢å• / å…¥åº“ / é€€è´§çš„æ€»æ•°ä¸Žæ€»é‡‘额)
     *
     * @return æ±‡æ€»ç»Ÿè®¡
     */
    ErpPurchaseSummaryRespVO selectPurchaseSummary();
    /**
     * ä¾›åº”商供货统计
     *
     * @param page   åˆ†é¡µ
     * @param reqVO  æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µç»“æžœ
     */
    Page<ErpPurchaseSupplierStatisticsRespVO> selectSupplierStatistics(
            Page<ErpPurchaseSupplierStatisticsRespVO> page,
            @Param("reqVO") ErpPurchaseStatisticsReqVO reqVO);
    /**
     * é‡‡è´­ä»·æ ¼åˆ†æžï¼ˆæŒ‰äº§å“èšåˆï¼‰
     *
     * @param page   åˆ†é¡µ
     * @param reqVO  æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µç»“æžœ
     */
    Page<ErpPurchasePriceAnalysisRespVO> selectPriceAnalysis(
            Page<ErpPurchasePriceAnalysisRespVO> page,
            @Param("reqVO") ErpPurchaseStatisticsReqVO reqVO);
    /**
     * æœªå®Œæˆé‡‡è´­è®¢å•跟踪
     *
     * @param page   åˆ†é¡µ
     * @param reqVO  æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µç»“æžœ
     */
    Page<ErpPurchaseUncompletedOrderRespVO> selectUncompletedOrders(
            Page<ErpPurchaseUncompletedOrderRespVO> page,
            @Param("reqVO") ErpPurchaseStatisticsReqVO reqVO);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
@@ -45,6 +45,16 @@
    public static final String PURCHASE_INVOICE_NO_PREFIX = "FPD";
    /**
     * é‡‡è´­å…¥åº“ {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO}
     */
    public static final String PURCHASE_IN_NO_PREFIX = "CGRK";
    /**
     * é‡‡è´­é€€è´§ {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO}
     */
    public static final String PURCHASE_RETURN_NO_PREFIX = "CGTH";
    /**
     * ä»˜æ¬¾å• {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO}
     */
    public static final String FINANCE_PAYMENT_NO_PREFIX = "FKD";
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO;
import jakarta.validation.Valid;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
/**
 * ERP é‡‡è´­å…¥åº“ Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface ErpPurchaseInService {
    /**
     * åˆ›å»ºé‡‡è´­å…¥åº“
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return ç¼–号
     */
    Long createPurchaseIn(@Valid ErpPurchaseInSaveReqVO createReqVO);
    /**
     * æ›´æ–°é‡‡è´­å…¥åº“
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updatePurchaseIn(@Valid ErpPurchaseInSaveReqVO updateReqVO);
    /**
     * æ›´æ–°é‡‡è´­å…¥åº“的状态
     *
     * @param id ç¼–号
     * @param status çŠ¶æ€
     */
    void updatePurchaseInStatus(Long id, Integer status);
    /**
     * æ›´æ–°é‡‡è´­å…¥åº“的付款金额
     *
     * @param id ç¼–号
     * @param paymentPrice ä»˜æ¬¾é‡‘额
     */
    void updatePurchaseInPaymentPrice(Long id, BigDecimal paymentPrice);
    /**
     * åˆ é™¤é‡‡è´­å…¥åº“
     *
     * @param ids ç¼–号数组
     */
    void deletePurchaseIn(List<Long> ids);
    /**
     * èŽ·å¾—é‡‡è´­å…¥åº“
     *
     * @param id ç¼–号
     * @return é‡‡è´­å…¥åº“
     */
    ErpPurchaseInDO getPurchaseIn(Long id);
    /**
     * æ ¡éªŒé‡‡è´­å…¥åº“,已经审核通过
     *
     * @param id ç¼–号
     * @return é‡‡è´­å…¥åº“
     */
    ErpPurchaseInDO validatePurchaseIn(Long id);
    /**
     * èŽ·å¾—é‡‡è´­å…¥åº“åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return é‡‡è´­å…¥åº“分页
     */
    PageResult<ErpPurchaseInDO> getPurchaseInPage(ErpPurchaseInPageReqVO pageReqVO);
    // ==================== é‡‡è´­å…¥åº“项 ====================
    /**
     * èŽ·å¾—é‡‡è´­å…¥åº“é¡¹åˆ—è¡¨
     *
     * @param inId é‡‡è´­å…¥åº“编号
     * @return é‡‡è´­å…¥åº“项列表
     */
    List<ErpPurchaseInItemDO> getPurchaseInItemListByInId(Long inId);
    /**
     * èŽ·å¾—é‡‡è´­å…¥åº“é¡¹ List
     *
     * @param inIds é‡‡è´­å…¥åº“编号数组
     * @return é‡‡è´­å…¥åº“项 List
     */
    List<ErpPurchaseInItemDO> getPurchaseInItemListByInIds(Collection<Long> inIds);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,352 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.api.wms.dto.WmsPurchaseInboundReqDTO;
import cn.iocoder.yudao.module.mes.api.wms.stock.WmsStockApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO èŠ‹è‰¿ï¼šè®°å½•æ“ä½œæ—¥å¿—
/**
 * ERP é‡‡è´­å…¥åº“ Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
@Slf4j
public class ErpPurchaseInServiceImpl implements ErpPurchaseInService {
    @Resource
    private ErpPurchaseInMapper purchaseInMapper;
    @Resource
    private ErpPurchaseInItemMapper purchaseInItemMapper;
    @Resource
    private ErpNoRedisDAO noRedisDAO;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    @Lazy // å»¶è¿ŸåŠ è½½ï¼Œé¿å…å¾ªçŽ¯ä¾èµ–
    private ErpPurchaseOrderService purchaseOrderService;
    @Resource
    private ErpAccountService accountService;
    @Resource
    private WmsStockApi wmsStockApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseIn(ErpPurchaseInSaveReqVO createReqVO) {
        // 1.1 æ ¡éªŒé‡‡è´­è®¢å•已审核
        ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(createReqVO.getOrderId());
        // 1.2 æ ¡éªŒå…¥åº“项的有效性
        List<ErpPurchaseInItemDO> purchaseInItems = validatePurchaseInItems(createReqVO.getItems());
        // 1.3 æ ¡éªŒç»“算账户
        accountService.validateAccount(createReqVO.getAccountId());
        // 1.4 ç”Ÿæˆå…¥åº“单号,并校验唯一性
        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_IN_NO_PREFIX);
        if (purchaseInMapper.selectByNo(no) != null) {
            throw exception(PURCHASE_IN_NO_EXISTS);
        }
        // 2.1 æ’入入库
        ErpPurchaseInDO purchaseIn = BeanUtils.toBean(createReqVO, ErpPurchaseInDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
                .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId());
        calculateTotalPrice(purchaseIn, purchaseInItems);
        purchaseInMapper.insert(purchaseIn);
        // 2.2 æ’入入库项
        purchaseInItems.forEach(o -> o.setInId(purchaseIn.getId()));
        purchaseInItemMapper.insertBatch(purchaseInItems);
        // 3. æ›´æ–°é‡‡è´­è®¢å•的入库数量
        updatePurchaseOrderInCount(createReqVO.getOrderId());
        return purchaseIn.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseIn(ErpPurchaseInSaveReqVO updateReqVO) {
        // 1.1 æ ¡éªŒå­˜åœ¨
        ErpPurchaseInDO purchaseIn = validatePurchaseInExists(updateReqVO.getId());
        if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseIn.getStatus())) {
            throw exception(PURCHASE_IN_UPDATE_FAIL_APPROVE, purchaseIn.getNo());
        }
        // 1.2 æ ¡éªŒé‡‡è´­è®¢å•已审核
        ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(updateReqVO.getOrderId());
        // 1.3 æ ¡éªŒç»“算账户
        accountService.validateAccount(updateReqVO.getAccountId());
        // 1.4 æ ¡éªŒè®¢å•项的有效性
        List<ErpPurchaseInItemDO> purchaseInItems = validatePurchaseInItems(updateReqVO.getItems());
        // 2.1 æ›´æ–°å…¥åº“
        ErpPurchaseInDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseInDO.class)
                .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId());
        calculateTotalPrice(updateObj, purchaseInItems);
        purchaseInMapper.updateById(updateObj);
        // 2.2 æ›´æ–°å…¥åº“项
        updatePurchaseInItemList(updateReqVO.getId(), purchaseInItems);
        // 3.1 æ›´æ–°é‡‡è´­è®¢å•的入库数量
        updatePurchaseOrderInCount(updateObj.getOrderId());
        // 3.2 æ³¨æ„ï¼šå¦‚果采购订单编号变更了,需要更新“老”采购订单的入库数量
        if (ObjectUtil.notEqual(purchaseIn.getOrderId(), updateObj.getOrderId())) {
            updatePurchaseOrderInCount(purchaseIn.getOrderId());
        }
    }
    private void calculateTotalPrice(ErpPurchaseInDO purchaseIn, List<ErpPurchaseInItemDO> purchaseInItems) {
        purchaseIn.setTotalCount(getSumValue(purchaseInItems, ErpPurchaseInItemDO::getCount, BigDecimal::add));
        purchaseIn.setTotalProductPrice(getSumValue(purchaseInItems, ErpPurchaseInItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseIn.setTotalTaxPrice(getSumValue(purchaseInItems, ErpPurchaseInItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseIn.setTotalPrice(purchaseIn.getTotalProductPrice().add(purchaseIn.getTotalTaxPrice()));
        // è®¡ç®—优惠价格
        if (purchaseIn.getDiscountPercent() == null) {
            purchaseIn.setDiscountPercent(BigDecimal.ZERO);
        }
        purchaseIn.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseIn.getTotalPrice(), purchaseIn.getDiscountPercent()));
        purchaseIn.setTotalPrice(purchaseIn.getTotalPrice().subtract(purchaseIn.getDiscountPrice()).add(purchaseIn.getOtherPrice()));
    }
    private void updatePurchaseOrderInCount(Long orderId) {
        // 1.1 æŸ¥è¯¢é‡‡è´­è®¢å•对应的采购入库单列表
        List<ErpPurchaseInDO> purchaseIns = purchaseInMapper.selectListByOrderId(orderId);
        // 1.2 æŸ¥è¯¢å¯¹åº”的采购订单项的入库数量
        Map<Long, BigDecimal> returnCountMap = purchaseInItemMapper.selectOrderItemCountSumMapByInIds(
                convertList(purchaseIns, ErpPurchaseInDO::getId));
        // 2. æ›´æ–°é‡‡è´­è®¢å•的入库数量
        purchaseOrderService.updatePurchaseOrderInCount(orderId, returnCountMap);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseInStatus(Long id, Integer status) {
        boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
        // 1.1 æ ¡éªŒå­˜åœ¨
        ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
        // 1.2 æ ¡éªŒçŠ¶æ€
        if (purchaseIn.getStatus().equals(status)) {
            throw exception(approve ? PURCHASE_IN_APPROVE_FAIL : PURCHASE_IN_PROCESS_FAIL);
        }
        // 1.3 æ ¡éªŒå·²ä»˜æ¬¾
        if (!approve && purchaseIn.getPaymentPrice().compareTo(BigDecimal.ZERO) > 0) {
            throw exception(PURCHASE_IN_PROCESS_FAIL_EXISTS_PAYMENT);
        }
        // 2. æ›´æ–°çŠ¶æ€
        int updateCount = purchaseInMapper.updateByIdAndStatus(id, purchaseIn.getStatus(),
                new ErpPurchaseInDO().setStatus(status));
        if (updateCount == 0) {
            throw exception(approve ? PURCHASE_IN_APPROVE_FAIL : PURCHASE_IN_PROCESS_FAIL);
        }
        // 3. åŒæ­¥ WMS åº“存(MES åº“存)
        if (approve) {
            List<ErpPurchaseInItemDO> purchaseInItems = purchaseInItemMapper.selectListByInId(id);
            syncToWmsPurchaseInbound(purchaseIn, purchaseInItems);
        }
    }
    /**
     * åŒæ­¥é‡‡è´­å…¥åº“到 WMS åº“å­˜
     */
    private void syncToWmsPurchaseInbound(ErpPurchaseInDO purchaseIn, List<ErpPurchaseInItemDO> purchaseInItems) {
        if (CollUtil.isEmpty(purchaseInItems)) {
            return;
        }
        log.info("[syncToWmsPurchaseInbound] å¼€å§‹åŒæ­¥é‡‡è´­å…¥åº“到WMS,purchaseInId={}", purchaseIn.getId());
        // æž„建入库请求
        WmsPurchaseInboundReqDTO reqDTO = new WmsPurchaseInboundReqDTO()
                .setBizId(purchaseIn.getId())
                .setBizCode(purchaseIn.getNo())
                .setReceiptTime(LocalDateTime.now());
        // è½¬æ¢æ˜Žç»†é¡¹ï¼šERP äº§å“ç¼–号即 MDM ç‰©æ–™ç¼–号,仓库编号即 MES ä»“库编号
        List<WmsPurchaseInboundReqDTO.Item> items = convertList(purchaseInItems, item -> new WmsPurchaseInboundReqDTO.Item()
                .setItemId(item.getProductId())
                .setWarehouseId(item.getWarehouseId())
                .setQuantity(item.getCount())
                .setBizLineId(item.getId()));
        // è¿‡æ»¤æŽ‰æ˜ å°„失败的项
        items = items.stream().filter(java.util.Objects::nonNull).toList();
        if (CollUtil.isEmpty(items)) {
            log.warn("[syncToWmsPurchaseInbound] æ— æœ‰æ•ˆçš„入库明细,跳过WMS同步");
            return;
        }
        reqDTO.setItems(items);
        try {
            Long transactionId = wmsStockApi.purchaseInbound(reqDTO);
            log.info("[syncToWmsPurchaseInbound] åŒæ­¥æˆåŠŸï¼ŒtransactionId={}", transactionId);
        } catch (Exception e) {
            log.error("[syncToWmsPurchaseInbound] åŒæ­¥å¤±è´¥", e);
            // æ³¨æ„ï¼šè¿™é‡Œå¯ä»¥é€‰æ‹©æŠ›å‡ºå¼‚常回滚,或仅记录日志
            // throw exception(PURCHASE_IN_SYNC_WMS_FAIL);
        }
    }
    @Override
    public void updatePurchaseInPaymentPrice(Long id, BigDecimal paymentPrice) {
        ErpPurchaseInDO purchaseIn = purchaseInMapper.selectById(id);
        if (purchaseIn.getPaymentPrice().equals(paymentPrice)) {
            return;
        }
        if (paymentPrice.compareTo(purchaseIn.getTotalPrice()) > 0) {
            throw exception(PURCHASE_IN_FAIL_PAYMENT_PRICE_EXCEED, paymentPrice, purchaseIn.getTotalPrice());
        }
        purchaseInMapper.updateById(new ErpPurchaseInDO().setId(id).setPaymentPrice(paymentPrice));
    }
    private List<ErpPurchaseInItemDO> validatePurchaseInItems(List<ErpPurchaseInSaveReqVO.Item> list) {
        // 1. æ ¡éªŒç‰©æ–™å­˜åœ¨ï¼ˆç‰©æ–™ä¸»æ•°æ®å·²è¿ç§»åˆ° MDM)
        Set<Long> productIds = convertSet(list, ErpPurchaseInSaveReqVO.Item::getProductId);
        Map<Long, MdmItemRespDTO> itemMap = productIds.isEmpty() ? Collections.emptyMap() : mdmItemApi.getItemMap(productIds);
        if (itemMap.size() != productIds.size()) {
            throw exception(PRODUCT_NOT_EXISTS);
        }
        // 2. è½¬åŒ–为 ErpPurchaseInItemDO åˆ—表
        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseInItemDO.class, item -> {
            item.setProductUnitId(itemMap.get(item.getProductId()).getUnitMeasureId());
            item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
            if (item.getTotalPrice() == null) {
                return;
            }
            if (item.getTaxPercent() != null) {
                item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
            }
        }));
    }
    private void updatePurchaseInItemList(Long id, List<ErpPurchaseInItemDO> newList) {
        // ç¬¬ä¸€æ­¥ï¼Œå¯¹æ¯”新老数据,获得添加、修改、删除的列表
        List<ErpPurchaseInItemDO> oldList = purchaseInItemMapper.selectListByInId(id);
        List<List<ErpPurchaseInItemDO>> diffList = diffList(oldList, newList, // id ä¸åŒï¼Œå°±è®¤ä¸ºæ˜¯ä¸åŒçš„记录
                (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
        // ç¬¬äºŒæ­¥ï¼Œæ‰¹é‡æ·»åŠ ã€ä¿®æ”¹ã€åˆ é™¤
        if (CollUtil.isNotEmpty(diffList.get(0))) {
            diffList.get(0).forEach(o -> o.setInId(id));
            purchaseInItemMapper.insertBatch(diffList.get(0));
        }
        if (CollUtil.isNotEmpty(diffList.get(1))) {
            purchaseInItemMapper.updateBatch(diffList.get(1));
        }
        if (CollUtil.isNotEmpty(diffList.get(2))) {
            purchaseInItemMapper.deleteByIds(convertList(diffList.get(2), ErpPurchaseInItemDO::getId));
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePurchaseIn(List<Long> ids) {
        // 1. æ ¡éªŒä¸å¤„于已审批
        List<ErpPurchaseInDO> purchaseIns = purchaseInMapper.selectByIds(ids);
        if (CollUtil.isEmpty(purchaseIns)) {
            return;
        }
        purchaseIns.forEach(purchaseIn -> {
            if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseIn.getStatus())) {
                throw exception(PURCHASE_IN_DELETE_FAIL_APPROVE, purchaseIn.getNo());
            }
        });
        // 2. éåŽ†åˆ é™¤ï¼Œå¹¶è®°å½•æ“ä½œæ—¥å¿—
        purchaseIns.forEach(purchaseIn -> {
            // 2.1 åˆ é™¤è®¢å•
            purchaseInMapper.deleteById(purchaseIn.getId());
            // 2.2 åˆ é™¤è®¢å•项
            purchaseInItemMapper.deleteByInId(purchaseIn.getId());
            // 2.3 æ›´æ–°é‡‡è´­è®¢å•的入库数量
            updatePurchaseOrderInCount(purchaseIn.getOrderId());
        });
    }
    private ErpPurchaseInDO validatePurchaseInExists(Long id) {
        ErpPurchaseInDO purchaseIn = purchaseInMapper.selectById(id);
        if (purchaseIn == null) {
            throw exception(PURCHASE_IN_NOT_EXISTS);
        }
        return purchaseIn;
    }
    @Override
    public ErpPurchaseInDO getPurchaseIn(Long id) {
        return purchaseInMapper.selectById(id);
    }
    @Override
    public ErpPurchaseInDO validatePurchaseIn(Long id) {
        ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id);
        if (ObjectUtil.notEqual(purchaseIn.getStatus(), ErpAuditStatus.APPROVE.getStatus())) {
            throw exception(PURCHASE_IN_NOT_APPROVE);
        }
        return purchaseIn;
    }
    @Override
    public PageResult<ErpPurchaseInDO> getPurchaseInPage(ErpPurchaseInPageReqVO pageReqVO) {
        return purchaseInMapper.selectPage(pageReqVO);
    }
    // ==================== é‡‡è´­å…¥åº“项 ====================
    @Override
    public List<ErpPurchaseInItemDO> getPurchaseInItemListByInId(Long inId) {
        return purchaseInItemMapper.selectListByInId(inId);
    }
    @Override
    public List<ErpPurchaseInItemDO> getPurchaseInItemListByInIds(Collection<Long> inIds) {
        if (CollUtil.isEmpty(inIds)) {
            return Collections.emptyList();
        }
        return purchaseInItemMapper.selectListByInIds(inIds);
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -550,7 +550,8 @@
        // 1. æ›´æ–°æ¯ä¸ªé‡‡è´­è®¢å•项
        orderItems.forEach(item -> {
            BigDecimal inCount = inCountMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            if (item.getInCount().equals(inCount)) {
            // åŽ†å²/导入数据的入库数量可能为空,按 0 å¤„理,避免 NPE
            if (ObjectUtil.defaultIfNull(item.getInCount(), BigDecimal.ZERO).compareTo(inCount) == 0) {
                return;
            }
            if (inCount.compareTo(item.getCount()) > 0) {
@@ -570,12 +571,14 @@
        // 1. æ›´æ–°æ¯ä¸ªé‡‡è´­è®¢å•项
        orderItems.forEach(item -> {
            BigDecimal returnCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            if (item.getReturnCount().equals(returnCount)) {
            // åŽ†å²/导入数据的退货数量、入库数量可能为空,按 0 å¤„理,避免 NPE
            if (ObjectUtil.defaultIfNull(item.getReturnCount(), BigDecimal.ZERO).compareTo(returnCount) == 0) {
                return;
            }
            if (returnCount.compareTo(item.getInCount()) > 0) {
            BigDecimal inCount = ObjectUtil.defaultIfNull(item.getInCount(), BigDecimal.ZERO);
            if (returnCount.compareTo(inCount) > 0) {
                throw exception(PURCHASE_ORDER_ITEM_RETURN_FAIL_IN_EXCEED,
                        "产品ID:" + item.getProductId(), item.getInCount());
                        "产品ID:" + item.getProductId(), inCount);
            }
            purchaseOrderItemMapper.updateById(new ErpPurchaseOrderItemDO().setId(item.getId()).setReturnCount(returnCount));
        });
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO;
import jakarta.validation.Valid;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
/**
 * ERP é‡‡è´­é€€è´§ Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface ErpPurchaseReturnService {
    /**
     * åˆ›å»ºé‡‡è´­é€€è´§
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return ç¼–号
     */
    Long createPurchaseReturn(@Valid ErpPurchaseReturnSaveReqVO createReqVO);
    /**
     * æ›´æ–°é‡‡è´­é€€è´§
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updatePurchaseReturn(@Valid ErpPurchaseReturnSaveReqVO updateReqVO);
    /**
     * æ›´æ–°é‡‡è´­é€€è´§çš„状态
     *
     * @param id ç¼–号
     * @param status çŠ¶æ€
     */
    void updatePurchaseReturnStatus(Long id, Integer status);
    /**
     * æ›´æ–°é‡‡è´­é€€è´§çš„退款金额
     *
     * @param id ç¼–号
     * @param refundPrice é€€æ¬¾é‡‘额
     */
    void updatePurchaseReturnRefundPrice(Long id, BigDecimal refundPrice);
    /**
     * åˆ é™¤é‡‡è´­é€€è´§
     *
     * @param ids ç¼–号数组
     */
    void deletePurchaseReturn(List<Long> ids);
    /**
     * èŽ·å¾—é‡‡è´­é€€è´§
     *
     * @param id ç¼–号
     * @return é‡‡è´­é€€è´§
     */
    ErpPurchaseReturnDO getPurchaseReturn(Long id);
    /**
     * æ ¡éªŒé‡‡è´­é€€è´§ï¼Œå·²ç»å®¡æ ¸é€šè¿‡
     *
     * @param id ç¼–号
     * @return é‡‡è´­é€€è´§
     */
    ErpPurchaseReturnDO validatePurchaseReturn(Long id);
    /**
     * èŽ·å¾—é‡‡è´­é€€è´§åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return é‡‡è´­é€€è´§åˆ†é¡µ
     */
    PageResult<ErpPurchaseReturnDO> getPurchaseReturnPage(ErpPurchaseReturnPageReqVO pageReqVO);
    // ==================== é‡‡è´­é€€è´§é¡¹ ====================
    /**
     * èŽ·å¾—é‡‡è´­é€€è´§é¡¹åˆ—è¡¨
     *
     * @param returnId é‡‡è´­é€€è´§ç¼–号
     * @return é‡‡è´­é€€è´§é¡¹åˆ—表
     */
    List<ErpPurchaseReturnItemDO> getPurchaseReturnItemListByReturnId(Long returnId);
    /**
     * èŽ·å¾—é‡‡è´­é€€è´§é¡¹ List
     *
     * @param returnIds é‡‡è´­é€€è´§ç¼–号数组
     * @return é‡‡è´­é€€è´§é¡¹ List
     */
    List<ErpPurchaseReturnItemDO> getPurchaseReturnItemListByReturnIds(Collection<Long> returnIds);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,327 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseReturnItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseReturnMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.api.wms.dto.WmsPurchaseInboundReqDTO;
import cn.iocoder.yudao.module.mes.api.wms.stock.WmsStockApi;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO èŠ‹è‰¿ï¼šè®°å½•æ“ä½œæ—¥å¿—
/**
 * ERP é‡‡è´­é€€è´§ Service å®žçŽ°ç±»
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
@Slf4j
public class ErpPurchaseReturnServiceImpl implements ErpPurchaseReturnService {
    @Resource
    private ErpPurchaseReturnMapper purchaseReturnMapper;
    @Resource
    private ErpPurchaseReturnItemMapper purchaseReturnItemMapper;
    @Resource
    private ErpNoRedisDAO noRedisDAO;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    @Lazy // å»¶è¿ŸåŠ è½½ï¼Œé¿å…å¾ªçŽ¯ä¾èµ–
    private ErpPurchaseOrderService purchaseOrderService;
    @Resource
    private ErpAccountService accountService;
    @Resource
    private WmsStockApi wmsStockApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseReturn(ErpPurchaseReturnSaveReqVO createReqVO) {
        // 1.1 æ ¡éªŒé‡‡è´­è®¢å•已审核
        ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(createReqVO.getOrderId());
        // 1.2 æ ¡éªŒé€€è´§é¡¹çš„æœ‰æ•ˆæ€§
        List<ErpPurchaseReturnItemDO> purchaseReturnItems = validatePurchaseReturnItems(createReqVO.getItems());
        // 1.3 æ ¡éªŒç»“算账户
        accountService.validateAccount(createReqVO.getAccountId());
        // 1.4 ç”Ÿæˆé€€è´§å•号,并校验唯一性
        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_RETURN_NO_PREFIX);
        if (purchaseReturnMapper.selectByNo(no) != null) {
            throw exception(PURCHASE_RETURN_NO_EXISTS);
        }
        // 2.1 æ’入退货
        ErpPurchaseReturnDO purchaseReturn = BeanUtils.toBean(createReqVO, ErpPurchaseReturnDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()))
                .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId());
        calculateTotalPrice(purchaseReturn, purchaseReturnItems);
        purchaseReturnMapper.insert(purchaseReturn);
        // 2.2 æ’入退货项
        purchaseReturnItems.forEach(o -> o.setReturnId(purchaseReturn.getId()));
        purchaseReturnItemMapper.insertBatch(purchaseReturnItems);
        // 3. æ›´æ–°é‡‡è´­è®¢å•的退货数量
        updatePurchaseOrderReturnCount(createReqVO.getOrderId());
        return purchaseReturn.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseReturn(ErpPurchaseReturnSaveReqVO updateReqVO) {
        // 1.1 æ ¡éªŒå­˜åœ¨
        ErpPurchaseReturnDO purchaseReturn = validatePurchaseReturnExists(updateReqVO.getId());
        if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseReturn.getStatus())) {
            throw exception(PURCHASE_RETURN_UPDATE_FAIL_APPROVE, purchaseReturn.getNo());
        }
        // 1.2 æ ¡éªŒé‡‡è´­è®¢å•已审核
        ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(updateReqVO.getOrderId());
        // 1.3 æ ¡éªŒç»“算账户
        accountService.validateAccount(updateReqVO.getAccountId());
        // 1.4 æ ¡éªŒè®¢å•项的有效性
        List<ErpPurchaseReturnItemDO> purchaseReturnItems = validatePurchaseReturnItems(updateReqVO.getItems());
        // 2.1 æ›´æ–°é€€è´§
        ErpPurchaseReturnDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseReturnDO.class)
                .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId());
        calculateTotalPrice(updateObj, purchaseReturnItems);
        purchaseReturnMapper.updateById(updateObj);
        // 2.2 æ›´æ–°é€€è´§é¡¹
        updatePurchaseReturnItemList(updateReqVO.getId(), purchaseReturnItems);
        // 3.1 æ›´æ–°é‡‡è´­è®¢å•的出库数量
        updatePurchaseOrderReturnCount(updateObj.getOrderId());
        // 3.2 æ³¨æ„ï¼šå¦‚果采购订单编号变更了,需要更新“老”采购订单的出库数量
        if (ObjectUtil.notEqual(purchaseReturn.getOrderId(), updateObj.getOrderId())) {
            updatePurchaseOrderReturnCount(purchaseReturn.getOrderId());
        }
    }
    private void calculateTotalPrice(ErpPurchaseReturnDO purchaseReturn, List<ErpPurchaseReturnItemDO> purchaseReturnItems) {
        purchaseReturn.setTotalCount(getSumValue(purchaseReturnItems, ErpPurchaseReturnItemDO::getCount, BigDecimal::add));
        purchaseReturn.setTotalProductPrice(getSumValue(purchaseReturnItems, ErpPurchaseReturnItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseReturn.setTotalTaxPrice(getSumValue(purchaseReturnItems, ErpPurchaseReturnItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseReturn.setTotalPrice(purchaseReturn.getTotalProductPrice().add(purchaseReturn.getTotalTaxPrice()));
        // è®¡ç®—优惠价格
        if (purchaseReturn.getDiscountPercent() == null) {
            purchaseReturn.setDiscountPercent(BigDecimal.ZERO);
        }
        purchaseReturn.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseReturn.getTotalPrice(), purchaseReturn.getDiscountPercent()));
        purchaseReturn.setTotalPrice(purchaseReturn.getTotalPrice().subtract(purchaseReturn.getDiscountPrice()).add(purchaseReturn.getOtherPrice()));
    }
    private void updatePurchaseOrderReturnCount(Long orderId) {
        // 1.1 æŸ¥è¯¢é‡‡è´­è®¢å•对应的采购出库单列表
        List<ErpPurchaseReturnDO> purchaseReturns = purchaseReturnMapper.selectListByOrderId(orderId);
        // 1.2 æŸ¥è¯¢å¯¹åº”的采购订单项的退货数量
        Map<Long, BigDecimal> returnCountMap = purchaseReturnItemMapper.selectOrderItemCountSumMapByReturnIds(
                convertList(purchaseReturns, ErpPurchaseReturnDO::getId));
        // 2. æ›´æ–°é‡‡è´­è®¢å•的出库数量
        purchaseOrderService.updatePurchaseOrderReturnCount(orderId, returnCountMap);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseReturnStatus(Long id, Integer status) {
        boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
        // 1.1 æ ¡éªŒå­˜åœ¨
        ErpPurchaseReturnDO purchaseReturn = validatePurchaseReturnExists(id);
        // 1.2 æ ¡éªŒçŠ¶æ€
        if (purchaseReturn.getStatus().equals(status)) {
            throw exception(approve ? PURCHASE_RETURN_APPROVE_FAIL : PURCHASE_RETURN_PROCESS_FAIL);
        }
        // 1.3 æ ¡éªŒå·²é€€æ¬¾
        if (!approve && purchaseReturn.getRefundPrice().compareTo(BigDecimal.ZERO) > 0) {
            throw exception(PURCHASE_RETURN_PROCESS_FAIL_EXISTS_REFUND);
        }
        // 2. æ›´æ–°çŠ¶æ€
        int updateCount = purchaseReturnMapper.updateByIdAndStatus(id, purchaseReturn.getStatus(),
                new ErpPurchaseReturnDO().setStatus(status));
        if (updateCount == 0) {
            throw exception(approve ? PURCHASE_RETURN_APPROVE_FAIL : PURCHASE_RETURN_PROCESS_FAIL);
        }
        // 3. å®¡æ ¸é€šè¿‡æ—¶ï¼ŒåŒæ­¥ MES ä»“储存货(采购退货出库)
        if (approve) {
            syncToWmsPurchaseReturnOutbound(purchaseReturn);
        }
    }
    /**
     * åŒæ­¥é‡‡è´­é€€è´§åˆ° WMS(MES ä»“储)库存
     */
    private void syncToWmsPurchaseReturnOutbound(ErpPurchaseReturnDO purchaseReturn) {
        List<ErpPurchaseReturnItemDO> purchaseReturnItems = purchaseReturnItemMapper.selectListByReturnId(purchaseReturn.getId());
        if (CollUtil.isEmpty(purchaseReturnItems)) {
            return;
        }
        WmsPurchaseInboundReqDTO reqDTO = new WmsPurchaseInboundReqDTO()
                .setBizId(purchaseReturn.getId())
                .setBizCode(purchaseReturn.getNo())
                .setVendorId(purchaseReturn.getSupplierId())
                .setReceiptTime(purchaseReturn.getReturnTime())
                .setItems(convertList(purchaseReturnItems, item -> new WmsPurchaseInboundReqDTO.Item()
                        .setItemId(item.getProductId())
                        .setWarehouseId(item.getWarehouseId())
                        .setQuantity(item.getCount())
                        .setBizLineId(item.getId())));
        try {
            wmsStockApi.purchaseReturnOutbound(reqDTO);
        } catch (Exception e) {
            log.error("[syncToWmsPurchaseReturnOutbound] åŒæ­¥å¤±è´¥ï¼ŒpurchaseReturnId={}", purchaseReturn.getId(), e);
        }
    }
    @Override
    public void updatePurchaseReturnRefundPrice(Long id, BigDecimal refundPrice) {
        ErpPurchaseReturnDO purchaseReturn = purchaseReturnMapper.selectById(id);
        if (purchaseReturn.getRefundPrice().equals(refundPrice)) {
            return;
        }
        if (refundPrice.compareTo(purchaseReturn.getTotalPrice()) > 0) {
            throw exception(PURCHASE_RETURN_FAIL_REFUND_PRICE_EXCEED, refundPrice, purchaseReturn.getTotalPrice());
        }
        purchaseReturnMapper.updateById(new ErpPurchaseReturnDO().setId(id).setRefundPrice(refundPrice));
    }
    private List<ErpPurchaseReturnItemDO> validatePurchaseReturnItems(List<ErpPurchaseReturnSaveReqVO.Item> list) {
        // 1. æ ¡éªŒç‰©æ–™å­˜åœ¨ï¼ˆç‰©æ–™ä¸»æ•°æ®å·²è¿ç§»åˆ° MDM)
        Set<Long> productIds = convertSet(list, ErpPurchaseReturnSaveReqVO.Item::getProductId);
        Map<Long, MdmItemRespDTO> itemMap = productIds.isEmpty() ? Collections.emptyMap() : mdmItemApi.getItemMap(productIds);
        if (itemMap.size() != productIds.size()) {
            throw exception(PRODUCT_NOT_EXISTS);
        }
        // 2. è½¬åŒ–为 ErpPurchaseReturnItemDO åˆ—表
        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseReturnItemDO.class, item -> {
            item.setProductUnitId(itemMap.get(item.getProductId()).getUnitMeasureId());
            item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
            if (item.getTotalPrice() == null) {
                return;
            }
            if (item.getTaxPercent() != null) {
                item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
            }
        }));
    }
    private void updatePurchaseReturnItemList(Long id, List<ErpPurchaseReturnItemDO> newList) {
        // ç¬¬ä¸€æ­¥ï¼Œå¯¹æ¯”新老数据,获得添加、修改、删除的列表
        List<ErpPurchaseReturnItemDO> oldList = purchaseReturnItemMapper.selectListByReturnId(id);
        List<List<ErpPurchaseReturnItemDO>> diffList = diffList(oldList, newList, // id ä¸åŒï¼Œå°±è®¤ä¸ºæ˜¯ä¸åŒçš„记录
                (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
        // ç¬¬äºŒæ­¥ï¼Œæ‰¹é‡æ·»åŠ ã€ä¿®æ”¹ã€åˆ é™¤
        if (CollUtil.isNotEmpty(diffList.get(0))) {
            diffList.get(0).forEach(o -> o.setReturnId(id));
            purchaseReturnItemMapper.insertBatch(diffList.get(0));
        }
        if (CollUtil.isNotEmpty(diffList.get(1))) {
            purchaseReturnItemMapper.updateBatch(diffList.get(1));
        }
        if (CollUtil.isNotEmpty(diffList.get(2))) {
            purchaseReturnItemMapper.deleteByIds(convertList(diffList.get(2), ErpPurchaseReturnItemDO::getId));
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePurchaseReturn(List<Long> ids) {
        // 1. æ ¡éªŒä¸å¤„于已审批
        List<ErpPurchaseReturnDO> purchaseReturns = purchaseReturnMapper.selectByIds(ids);
        if (CollUtil.isEmpty(purchaseReturns)) {
            return;
        }
        purchaseReturns.forEach(purchaseReturn -> {
            if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseReturn.getStatus())) {
                throw exception(PURCHASE_RETURN_DELETE_FAIL_APPROVE, purchaseReturn.getNo());
            }
        });
        // 2. éåŽ†åˆ é™¤ï¼Œå¹¶è®°å½•æ“ä½œæ—¥å¿—
        purchaseReturns.forEach(purchaseReturn -> {
            // 2.1 åˆ é™¤è®¢å•
            purchaseReturnMapper.deleteById(purchaseReturn.getId());
            // 2.2 åˆ é™¤è®¢å•项
            purchaseReturnItemMapper.deleteByReturnId(purchaseReturn.getId());
            // 2.3 æ›´æ–°é‡‡è´­è®¢å•的出库数量
            updatePurchaseOrderReturnCount(purchaseReturn.getOrderId());
        });
    }
    private ErpPurchaseReturnDO validatePurchaseReturnExists(Long id) {
        ErpPurchaseReturnDO purchaseReturn = purchaseReturnMapper.selectById(id);
        if (purchaseReturn == null) {
            throw exception(PURCHASE_RETURN_NOT_EXISTS);
        }
        return purchaseReturn;
    }
    @Override
    public ErpPurchaseReturnDO getPurchaseReturn(Long id) {
        return purchaseReturnMapper.selectById(id);
    }
    @Override
    public ErpPurchaseReturnDO validatePurchaseReturn(Long id) {
        ErpPurchaseReturnDO purchaseReturn = getPurchaseReturn(id);
        if (ObjectUtil.notEqual(purchaseReturn.getStatus(), ErpAuditStatus.APPROVE.getStatus())) {
            throw exception(PURCHASE_RETURN_NOT_APPROVE);
        }
        return purchaseReturn;
    }
    @Override
    public PageResult<ErpPurchaseReturnDO> getPurchaseReturnPage(ErpPurchaseReturnPageReqVO pageReqVO) {
        return purchaseReturnMapper.selectPage(pageReqVO);
    }
    // ==================== é‡‡è´­é€€è´§é¡¹ ====================
    @Override
    public List<ErpPurchaseReturnItemDO> getPurchaseReturnItemListByReturnId(Long returnId) {
        return purchaseReturnItemMapper.selectListByReturnId(returnId);
    }
    @Override
    public List<ErpPurchaseReturnItemDO> getPurchaseReturnItemListByReturnIds(Collection<Long> returnIds) {
        if (CollUtil.isEmpty(returnIds)) {
            return Collections.emptyList();
        }
        return purchaseReturnItemMapper.selectListByReturnIds(returnIds);
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/statistics/ErpPurchaseStatisticsService.java
@@ -1,5 +1,12 @@
package cn.iocoder.yudao.module.erp.service.statistics;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceAnalysisRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseStatisticsReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSummaryRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSupplierStatisticsRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseUncompletedOrderRespVO;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -21,4 +28,35 @@
     */
    BigDecimal getPurchasePrice(LocalDateTime beginTime, LocalDateTime endTime);
    /**
     * èŽ·å¾—é‡‡è´­æ±‡æ€»ç»Ÿè®¡ï¼ˆè®¢å• / å…¥åº“ / é€€è´§çš„æ€»æ•°ä¸Žæ€»é‡‘额)
     *
     * @return æ±‡æ€»ç»Ÿè®¡
     */
    ErpPurchaseSummaryRespVO getPurchaseSummary();
    /**
     * èŽ·å¾—ä¾›åº”å•†ä¾›è´§ç»Ÿè®¡
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µç»“æžœ
     */
    PageResult<ErpPurchaseSupplierStatisticsRespVO> getSupplierStatistics(ErpPurchaseStatisticsReqVO reqVO);
    /**
     * èŽ·å¾—é‡‡è´­ä»·æ ¼åˆ†æž
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µç»“æžœ
     */
    PageResult<ErpPurchasePriceAnalysisRespVO> getPriceAnalysis(ErpPurchaseStatisticsReqVO reqVO);
    /**
     * èŽ·å¾—æœªå®Œæˆé‡‡è´­è®¢å•è·Ÿè¸ª
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†é¡µç»“æžœ
     */
    PageResult<ErpPurchaseUncompletedOrderRespVO> getUncompletedOrders(ErpPurchaseStatisticsReqVO reqVO);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/statistics/ErpPurchaseStatisticsServiceImpl.java
@@ -1,11 +1,32 @@
package cn.iocoder.yudao.module.erp.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceAnalysisRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseStatisticsReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSummaryRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSupplierStatisticsRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseUncompletedOrderRespVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.statistics.ErpPurchaseStatisticsMapper;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
/**
 * ERP é‡‡è´­ç»Ÿè®¡ Service å®žçŽ°ç±»
@@ -17,10 +38,97 @@
    @Resource
    private ErpPurchaseStatisticsMapper purchaseStatisticsMapper;
    @Resource
    private ErpPurchaseOrderItemMapper purchaseOrderItemMapper;
    @Resource
    private MdmItemApi mdmItemApi;
    @Override
    public BigDecimal getPurchasePrice(LocalDateTime beginTime, LocalDateTime endTime) {
        return purchaseStatisticsMapper.getPurchasePrice(beginTime, endTime);
    }
    @Override
    public ErpPurchaseSummaryRespVO getPurchaseSummary() {
        ErpPurchaseSummaryRespVO summary = purchaseStatisticsMapper.selectPurchaseSummary();
        return summary != null ? summary : new ErpPurchaseSummaryRespVO()
                .setTotalOrderCount(0L).setTotalOrderAmount(BigDecimal.ZERO)
                .setTotalInCount(0L).setTotalInAmount(BigDecimal.ZERO)
                .setTotalReturnCount(0L).setTotalReturnAmount(BigDecimal.ZERO);
    }
    @Override
    public PageResult<ErpPurchaseSupplierStatisticsRespVO> getSupplierStatistics(ErpPurchaseStatisticsReqVO reqVO) {
        Page<ErpPurchaseSupplierStatisticsRespVO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        purchaseStatisticsMapper.selectSupplierStatistics(page, reqVO);
        return new PageResult<>(page.getRecords(), page.getTotal());
    }
    @Override
    public PageResult<ErpPurchasePriceAnalysisRespVO> getPriceAnalysis(ErpPurchaseStatisticsReqVO reqVO) {
        Page<ErpPurchasePriceAnalysisRespVO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        purchaseStatisticsMapper.selectPriceAnalysis(page, reqVO);
        fillProductInfo(page.getRecords());
        return new PageResult<>(page.getRecords(), page.getTotal());
    }
    @Override
    public PageResult<ErpPurchaseUncompletedOrderRespVO> getUncompletedOrders(ErpPurchaseStatisticsReqVO reqVO) {
        Page<ErpPurchaseUncompletedOrderRespVO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        purchaseStatisticsMapper.selectUncompletedOrders(page, reqVO);
        fillUncompletedOrderInfo(page.getRecords());
        return new PageResult<>(page.getRecords(), page.getTotal());
    }
    /**
     * è¡¥å…¨äº§å“åç§°ã€æ¡ç ï¼Œå¹¶è®¡ç®—价格变化率
     */
    private void fillProductInfo(List<ErpPurchasePriceAnalysisRespVO> list) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, ErpPurchasePriceAnalysisRespVO::getProductId));
        list.forEach(vo -> {
            MapUtils.findAndThen(itemMap, vo.getProductId(), item -> vo
                    .setProductName(item.getName()).setProductBarCode(item.getBarCode()));
            // ä»·æ ¼å˜åŒ–率 = (最近价格 - å¹³å‡ä»·æ ¼) / å¹³å‡ä»·æ ¼ Ã— 100
            if (vo.getLatestPrice() == null || vo.getAvgPrice() == null
                    || vo.getAvgPrice().compareTo(BigDecimal.ZERO) == 0) {
                vo.setPriceChangeRate(BigDecimal.ZERO);
                return;
            }
            vo.setPriceChangeRate(vo.getLatestPrice().subtract(vo.getAvgPrice())
                    .multiply(BigDecimal.valueOf(100))
                    .divide(vo.getAvgPrice(), 2, RoundingMode.HALF_UP));
        });
    }
    /**
     * è¡¥å…¨äº§å“åç§°ï¼Œå¹¶è®¡ç®—未完成金额
     */
    private void fillUncompletedOrderInfo(List<ErpPurchaseUncompletedOrderRespVO> list) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        List<ErpPurchaseOrderItemDO> orderItems = purchaseOrderItemMapper.selectListByOrderIds(
                convertSet(list, ErpPurchaseUncompletedOrderRespVO::getOrderId));
        Map<Long, List<ErpPurchaseOrderItemDO>> orderItemMap = convertMultiMap(orderItems, ErpPurchaseOrderItemDO::getOrderId);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(orderItems, ErpPurchaseOrderItemDO::getProductId));
        list.forEach(vo -> {
            List<ErpPurchaseOrderItemDO> items = orderItemMap.getOrDefault(vo.getOrderId(), Collections.emptyList());
            vo.setProductNames(items.stream()
                    .map(item -> {
                        MdmItemRespDTO mdmItem = itemMap.get(item.getProductId());
                        return mdmItem != null ? mdmItem.getName() : "ID=" + item.getProductId();
                    })
                    .distinct()
                    .collect(Collectors.joining(",")));
            BigDecimal orderAmount = vo.getOrderAmount() != null ? vo.getOrderAmount() : BigDecimal.ZERO;
            BigDecimal inAmount = vo.getInAmount() != null ? vo.getInAmount() : BigDecimal.ZERO;
            vo.setUncompletedAmount(orderAmount.subtract(inAmount));
        });
    }
}
yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml
@@ -10,9 +10,6 @@
               <if test="endTime != null">
                   AND in_time &lt; #{endTime}
               </if>
               <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
                   AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
               </if>
               AND deleted = 0) -
            (SELECT IFNULL(SUM(total_price), 0)
                FROM erp_purchase_return
@@ -20,10 +17,135 @@
                <if test="endTime != null">
                    AND return_time &lt; #{endTime}
                </if>
                <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
                    AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
                </if>
                AND deleted = 0)
    </select>
</mapper>
    <select id="selectPurchaseSummary"
            resultType="cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSummaryRespVO">
        SELECT
            (SELECT COUNT(1) FROM erp_purchase_order WHERE deleted = 0) AS totalOrderCount,
            (SELECT IFNULL(SUM(total_price), 0) FROM erp_purchase_order WHERE deleted = 0) AS totalOrderAmount,
            (SELECT COUNT(1) FROM erp_purchase_in WHERE deleted = 0) AS totalInCount,
            (SELECT IFNULL(SUM(total_price), 0) FROM erp_purchase_in WHERE deleted = 0) AS totalInAmount,
            (SELECT COUNT(1) FROM erp_purchase_return WHERE deleted = 0) AS totalReturnCount,
            (SELECT IFNULL(SUM(total_price), 0) FROM erp_purchase_return WHERE deleted = 0) AS totalReturnAmount
    </select>
    <select id="selectSupplierStatistics"
            resultType="cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseSupplierStatisticsRespVO">
        SELECT s.id AS supplierId,
               s.name AS supplierName,
               IFNULL(o.order_count, 0) AS orderCount,
               IFNULL(o.order_amount, 0) AS orderAmount,
               IFNULL(i.in_count, 0) AS inCount,
               IFNULL(i.in_amount, 0) AS inAmount,
               IFNULL(r.return_count, 0) AS returnCount,
               IFNULL(r.return_amount, 0) AS returnAmount
        FROM srm_supplier s
        INNER JOIN (
            SELECT supplier_id,
                   COUNT(1) AS order_count,
                   IFNULL(SUM(total_price), 0) AS order_amount
            FROM erp_purchase_order
            WHERE deleted = 0
              AND supplier_id IS NOT NULL
              <if test="reqVO.supplierId != null">
                  AND supplier_id = #{reqVO.supplierId}
              </if>
              <if test="reqVO.orderTime != null and reqVO.orderTime.length == 2">
                  AND order_time BETWEEN #{reqVO.orderTime[0]} AND #{reqVO.orderTime[1]}
              </if>
            GROUP BY supplier_id
        ) o ON o.supplier_id = s.id
        LEFT JOIN (
            SELECT po.supplier_id,
                   COUNT(1) AS in_count,
                   IFNULL(SUM(pi.total_price), 0) AS in_amount
            FROM erp_purchase_in pi
            INNER JOIN erp_purchase_order po ON po.id = pi.order_id AND po.deleted = 0
            WHERE pi.deleted = 0
              <if test="reqVO.supplierId != null">
                  AND po.supplier_id = #{reqVO.supplierId}
              </if>
              <if test="reqVO.orderTime != null and reqVO.orderTime.length == 2">
                  AND po.order_time BETWEEN #{reqVO.orderTime[0]} AND #{reqVO.orderTime[1]}
              </if>
            GROUP BY po.supplier_id
        ) i ON i.supplier_id = s.id
        LEFT JOIN (
            SELECT po.supplier_id,
                   COUNT(1) AS return_count,
                   IFNULL(SUM(pr.total_price), 0) AS return_amount
            FROM erp_purchase_return pr
            INNER JOIN erp_purchase_order po ON po.id = pr.order_id AND po.deleted = 0
            WHERE pr.deleted = 0
              <if test="reqVO.supplierId != null">
                  AND po.supplier_id = #{reqVO.supplierId}
              </if>
              <if test="reqVO.orderTime != null and reqVO.orderTime.length == 2">
                  AND po.order_time BETWEEN #{reqVO.orderTime[0]} AND #{reqVO.orderTime[1]}
              </if>
            GROUP BY po.supplier_id
        ) r ON r.supplier_id = s.id
        WHERE s.deleted = 0
        <if test="reqVO.supplierId != null">
            AND s.id = #{reqVO.supplierId}
        </if>
        ORDER BY orderAmount DESC, supplierId ASC
    </select>
    <select id="selectPriceAnalysis"
            resultType="cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchasePriceAnalysisRespVO">
        SELECT item.product_id AS productId,
               AVG(item.product_price) AS avgPrice,
               MIN(item.product_price) AS minPrice,
               MAX(item.product_price) AS maxPrice,
               (SELECT i2.product_price
                FROM erp_purchase_order_items i2
                INNER JOIN erp_purchase_order o2 ON o2.id = i2.order_id AND o2.deleted = 0
                WHERE i2.deleted = 0
                  AND i2.product_id = item.product_id
                ORDER BY o2.order_time DESC, i2.id DESC
                LIMIT 1) AS latestPrice
        FROM erp_purchase_order_items item
        INNER JOIN erp_purchase_order o ON o.id = item.order_id AND o.deleted = 0
        WHERE item.deleted = 0
          <if test="reqVO.productId != null">
              AND item.product_id = #{reqVO.productId}
          </if>
          <if test="reqVO.orderTime != null and reqVO.orderTime.length == 2">
              AND o.order_time BETWEEN #{reqVO.orderTime[0]} AND #{reqVO.orderTime[1]}
          </if>
        GROUP BY item.product_id
        ORDER BY productId ASC
    </select>
    <select id="selectUncompletedOrders"
            resultType="cn.iocoder.yudao.module.erp.controller.admin.statistics.vo.purchase.ErpPurchaseUncompletedOrderRespVO">
        SELECT o.id AS orderId,
               o.no AS orderNo,
               s.name AS supplierName,
               o.order_time AS orderTime,
               o.total_price AS orderAmount,
               IFNULL((SELECT IFNULL(SUM(i.total_price), 0)
                       FROM erp_purchase_in i
                       WHERE i.order_id = o.id AND i.deleted = 0 AND i.status = 20), 0) AS inAmount,
               GREATEST(DATEDIFF(CURDATE(), DATE(o.order_time)) - 30, 0) AS daysOverdue
        FROM erp_purchase_order o
        LEFT JOIN srm_supplier s ON s.id = o.supplier_id AND s.deleted = 0
        WHERE o.deleted = 0
          AND o.status = 20
          AND (o.in_status IS NULL OR o.in_status != 2)
          <if test="reqVO.supplierId != null">
              AND o.supplier_id = #{reqVO.supplierId}
          </if>
          <if test="reqVO.orderNo != null and reqVO.orderNo != ''">
              AND o.no LIKE CONCAT('%', #{reqVO.orderNo}, '%')
          </if>
          <if test="reqVO.orderTime != null and reqVO.orderTime.length == 2">
              AND o.order_time BETWEEN #{reqVO.orderTime[0]} AND #{reqVO.orderTime[1]}
          </if>
        ORDER BY o.order_time ASC, o.id ASC
    </select>
</mapper>
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsPurchaseInboundReqDTO.java
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsSalesOutboundReqDTO.java
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsStockDetailRespDTO.java
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/dto/WmsStockQueryReqDTO.java
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/stock/WmsStockApi.java