宁夏万通需求:出库单行支持主/辅单位录入;考勤节假日配置改为时间段;物料型号维护;设备批量删除;来料检验默认方案解析

1. MES 仓储:6 张出入库单明细行(采购入库、杂项出库、外协发料、生产领料、销售出库、采购退货)新增 item_model_id/enter_unit_measure_id/enter_quantity 三列,支持按「主单位 + 辅单位(结合型号换算)」录入,新增 MesWmItemUnitConvertService 统一换算;quantity 仍为主单位口径,库存与库存流水口径不变。
2. MDM:新增物料型号(mdm_item_model)全套接口与管理服务,物料 DTO 补充 unitMeasureId2/unitMeasureRate1/unitMeasureId3/unitMeasureRate2 主辅单位信息,物料列表支持型号相关排序。
3. HRM 考勤:节假日配置由「单日」改为「时间段」,新增 start_date/end_date 两列,保存时校验结束不早于开始、且与已有配置区间不重叠(提示带本次区间、已有配置名称与区间);列表查询改为按区间交集筛选,isWorkDay 按时间段覆盖判断;date 列保留写入开始日期仅作存量兼容。
4. MES 设备:设备台账支持批量删除。
5. MES 质检:IQC 默认检验方案解析(物料已绑定优先,未绑定取任一启用方案)。
6. docs:全库配置导出生成脚本更新为 0930 版基线(417 张表,含上述表结构变更)。
7. 不含本地配置 application-local.yaml。
已修改47个文件
已添加13个文件
1987 ■■■■■ 文件已修改
docs/sql/gen_config_export_all.py 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemModelApi.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemModelRespDTO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemModelApiImpl.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemModelController.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemModelPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemModelRespVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemModelSaveReqVO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemModelDO.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemModelMapper.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemModelService.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemModelServiceImpl.java 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptLineController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/line/MesWmItemReceiptLineRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/line/MesWmItemReceiptLineSaveReqVO.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/MesWmMiscIssueLineController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/line/MesWmMiscIssueLineRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/line/MesWmMiscIssueLineSaveReqVO.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueLineController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/line/MesWmOutsourceIssueLineRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/line/MesWmOutsourceIssueLineSaveReqVO.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/MesWmProductIssueLineController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/line/MesWmProductIssueLineRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/line/MesWmProductIssueLineSaveReqVO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/MesWmProductSalesLineController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorLineController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/line/MesWmReturnVendorLineRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/line/MesWmReturnVendorLineSaveReqVO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/itemreceipt/MesWmItemReceiptLineDO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscissue/MesWmMiscIssueLineDO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/outsourceissue/MesWmOutsourceIssueLineDO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueLineDO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorLineDO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptLineServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueLineServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueLineServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorLineServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/unitconvert/MesWmItemUnitConvertService.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/unitconvert/MesWmItemUnitConvertServiceImpl.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/gen_config_export_all.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
"""重新生成 docs/sql/config_export_all_20260928.sql(全量重生成,替换 0918 ç‰ˆï¼‰ã€‚
"""重新生成 docs/sql/config_export_all_20260930.sql(全量重生成,替换 0929 ç‰ˆï¼‰ã€‚
阶段一:当前源库 ruoyi-vue-pro å…¨åº“ 416 å¼ è¡¨ CREATE TABLE + 6 ç±»é…ç½®æ•°æ®ï¼ˆmysqldump å­—面量)。
阶段二:沿用 0918 æ–‡ä»¶çš„跨库身份/菜单/OAuth2 è¡¥å¯¼æ®µï¼ˆé€å­—复用)。
阶段一:当前源库 ruoyi-vue-pro å…¨åº“ CREATE TABLE + 6 ç±»é…ç½®æ•°æ®ï¼ˆmysqldump å­—面量)。
阶段二:沿用 0929 æ–‡ä»¶çš„跨库身份/菜单/OAuth2 è¡¥å¯¼æ®µï¼ˆé€å­—复用)。
"""
import re
import subprocess
@@ -13,9 +13,9 @@
USER = "root"
PWD = "1234"
BASE_DIR = r"D:\develop\XingRuan\MOM\mom-pro2-after\docs\sql"
SRC_0918 = BASE_DIR + r"\config_export_all_20260918.sql"
OUT = BASE_DIR + r"\config_export_all_20260928.sql"
BASE_DIR = r"D:\develop\XingRuan\MOM-LXWT\mom-pro2-after\docs\sql"
SRC_PREV = BASE_DIR + r"\config_export_all_20260929.sql"
OUT = BASE_DIR + r"\config_export_all_20260930.sql"
MYSQL = MYSQL_BIN + r"\mysql.exe"
MYSQLDUMP = MYSQL_BIN + r"\mysqldump.exe"
@@ -181,36 +181,40 @@
def load_stage2_block():
    with open(SRC_0918, encoding="utf-8") as f:
    with open(SRC_PREV, encoding="utf-8") as f:
        lines = f.read().splitlines()
    for i, line in enumerate(lines):
        if "阶段二:补导身份" in line:
            start = i - 1  # ä¸Šæ–¹ =============== æ¨ªçº¿
            return "\n".join(lines[start:]).rstrip() + "\n"
    raise RuntimeError("0918 æ–‡ä»¶æœªæ‰¾åˆ°é˜¶æ®µäºŒæ®µè½")
    raise RuntimeError("0929 æ–‡ä»¶æœªæ‰¾åˆ°é˜¶æ®µäºŒæ®µè½")
HEADER = """-- =======================================================================
-- æ–‡ä»¶ï¼šconfig_export_all_20260928.sql
-- æ–‡ä»¶ï¼šconfig_export_all_20260930.sql
-- ç”¨é€”:ruoyi-vue-pro「全库表结构 + 6 ç±»é…ç½®æ•°æ®ã€å¯¼å‡ºï¼Œä¾›åŒç»“构全新库初始化
--       é˜¶æ®µä¸€ï¼šå…¨åº“ {n_tables} å¼ è¡¨ CREATE TABLE + 6 ç±»é…ç½®æ•°æ®ï¼ˆå­—面量,自包含,不依赖源库在线)
--       é˜¶æ®µäºŒï¼šè·¨åº“补导 èœå•/角色/用户/OAuth2 ç­‰èº«ä»½æ•°æ®ï¼ˆæ‰§è¡ŒæœŸä»Žæºåº“拷贝)
-- æ¥æºåº“:ruoyi-vue-pro
-- åŸºçº¿ï¼š2026-09-18 ç‰ˆï¼ˆ419 å¼ è¡¨ï¼‰â†’ æœ¬æ¬¡ä¸º**全量重生成**(不再基于旧文件打补丁):
--       ç›´æŽ¥æŒ‰å½“前源库最新状态重导,一次性覆盖 0918 ä¹‹åŽå…¨éƒ¨ç»“构与配置漂移。
-- æœ¬æ¬¡å˜æ›´ï¼ˆç›¸å¯¹ 0918,2026-09-28):
--   â‘  è¡¨ç»“构:419 â†’ {n_tables} å¼ 
--      - åˆ é™¤ 6 å¼ åŽ†å²å¤‡ä»½è¡¨ï¼ˆbpm_bak_20260904_bytearray/category/deployment/model/procdef/procinfo,源库已无)
--      + æ–°å¢ž 3 å¼ è¡¨ï¼ˆafter_sale_problem、after_sale_visit、bpm_process_auto_approve_config,仅结构,源库无数据)
--      + 10 å¼ è¡¨ DDL ä¸Ž 0918 ä¸åŒï¼Œä»¥å½“前源库为准(after_sale_ticket、mes_qc_indicator、mes_qc_indicator_result_detail、
--        mes_qc_ipqc、mes_qc_iqc、mes_qc_oqc、mes_qc_rqc、mes_qc_template、mes_qc_template_indicator、mes_wm_return_issue_line)
--   â‘¡ é…ç½®æ•°æ®ä»¥å½“前源库为准重导(下列为 0918 å£°æ˜Žå€¼ â†’ æœ¬æ¬¡å¯¼å‡ºå€¼ï¼‰ï¼š
--      - system_dict_type 241→{n_dicttype}、system_dict_data 1157→{n_dictdata}(含 hrm_holiday_type èŠ‚å‡æ—¥ç±»åž‹ç­‰æ–°å¢žå­—å…¸ï¼‰
--      - system_company_config 0→1、bi_chart_config 36→37、bi_warehouse_coordinate 1→2、ai_api_key 1→3、bpm_user_group 3→4
--      - bpm_business_attach 11→4、bpm_category 22→15、bpm_form 4→3、bpm_process_listener 1→0(源库已删除的行不再导出)
-- åŸºçº¿ï¼š2026-09-29 ç‰ˆï¼ˆ417 å¼ è¡¨ï¼‰â†’ æœ¬æ¬¡ä¸º**全量重生成**(不再基于旧文件打补丁):
--       ç›´æŽ¥æŒ‰å½“前源库最新状态重导,一次性覆盖 0929 ä¹‹åŽå…¨éƒ¨ç»“构与配置漂移。
-- æœ¬æ¬¡å˜æ›´ï¼ˆç›¸å¯¹ 0929,2026-09-30):
--   â‘  è¡¨æ•°é‡ä¸å˜ï¼ˆ{n_tables} å¼ ï¼‰ï¼Œæ— æ–°å¢ž/删除表。
--   â‘¡ 5 å¼ å‡ºåº“单明细行增加 3 åˆ—(出库单行支持「主单位 + è¾…单位(结合型号换算)」录入):
--      mes_wm_misc_issue_line(杂项出库)、mes_wm_outsource_issue_line(外协发料)、
--      mes_wm_product_issue_line(生产领料)、mes_wm_product_sales_line(销售出库)、
--      mes_wm_return_vendor_line(采购退货):
--      item_model_id(物料型号编号)、enter_unit_measure_id(录入单位编号)、
--      enter_quantity(录入数量,按录入单位计);
--      quantity ä¿æŒä¸ºã€Œç‰©æ–™ä¸»å•位口径的出库数量」,库存与库存流水口径不变。
--   â‘¢ hrm_attendance_holiday å¢žåŠ  2 åˆ—(考勤节假日配置由「单日」改为「时间段」录入):
--      start_date(开始日期)、end_date(结束日期);原 date åˆ—保留不动,
--      åŽç«¯ä¿å­˜æ—¶ä»å†™å…¥å¼€å§‹æ—¥æœŸï¼Œä»…作存量兼容,业务判断改用 start_date/end_date。
--   â‘£ é…ç½®æ•°æ®ä»¥å½“前源库为准重导:
--      - system_dict_type {n_dicttype}、system_dict_data {n_dictdata}
--      - ååŒæµç¨‹å®šä¹‰æŒ‰æºåº“当前**激活**集合重导({n_procdef} ä¸ªæµç¨‹å®šä¹‰ + å¯¹åº”部署/字节资源 + è®¾è®¡æ€æ¨¡åž‹ï¼‰
--   â‘¢ èœå•/权限节点(节假日配置 6014/60141-60145、各模块导入按钮 1075311/1075417-1075420 ç­‰ï¼‰ç”±é˜¶æ®µäºŒ
--      è·¨åº“全量拷贝自动携带,无需改动本文件数据段;在此登记以说明本版文件确实覆盖这些变更。
--   â‘¤ èœå•/权限节点由阶段二跨库全量拷贝自动携带,无需改动本文件数据段。
--      æœ¬æ¬¡æœªæ–°å¢žèœå•/权限点(节假日配置菜单及其按钮权限此前已入库,随阶段二自动携带)。
-- éªŒè¯ï¼š{verify}
-- èŒƒå›´ï¼šå…¨åº“ BASE TABLE å…± {n_tables} å¼ ï¼Œå…¨éƒ¨è¾“出 CREATE TABLE;
--       ä»…以下配置类表带数据(INSERT),其余业务/系统/运行历史表只保留空表结构:
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -85,7 +85,7 @@
    ErrorCode ATTENDANCE_RULE_NAME_EXISTS = new ErrorCode(1_030_004_006, "考勤规则名称已存在");
    ErrorCode ATTENDANCE_RULE_DEPT_EXISTS = new ErrorCode(1_030_004_007, "该部门已配置考勤规则");
    ErrorCode ATTENDANCE_HOLIDAY_NOT_EXISTS = new ErrorCode(1_030_004_008, "节假日配置不存在(ID={}),可能已被他人删除。请刷新列表后重试");
    ErrorCode ATTENDANCE_HOLIDAY_DATE_EXISTS = new ErrorCode(1_030_004_009, "日期【{}】已存在节假日配置,不能重复新增。请改为编辑该日期的记录(可直接修改类型与备注),或先删除原记录再新增");
    ErrorCode ATTENDANCE_HOLIDAY_RANGE_OVERLAP = new ErrorCode(1_030_004_009, "日期区间【{} ~ {}】与已有配置「{}」({} ~ {})重叠,无法保存。请调整本次日期区间避开该配置,或直接编辑/删除已有配置后再保存");
    ErrorCode ATTENDANCE_CLOCK_FAIL_NO_RULE = new ErrorCode(1_030_004_010, "打卡失败,未找到考勤规则");
    ErrorCode ATTENDANCE_CLOCK_FAIL_HOLIDAY = new ErrorCode(1_030_004_011, "打卡失败,当日为休息日");
    ErrorCode ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY = new ErrorCode(1_030_004_012, "打卡失败,当日不是工作日");
@@ -93,6 +93,7 @@
    ErrorCode ATTENDANCE_EXCEPTION_UPDATE_AUDIT_STATUS_FAIL = new ErrorCode(1_030_004_014, "更新考勤异常审批状态失败,原因:不是审批中状态");
    ErrorCode ATTENDANCE_EXCEPTION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_004_015, "考勤异常审批流程不存在");
    ErrorCode ATTENDANCE_CLOCK_ALREADY_CLOCKED = new ErrorCode(1_030_004_016, "已打过卡,请勿重复打卡");
    ErrorCode ATTENDANCE_HOLIDAY_DATE_RANGE_INVALID = new ErrorCode(1_030_004_017, "结束日期【{}】不能早于开始日期【{}】,无法保存。请重新选择正确的日期区间");
    // ========== è–ªé…¬ç®¡ç† 1-030-005-000 ==========
    ErrorCode SALARY_STRUCTURE_NOT_EXISTS = new ErrorCode(1_030_005_000, "薪酬结构不存在");
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java
@@ -23,7 +23,7 @@
    @Schema(description = "是否法定节假日:0-否,1-是", example = "1")
    private Integer isLegal;
    @Schema(description = "日期区间,传 [开始日期, ç»“束日期]", example = "[\"2026-01-01\", \"2026-12-31\"]")
    @Schema(description = "日期区间,传 [开始日期, ç»“束日期],返回与配置时间段有交集的数据", example = "[\"2026-01-01\", \"2026-12-31\"]")
    private LocalDate[] date;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java
@@ -23,9 +23,13 @@
    @ExcelProperty("节假日名称")
    private String name;
    @Schema(description = "日期")
    @ExcelProperty("日期")
    private LocalDate date;
    @Schema(description = "开始日期")
    @ExcelProperty("开始日期")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @ExcelProperty("结束日期")
    private LocalDate endDate;
    @Schema(description = "类型:1-休息日,2-工作日")
    @ExcelProperty(value = "类型", converter = DictConvert.class)
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java
@@ -18,9 +18,13 @@
    @NotBlank(message = "节假日名称不能为空")
    private String name;
    @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-10")
    @NotNull(message = "日期不能为空")
    private LocalDate date;
    @Schema(description = "开始日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-10")
    @NotNull(message = "开始日期不能为空")
    private LocalDate startDate;
    @Schema(description = "结束日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-17")
    @NotNull(message = "结束日期不能为空")
    private LocalDate endDate;
    @Schema(description = "类型:1-休息日,2-工作日", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "类型不能为空")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java
@@ -35,11 +35,21 @@
    private String name;
    /**
     * æ—¥æœŸ
     * æ—¥æœŸï¼ˆå…¼å®¹å­˜é‡ NOT NULL åˆ—的冗余字段,始终写入 startDate)
     */
    private LocalDate date;
    /**
     * å¼€å§‹æ—¥æœŸ
     */
    private LocalDate startDate;
    /**
     * ç»“束日期
     */
    private LocalDate endDate;
    /**
     * ç±»åž‹ï¼š1-休息日,2-工作日
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java
@@ -19,23 +19,30 @@
public interface HrmAttendanceHolidayMapper extends BaseMapperX<HrmAttendanceHolidayDO> {
    default PageResult<HrmAttendanceHolidayDO> selectPage(HrmAttendanceHolidayPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
        LambdaQueryWrapperX<HrmAttendanceHolidayDO> query = new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
                .likeIfPresent(HrmAttendanceHolidayDO::getName, reqVO.getName())
                .eqIfPresent(HrmAttendanceHolidayDO::getType, reqVO.getType())
                .eqIfPresent(HrmAttendanceHolidayDO::getIsLegal, reqVO.getIsLegal())
                .betweenIfPresent(HrmAttendanceHolidayDO::getDate, reqVO.getDate())
                .orderByDesc(HrmAttendanceHolidayDO::getDate));
                .eqIfPresent(HrmAttendanceHolidayDO::getIsLegal, reqVO.getIsLegal());
        // æ—¥æœŸç­›é€‰ï¼šé…ç½®æ—¶é—´æ®µä¸ŽæŸ¥è¯¢åŒºé—´æœ‰äº¤é›†å³å‘½ä¸­
        LocalDate[] dateRange = reqVO.getDate();
        if (dateRange != null && dateRange.length >= 2 && dateRange[0] != null && dateRange[1] != null) {
            query.le(HrmAttendanceHolidayDO::getStartDate, dateRange[1])
                    .ge(HrmAttendanceHolidayDO::getEndDate, dateRange[0]);
        }
        return selectPage(reqVO, query.orderByDesc(HrmAttendanceHolidayDO::getStartDate)
                .orderByDesc(HrmAttendanceHolidayDO::getId));
    }
    default HrmAttendanceHolidayDO selectByDate(LocalDate date) {
        return selectOne(HrmAttendanceHolidayDO::getDate, date);
    }
    default List<HrmAttendanceHolidayDO> selectListByDateBetween(LocalDate startDate, LocalDate endDate) {
    /**
     * æŸ¥è¯¢æ—¶é—´æ®µä¸Ž [startDate, endDate] æœ‰äº¤é›†çš„配置
     * ä¼ å…¥åŒä¸€å¤©ï¼ˆdate, date)时,即查询覆盖该日的配置
     */
    default List<HrmAttendanceHolidayDO> selectListByDateRange(LocalDate startDate, LocalDate endDate) {
        return selectList(new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
                .ge(HrmAttendanceHolidayDO::getDate, startDate)
                .le(HrmAttendanceHolidayDO::getDate, endDate)
                .orderByAsc(HrmAttendanceHolidayDO::getDate));
                .le(HrmAttendanceHolidayDO::getStartDate, endDate)
                .ge(HrmAttendanceHolidayDO::getEndDate, startDate)
                .orderByAsc(HrmAttendanceHolidayDO::getStartDate)
                .orderByAsc(HrmAttendanceHolidayDO::getId));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java
@@ -55,7 +55,7 @@
    PageResult<HrmAttendanceHolidayDO> getAttendanceHolidayPage(HrmAttendanceHolidayPageReqVO pageReqVO);
    /**
     * èŽ·å¾—æŒ‡å®šæ—¥æœŸèŒƒå›´çš„èŠ‚å‡æ—¥åˆ—è¡¨
     * èŽ·å¾—æ—¶é—´æ®µä¸ŽæŒ‡å®šæ—¥æœŸèŒƒå›´æœ‰äº¤é›†çš„èŠ‚å‡æ—¥åˆ—è¡¨
     *
     * @param startDate å¼€å§‹æ—¥æœŸ
     * @param endDate ç»“束日期
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java
@@ -32,9 +32,10 @@
    @Override
    public Long createAttendanceHoliday(HrmAttendanceHolidaySaveReqVO createReqVO) {
        // æ ¡éªŒæ—¥æœŸæ˜¯å¦å·²å­˜åœ¨é…ç½®
        validateAttendanceHolidayDateExists(createReqVO.getDate());
        validateDateRange(createReqVO);
        validateDateRangeNoOverlap(createReqVO.getStartDate(), createReqVO.getEndDate(), null);
        HrmAttendanceHolidayDO attendanceHoliday = BeanUtils.toBean(createReqVO, HrmAttendanceHolidayDO.class);
        attendanceHoliday.setDate(createReqVO.getStartDate());
        if (attendanceHoliday.getIsLegal() == null) {
            attendanceHoliday.setIsLegal(0);
        }
@@ -45,12 +46,10 @@
    @Override
    public void updateAttendanceHoliday(HrmAttendanceHolidaySaveReqVO updateReqVO) {
        validateAttendanceHolidayExists(updateReqVO.getId());
        // å¦‚果日期变更,校验新日期是否已存在配置
        HrmAttendanceHolidayDO oldHoliday = attendanceHolidayMapper.selectById(updateReqVO.getId());
        if (!oldHoliday.getDate().equals(updateReqVO.getDate())) {
            validateAttendanceHolidayDateExists(updateReqVO.getDate());
        }
        validateDateRange(updateReqVO);
        validateDateRangeNoOverlap(updateReqVO.getStartDate(), updateReqVO.getEndDate(), updateReqVO.getId());
        HrmAttendanceHolidayDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceHolidayDO.class);
        updateObj.setDate(updateReqVO.getStartDate());
        attendanceHolidayMapper.updateById(updateObj);
    }
@@ -66,9 +65,19 @@
        }
    }
    private void validateAttendanceHolidayDateExists(LocalDate date) {
        if (attendanceHolidayMapper.selectByDate(date) != null) {
            throw exception(ATTENDANCE_HOLIDAY_DATE_EXISTS, date);
    private void validateDateRange(HrmAttendanceHolidaySaveReqVO reqVO) {
        if (reqVO.getEndDate().isBefore(reqVO.getStartDate())) {
            throw exception(ATTENDANCE_HOLIDAY_DATE_RANGE_INVALID, reqVO.getEndDate(), reqVO.getStartDate());
        }
    }
    private void validateDateRangeNoOverlap(LocalDate startDate, LocalDate endDate, Long excludeId) {
        for (HrmAttendanceHolidayDO exist : attendanceHolidayMapper.selectListByDateRange(startDate, endDate)) {
            if (excludeId != null && excludeId.equals(exist.getId())) {
                continue;
            }
            throw exception(ATTENDANCE_HOLIDAY_RANGE_OVERLAP, startDate, endDate,
                    exist.getName(), exist.getStartDate(), exist.getEndDate());
        }
    }
@@ -84,16 +93,16 @@
    @Override
    public List<HrmAttendanceHolidayDO> getAttendanceHolidayListByDateRange(LocalDate startDate, LocalDate endDate) {
        return attendanceHolidayMapper.selectListByDateBetween(startDate, endDate);
        return attendanceHolidayMapper.selectListByDateRange(startDate, endDate);
    }
    @Override
    public boolean isWorkDay(LocalDate date) {
        // 1. æŸ¥è¯¢èŠ‚å‡æ—¥é…ç½®
        HrmAttendanceHolidayDO holiday = attendanceHolidayMapper.selectByDate(date);
        if (holiday != null) {
        // 1. æŸ¥è¯¢è¦†ç›–该日期的节假日配置(配置时间段不允许重叠,至多命中一条)
        List<HrmAttendanceHolidayDO> holidays = attendanceHolidayMapper.selectListByDateRange(date, date);
        if (!holidays.isEmpty()) {
            // æœ‰é…ç½®æ—¶ï¼ŒæŒ‰é…ç½®åˆ¤æ–­
            return HrmAttendanceHolidayTypeEnum.WORK_DAY.getType().equals(holiday.getType());
            return HrmAttendanceHolidayTypeEnum.WORK_DAY.getType().equals(holidays.get(0).getType());
        }
        // 2. æ— é…ç½®æ—¶ï¼ŒæŒ‰å‘¨æœ«åˆ¤æ–­
        DayOfWeek dayOfWeek = date.getDayOfWeek();
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemModelApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.mdm.api.item;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
 * MDM ç‰©æ–™åž‹å· API æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface MdmItemModelApi {
    String PREFIX = "/rpc-api/mdm/item-model";
    /**
     * èŽ·å–ç‰©æ–™åž‹å·
     *
     * @param id åž‹å·ç¼–号
     * @return ç‰©æ–™åž‹å·
     */
    CommonResult<MdmItemModelRespDTO> getItemModel(Long id);
    /**
     * èŽ·å–ç‰©æ–™åž‹å·åˆ—è¡¨
     *
     * @param ids åž‹å·ç¼–号列表
     * @return ç‰©æ–™åž‹å·åˆ—表
     */
    CommonResult<List<MdmItemModelRespDTO>> getItemModelList(Collection<Long> ids);
    /**
     * èŽ·å–ç‰©æ–™åž‹å· Map
     *
     * @param ids åž‹å·ç¼–号列表
     * @return ç‰©æ–™åž‹å· Map
     */
    default Map<Long, MdmItemModelRespDTO> getItemModelMap(Collection<Long> ids) {
        List<MdmItemModelRespDTO> list = getItemModelList(ids).getCheckedData();
        return convertMap(list, MdmItemModelRespDTO::getId);
    }
    /**
     * èŽ·å–æŒ‡å®šç‰©æ–™çš„åž‹å·åˆ—è¡¨
     *
     * @param itemId ç‰©æ–™ç¼–号
     * @return ç‰©æ–™åž‹å·åˆ—表
     */
    CommonResult<List<MdmItemModelRespDTO>> getItemModelListByItemId(Long itemId);
    /**
     * æ ¡éªŒç‰©æ–™åž‹å·å­˜åœ¨
     *
     * @param id åž‹å·ç¼–号
     * @return ç‰©æ–™åž‹å·
     */
    CommonResult<MdmItemModelRespDTO> validateItemModelExists(Long id);
    /**
     * æ ¡éªŒç‰©æ–™åž‹å·å­˜åœ¨ã€å¯ç”¨ï¼Œä¸”归属于指定物料
     *
     * @param id     åž‹å·ç¼–号
     * @param itemId ç‰©æ–™ç¼–号
     * @return ç‰©æ–™åž‹å·
     */
    CommonResult<MdmItemModelRespDTO> validateItemModelExistsAndEnable(Long id, Long itemId);
}
yudao-module-mdm-api/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemModelRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.mdm.api.item.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "RPC æœåŠ¡ - MDM ç‰©æ–™åž‹å· Response DTO")
@Data
public class MdmItemModelRespDTO {
    @Schema(description = "型号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long itemId;
    @Schema(description = "型号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "4ç±³/æ ¹")
    private String model;
    @Schema(description = "型号计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
    private Long unitMeasureId;
    @Schema(description = "换算比率:1 åž‹å·å•位 = N ç‰©æ–™ä¸»å•位", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
    private BigDecimal unitMeasureRate;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/MdmItemModelApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.mdm.api.item;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemModelService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.MDM_ITEM_MODEL_UNIT_MISMATCH;
/**
 * MDM ç‰©æ–™åž‹å· API å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@RestController
@RequestMapping(MdmItemModelApi.PREFIX)
@Validated
public class MdmItemModelApiImpl implements MdmItemModelApi {
    @Resource
    private MdmItemModelService itemModelService;
    @Override
    public CommonResult<MdmItemModelRespDTO> getItemModel(Long id) {
        MdmItemModelDO itemModel = itemModelService.getItemModel(id);
        return success(BeanUtils.toBean(itemModel, MdmItemModelRespDTO.class));
    }
    @Override
    public CommonResult<List<MdmItemModelRespDTO>> getItemModelList(Collection<Long> ids) {
        List<MdmItemModelDO> list = itemModelService.getItemModelList(ids);
        return success(BeanUtils.toBean(list, MdmItemModelRespDTO.class));
    }
    @Override
    public CommonResult<List<MdmItemModelRespDTO>> getItemModelListByItemId(Long itemId) {
        List<MdmItemModelDO> list = itemModelService.getItemModelListByItemId(itemId);
        return success(BeanUtils.toBean(list, MdmItemModelRespDTO.class));
    }
    @Override
    public CommonResult<MdmItemModelRespDTO> validateItemModelExists(Long id) {
        MdmItemModelDO itemModel = itemModelService.validateItemModelExists(id);
        return success(BeanUtils.toBean(itemModel, MdmItemModelRespDTO.class));
    }
    @Override
    public CommonResult<MdmItemModelRespDTO> validateItemModelExistsAndEnable(Long id, Long itemId) {
        MdmItemModelDO itemModel = itemModelService.validateItemModelExistsAndEnable(id);
        if (ObjUtil.notEqual(itemModel.getItemId(), itemId)) {
            throw new ServiceException(MDM_ITEM_MODEL_UNIT_MISMATCH.getCode(),
                    String.format("所选型号[%s](ID=%d)不属于当前物料(ID=%d),请重新选择该物料的型号",
                            itemModel.getModel(), itemModel.getId(), itemId));
        }
        return success(BeanUtils.toBean(itemModel, MdmItemModelRespDTO.class));
    }
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/api/item/dto/MdmItemRespDTO.java
@@ -30,6 +30,18 @@
    @Schema(description = "计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long unitMeasureId;
    @Schema(description = "辅单位1编号", example = "2")
    private Long unitMeasureId2;
    @Schema(description = "辅单位1换算比率", example = "12")
    private BigDecimal unitMeasureRate1;
    @Schema(description = "辅单位2编号", example = "3")
    private Long unitMeasureId3;
    @Schema(description = "辅单位2换算比率", example = "100")
    private BigDecimal unitMeasureRate2;
    @Schema(description = "品牌编号", example = "1")
    private Long brandId;
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemModelController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelRespVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemModelService;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
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.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - MDM ç‰©æ–™åž‹å·ï¼ˆåž‹å· + ä¸»è¾…单位换算)")
@RestController
@RequestMapping("/mdm/item-model")
@Validated
public class MdmItemModelController {
    @Resource
    private MdmItemModelService itemModelService;
    @Resource
    private MdmItemService itemService;
    @Resource
    private MdmUnitMeasureService unitMeasureService;
    @PostMapping("/create")
    @Operation(summary = "创建物料型号")
    @PreAuthorize("@ss.hasPermission('mdm:item:create')")
    public CommonResult<Long> createItemModel(@Valid @RequestBody MdmItemModelSaveReqVO createReqVO) {
        return success(itemModelService.createItemModel(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新物料型号")
    @PreAuthorize("@ss.hasPermission('mdm:item:update')")
    public CommonResult<Boolean> updateItemModel(@Valid @RequestBody MdmItemModelSaveReqVO updateReqVO) {
        itemModelService.updateItemModel(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除物料型号")
    @Parameter(name = "id", description = "型号编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('mdm:item:delete')")
    public CommonResult<Boolean> deleteItemModel(@RequestParam("id") Long id) {
        itemModelService.deleteItemModel(id);
        return success(true);
    }
    @PostMapping("/save-list")
    @Operation(summary = "全量覆盖保存物料的型号列表")
    @PreAuthorize("@ss.hasPermission('mdm:item:update')")
    public CommonResult<Boolean> saveItemModelList(@RequestParam("itemId") Long itemId,
                                                   @RequestBody List<MdmItemModelSaveReqVO> modelList) {
        itemModelService.saveItemModelList(itemId, modelList);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得物料型号")
    @Parameter(name = "id", description = "型号编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<MdmItemModelRespVO> getItemModel(@RequestParam("id") Long id) {
        MdmItemModelDO itemModel = itemModelService.getItemModel(id);
        if (itemModel == null) {
            return success(null);
        }
        return success(buildRespVOList(List.of(itemModel)).get(0));
    }
    @GetMapping("/list-by-item")
    @Operation(summary = "获得指定物料的型号列表")
    @Parameter(name = "itemId", description = "物料编号", required = true, example = "1")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<List<MdmItemModelRespVO>> getItemModelListByItemId(@RequestParam("itemId") Long itemId) {
        return success(buildRespVOList(itemModelService.getItemModelListByItemId(itemId)));
    }
    @GetMapping("/list-by-items")
    @Operation(summary = "获得指定多个物料的型号列表")
    @Parameter(name = "itemIds", description = "物料编号列表", required = true)
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<List<MdmItemModelRespVO>> getItemModelListByItemIds(@RequestParam("itemIds") Collection<Long> itemIds) {
        List<MdmItemModelDO> list = itemModelService.getItemModelListByItemIds(itemIds);
        return success(buildRespVOList(list));
    }
    @GetMapping("/page")
    @Operation(summary = "获得物料型号分页")
    @PreAuthorize("@ss.hasPermission('mdm:item:query')")
    public CommonResult<PageResult<MdmItemModelRespVO>> getItemModelPage(@Valid MdmItemModelPageReqVO pageReqVO) {
        PageResult<MdmItemModelDO> pageResult = itemModelService.getItemModelPage(pageReqVO);
        return success(new PageResult<>(buildRespVOList(pageResult.getList()), pageResult.getTotal()));
    }
    private List<MdmItemModelRespVO> buildRespVOList(List<MdmItemModelDO> list) {
        if (list == null || list.isEmpty()) {
            return List.of();
        }
        List<MdmItemModelRespVO> respList = BeanUtils.toBean(list, MdmItemModelRespVO.class);
        Map<Long, MdmItemDO> itemMap = convertMap(itemService.getItemList(convertSet(list, MdmItemModelDO::getItemId)),
                MdmItemDO::getId);
        Map<Long, MdmUnitMeasureDO> unitMap = convertMap(
                unitMeasureService.getUnitMeasureList(convertSet(list, MdmItemModelDO::getUnitMeasureId)),
                MdmUnitMeasureDO::getId);
        respList.forEach(resp -> {
            MdmItemDO item = itemMap.get(resp.getItemId());
            if (item != null) {
                resp.setItemCode(item.getCode()).setItemName(item.getName());
            }
            MdmUnitMeasureDO unit = unitMap.get(resp.getUnitMeasureId());
            if (unit != null) {
                resp.setUnitMeasureName(unit.getName());
            }
        });
        return respList;
    }
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemModelPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
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;
@Schema(description = "管理后台 - MDM ç‰©æ–™åž‹å·åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MdmItemModelPageReqVO extends PageParam {
    @Schema(description = "物料编号", example = "1")
    private Long itemId;
    @Schema(description = "型号名称", example = "4ç±³/æ ¹")
    private String model;
    @Schema(description = "状态", example = "0")
    private Integer status;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemModelRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MDM ç‰©æ–™åž‹å· Response VO")
@Data
public class MdmItemModelRespVO {
    @Schema(description = "型号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private Long id;
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long itemId;
    @Schema(description = "物料编码", example = "M001")
    private String itemCode;
    @Schema(description = "物料名称", example = "不锈钢管")
    private String itemName;
    @Schema(description = "型号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "4ç±³/æ ¹")
    private String model;
    @Schema(description = "型号计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
    private Long unitMeasureId;
    @Schema(description = "型号计量单位名称", example = "æ ¹")
    private String unitMeasureName;
    @Schema(description = "换算比率:1 åž‹å·è®¡é‡å•位 = N ç‰©æ–™ä¸»è®¡é‡å•位", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
    private BigDecimal unitMeasureRate;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemModelSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - MDM ç‰©æ–™åž‹å·æ–°å¢ž/修改 Request VO")
@Data
public class MdmItemModelSaveReqVO {
    @Schema(description = "型号编号", example = "1024")
    private Long id;
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "物料编号不能为空")
    private Long itemId;
    @Schema(description = "型号名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "4ç±³/æ ¹")
    @NotBlank(message = "型号名称不能为空")
    @Size(max = 128, message = "型号名称长度不能超过 128 ä¸ªå­—符")
    private String model;
    @Schema(description = "型号计量单位编号(该型号下出入库录入使用的单位)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
    @NotNull(message = "型号计量单位不能为空")
    private Long unitMeasureId;
    @Schema(description = "换算比率:1 åž‹å·è®¡é‡å•位 = N ç‰©æ–™ä¸»è®¡é‡å•位", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
    @NotNull(message = "换算比率不能为空")
    @DecimalMin(value = "0", inclusive = false, message = "换算比率必须大于 0")
    private BigDecimal unitMeasureRate;
    @Schema(description = "状态", example = "0")
    private Integer status;
    @Schema(description = "备注", example = "备注")
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/item/MdmItemModelDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.mdm.dal.dataobject.item;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * MDM ç‰©æ–™åž‹å· DO
 *
 * åŒä¸€ç‰©æ–™å¯ç»´æŠ¤å¤šä¸ªåž‹å·ï¼Œæ¯ä¸ªåž‹å·å•独配置「型号单位 + æ¢ç®—比率」,
 * ç”¨äºŽå‡ºå…¥åº“时按型号单位录入、由系统换算成物料主单位数量记账。
 *
 * @author è¶…级管理员
 */
@TableName("mdm_item_model")
@KeySequence("mdm_item_model_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MdmItemModelDO extends BaseDO {
    /**
     * åž‹å·ç¼–号
     */
    @TableId
    private Long id;
    /**
     * ç‰©æ–™ç¼–号
     *
     * å…³è” {@link MdmItemDO#getId()}
     */
    private Long itemId;
    /**
     * åž‹å·åç§°
     *
     * ä¾‹å¦‚:4ç±³/æ ¹
     */
    private String model;
    /**
     * åž‹å·è®¡é‡å•位编号
     *
     * å…³è” {@link MdmUnitMeasureDO#getId()}
     * ä¾‹å¦‚:根
     */
    private Long unitMeasureId;
    /**
     * æ¢ç®—比率
     *
     * 1 åž‹å·è®¡é‡å•位 = N ç‰©æ–™ä¸»è®¡é‡å•位。例如 1æ ¹ = 4米,则比率为 4
     */
    private BigDecimal unitMeasureRate;
    /**
     * çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
@@ -30,7 +30,8 @@
                .eqIfPresent(MdmItemDO::getBrandId, reqVO.getBrandId())
                .eqIfPresent(MdmItemDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(MdmItemDO::getCreateTime, reqVO.getCreateTime())
                .orderByDesc(MdmItemDO::getId));
                .orderByAsc(MdmItemDO::getCategoryId)
                .orderByAsc(MdmItemDO::getCode));
    }
    default List<MdmItemDO> selectList(MdmItemPageReqVO reqVO, Collection<Long> categoryIds) {
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemModelMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.mdm.dal.mysql.item;
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.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelPageReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
 * MDM ç‰©æ–™åž‹å· Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface MdmItemModelMapper extends BaseMapperX<MdmItemModelDO> {
    default List<MdmItemModelDO> selectListByItemId(Long itemId) {
        return selectList(new LambdaQueryWrapperX<MdmItemModelDO>()
                .eq(MdmItemModelDO::getItemId, itemId)
                .orderByAsc(MdmItemModelDO::getId));
    }
    default List<MdmItemModelDO> selectListByItemIds(Collection<Long> itemIds) {
        return selectList(MdmItemModelDO::getItemId, itemIds);
    }
    default MdmItemModelDO selectByItemIdAndModel(Long itemId, String model) {
        return selectOne(new LambdaQueryWrapperX<MdmItemModelDO>()
                .eq(MdmItemModelDO::getItemId, itemId)
                .eq(MdmItemModelDO::getModel, model));
    }
    default int deleteByItemId(Long itemId) {
        return delete(MdmItemModelDO::getItemId, itemId);
    }
    default PageResult<MdmItemModelDO> selectPage(MdmItemModelPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MdmItemModelDO>()
                .eqIfPresent(MdmItemModelDO::getItemId, reqVO.getItemId())
                .likeIfPresent(MdmItemModelDO::getModel, reqVO.getModel())
                .eqIfPresent(MdmItemModelDO::getStatus, reqVO.getStatus())
                .orderByAsc(MdmItemModelDO::getId));
    }
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
@@ -18,6 +18,12 @@
    ErrorCode MDM_ITEM_SKU_CODE_DUPLICATE = new ErrorCode(1_050_006, "SKU编码已存在");
    ErrorCode MDM_ITEM_EXISTS_REFERENCE = new ErrorCode(1_050_007, "物料已被业务引用,无法删除");
    ErrorCode MDM_ITEM_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_008, "导入的物料数据不能为空");
    ErrorCode MDM_ITEM_MODEL_NOT_EXISTS = new ErrorCode(1_050_009, "物料型号不存在");
    ErrorCode MDM_ITEM_MODEL_IS_DISABLE = new ErrorCode(1_050_010, "物料型号已禁用");
    ErrorCode MDM_ITEM_MODEL_NAME_DUPLICATE = new ErrorCode(1_050_011, "该物料下已存在型号名称【{}】,请修改型号名称后重试");
    ErrorCode MDM_ITEM_MODEL_RATE_INVALID = new ErrorCode(1_050_012, "型号【{}】的换算比率【{}】不合理,1 ä¸ªåž‹å·è®¡é‡å•位必须对应大于 0 çš„物料主单位数量,请填写大于 0 çš„æ¢ç®—比率");
    ErrorCode MDM_ITEM_MODEL_UNIT_MISMATCH = new ErrorCode(1_050_013, "型号的计量单位与所选单位不一致");
    ErrorCode MDM_ITEM_MODEL_RATE_MUST_BE_ONE = new ErrorCode(1_050_014, "型号【{}】的计量单位【{}】与物料主单位【{}】相同,换算比率必须填 1,请修改换算比率或改选其他计量单位");
    // ========== ç‰©æ–™åˆ†ç±»ç›¸å…³ 1-050-051 ~ 1-050-100 ==========
    ErrorCode MDM_ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_051, "物料分类不存在");
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemModelService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
package cn.iocoder.yudao.module.mdm.service.item;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO;
import java.util.Collection;
import java.util.List;
/**
 * MDM ç‰©æ–™åž‹å· Service æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface MdmItemModelService {
    /**
     * åˆ›å»ºç‰©æ–™åž‹å·
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return åž‹å·ç¼–号
     */
    Long createItemModel(MdmItemModelSaveReqVO createReqVO);
    /**
     * æ›´æ–°ç‰©æ–™åž‹å·
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateItemModel(MdmItemModelSaveReqVO updateReqVO);
    /**
     * åˆ é™¤ç‰©æ–™åž‹å·
     *
     * @param id åž‹å·ç¼–号
     */
    void deleteItemModel(Long id);
    /**
     * å…¨é‡è¦†ç›–保存物料的型号列表(先删后插)
     *
     * @param itemId    ç‰©æ–™ç¼–号
     * @param modelList åž‹å·åˆ—表
     */
    void saveItemModelList(Long itemId, List<MdmItemModelSaveReqVO> modelList);
    /**
     * èŽ·å¾—ç‰©æ–™åž‹å·
     *
     * @param id åž‹å·ç¼–号
     * @return ç‰©æ–™åž‹å·
     */
    MdmItemModelDO getItemModel(Long id);
    /**
     * èŽ·å¾—ç‰©æ–™åž‹å·åˆ—è¡¨
     *
     * @param ids åž‹å·ç¼–号列表
     * @return ç‰©æ–™åž‹å·åˆ—表
     */
    List<MdmItemModelDO> getItemModelList(Collection<Long> ids);
    /**
     * èŽ·å¾—æŒ‡å®šç‰©æ–™çš„åž‹å·åˆ—è¡¨
     *
     * @param itemId ç‰©æ–™ç¼–号
     * @return ç‰©æ–™åž‹å·åˆ—表
     */
    List<MdmItemModelDO> getItemModelListByItemId(Long itemId);
    /**
     * èŽ·å¾—æŒ‡å®šå¤šä¸ªç‰©æ–™çš„åž‹å·åˆ—è¡¨
     *
     * @param itemIds ç‰©æ–™ç¼–号列表
     * @return ç‰©æ–™åž‹å·åˆ—表
     */
    List<MdmItemModelDO> getItemModelListByItemIds(Collection<Long> itemIds);
    /**
     * èŽ·å¾—ç‰©æ–™åž‹å·åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return ç‰©æ–™åž‹å·åˆ†é¡µ
     */
    PageResult<MdmItemModelDO> getItemModelPage(MdmItemModelPageReqVO pageReqVO);
    /**
     * æ ¡éªŒç‰©æ–™åž‹å·å­˜åœ¨
     *
     * @param id åž‹å·ç¼–号
     * @return ç‰©æ–™åž‹å·
     */
    MdmItemModelDO validateItemModelExists(Long id);
    /**
     * æ ¡éªŒç‰©æ–™åž‹å·å­˜åœ¨ä¸”启用
     *
     * @param id åž‹å·ç¼–号
     * @return ç‰©æ–™åž‹å·
     */
    MdmItemModelDO validateItemModelExistsAndEnable(Long id);
    /**
     * åˆ é™¤æŒ‡å®šç‰©æ–™çš„全部型号
     *
     * @param itemId ç‰©æ–™ç¼–号
     */
    void deleteItemModelListByItemId(Long itemId);
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemModelServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,173 @@
package cn.iocoder.yudao.module.mdm.service.item;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemModelSaveReqVO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemModelMapper;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mdm.enums.ErrorCodeConstants.*;
/**
 * MDM ç‰©æ–™åž‹å· Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class MdmItemModelServiceImpl implements MdmItemModelService {
    @Resource
    private MdmItemModelMapper itemModelMapper;
    @Resource
    private MdmItemService itemService;
    @Resource
    private MdmUnitMeasureService unitMeasureService;
    @Override
    public Long createItemModel(MdmItemModelSaveReqVO createReqVO) {
        validateItemModelSaveData(null, createReqVO);
        MdmItemModelDO itemModel = BeanUtils.toBean(createReqVO, MdmItemModelDO.class);
        if (itemModel.getStatus() == null) {
            itemModel.setStatus(CommonStatusEnum.ENABLE.getStatus());
        }
        itemModelMapper.insert(itemModel);
        return itemModel.getId();
    }
    @Override
    public void updateItemModel(MdmItemModelSaveReqVO updateReqVO) {
        validateItemModelExists(updateReqVO.getId());
        validateItemModelSaveData(updateReqVO.getId(), updateReqVO);
        itemModelMapper.updateById(BeanUtils.toBean(updateReqVO, MdmItemModelDO.class));
    }
    private void validateItemModelSaveData(Long id, MdmItemModelSaveReqVO reqVO) {
        // æ ¡éªŒç‰©æ–™å­˜åœ¨
        MdmItemDO item = itemService.validateItemExists(reqVO.getItemId());
        // æ ¡éªŒåž‹å·å•位存在
        unitMeasureService.validateUnitMeasureExists(reqVO.getUnitMeasureId());
        // æ ¡éªŒæ¢ç®—比率大于 0
        if (reqVO.getUnitMeasureRate() == null
                || reqVO.getUnitMeasureRate().compareTo(BigDecimal.ZERO) <= 0) {
            throw exception(MDM_ITEM_MODEL_RATE_INVALID, reqVO.getModel(), reqVO.getUnitMeasureRate());
        }
        // æ ¡éªŒåŒä¸€ç‰©æ–™ä¸‹åž‹å·åç§°å”¯ä¸€
        if (StrUtil.isNotBlank(reqVO.getModel())) {
            MdmItemModelDO exists = itemModelMapper.selectByItemIdAndModel(reqVO.getItemId(), reqVO.getModel());
            if (exists != null && ObjUtil.notEqual(exists.getId(), id)) {
                throw exception(MDM_ITEM_MODEL_NAME_DUPLICATE, reqVO.getModel());
            }
        }
        // åž‹å·å•位与物料主单位一致时,比率必须为 1(否则换算无意义且易误配)
        if (ObjUtil.equal(item.getUnitMeasureId(), reqVO.getUnitMeasureId())
                && reqVO.getUnitMeasureRate().compareTo(BigDecimal.ONE) != 0) {
            throw exception(MDM_ITEM_MODEL_RATE_MUST_BE_ONE, reqVO.getModel(),
                    unitName(reqVO.getUnitMeasureId()), unitName(item.getUnitMeasureId()));
        }
    }
    private String unitName(Long unitMeasureId) {
        MdmUnitMeasureDO unit = unitMeasureService.getUnitMeasure(unitMeasureId);
        return unit != null ? unit.getName() : "ID=" + unitMeasureId;
    }
    @Override
    public void deleteItemModel(Long id) {
        validateItemModelExists(id);
        itemModelMapper.deleteById(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveItemModelList(Long itemId, List<MdmItemModelSaveReqVO> modelList) {
        itemService.validateItemExists(itemId);
        // ç¬¬ä¸€æ­¥ï¼Œåˆ é™¤æ—§çš„型号
        itemModelMapper.deleteByItemId(itemId);
        if (CollUtil.isEmpty(modelList)) {
            return;
        }
        // ç¬¬äºŒæ­¥ï¼Œæ’入新的型号
        for (MdmItemModelSaveReqVO reqVO : modelList) {
            reqVO.setItemId(itemId);
            reqVO.setId(null);
            createItemModel(reqVO);
        }
    }
    @Override
    public MdmItemModelDO getItemModel(Long id) {
        return itemModelMapper.selectById(id);
    }
    @Override
    public List<MdmItemModelDO> getItemModelList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return itemModelMapper.selectBatchIds(ids);
    }
    @Override
    public List<MdmItemModelDO> getItemModelListByItemId(Long itemId) {
        return itemModelMapper.selectListByItemId(itemId);
    }
    @Override
    public List<MdmItemModelDO> getItemModelListByItemIds(Collection<Long> itemIds) {
        if (CollUtil.isEmpty(itemIds)) {
            return Collections.emptyList();
        }
        return itemModelMapper.selectListByItemIds(itemIds);
    }
    @Override
    public PageResult<MdmItemModelDO> getItemModelPage(MdmItemModelPageReqVO pageReqVO) {
        return itemModelMapper.selectPage(pageReqVO);
    }
    @Override
    public MdmItemModelDO validateItemModelExists(Long id) {
        MdmItemModelDO itemModel = itemModelMapper.selectById(id);
        if (itemModel == null) {
            throw exception(MDM_ITEM_MODEL_NOT_EXISTS);
        }
        return itemModel;
    }
    @Override
    public MdmItemModelDO validateItemModelExistsAndEnable(Long id) {
        MdmItemModelDO itemModel = validateItemModelExists(id);
        if (CommonStatusEnum.isDisable(itemModel.getStatus())) {
            throw exception(MDM_ITEM_MODEL_IS_DISABLE);
        }
        return itemModel;
    }
    @Override
    public void deleteItemModelListByItemId(Long itemId) {
        itemModelMapper.deleteByItemId(itemId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java
@@ -79,6 +79,15 @@
        return success(true);
    }
    @DeleteMapping("/delete-list")
    @Operation(summary = "批量删除设备")
    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
    @PreAuthorize("@ss.hasPermission('mes:dv-machinery:delete')")
    public CommonResult<Boolean> deleteMachineryList(@RequestParam("ids") List<Long> ids) {
        machineryService.deleteMachineryList(ids);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得设备")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptLineController.java
@@ -9,6 +9,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.line.MesWmItemReceiptLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.line.MesWmItemReceiptLineSaveReqVO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
@@ -28,8 +30,10 @@
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -45,6 +49,8 @@
    private MesWmItemReceiptLineService itemReceiptLineService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
@@ -104,18 +110,26 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmItemReceiptLineDO::getItemId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(
                convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)).getCheckedData()
                .stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Set<Long> unitMeasureIds = new HashSet<>(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId));
        unitMeasureIds.addAll(convertSet(list, MesWmItemReceiptLineDO::getEnterUnitMeasureId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(unitMeasureIds)
                .getCheckedData().stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Map<Long, MesWmItemReceiptDO> receiptMap = itemReceiptService.getItemReceiptMap(
                convertSet(list, MesWmItemReceiptLineDO::getReceiptId));
        Map<Long, MdmItemModelRespDTO> itemModelMap = mdmItemModelApi.getItemModelMap(
                convertSet(list, MesWmItemReceiptLineDO::getItemModelId));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmItemReceiptLineRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification());
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification())
                        .setUnitMeasureId(item.getUnitMeasureId());
                MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                        unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            });
            MapUtils.findAndThen(unitMeasureMap, vo.getEnterUnitMeasureId(),
                    unitMeasure -> vo.setEnterUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemModelMap, vo.getItemModelId(),
                    itemModel -> vo.setItemModel(itemModel.getModel()));
            MapUtils.findAndThen(receiptMap, vo.getReceiptId(), receipt ->
                    vo.setReceiptCode(receipt.getCode()).setPurchaseOrderCode(receipt.getPurchaseOrderCode()));
        });
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/line/MesWmItemReceiptLineRespVO.java
@@ -40,10 +40,28 @@
    @Schema(description = "规格型号", example = "10mm*100mm")
    private String specification;
    @Schema(description = "计量单位名称", example = "千克")
    @Schema(description = "主计量单位编号(物料主单位)", example = "1")
    private Long unitMeasureId;
    @Schema(description = "主计量单位名称(物料主单位)", example = "ç±³")
    private String unitMeasureName;
    @Schema(description = "入库数量", example = "500.00")
    @Schema(description = "物料型号编号", example = "1")
    private Long itemModelId;
    @Schema(description = "物料型号名称", example = "4ç±³/æ ¹")
    private String itemModel;
    @Schema(description = "录入单位编号", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入单位名称", example = "æ ¹")
    private String enterUnitMeasureName;
    @Schema(description = "录入数量(按录入单位计)", example = "10.00")
    private BigDecimal enterQuantity;
    @Schema(description = "入库数量(物料主单位口径)", example = "40.00")
    private BigDecimal receivedQuantity;
    @Schema(description = "批次编号", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/vo/line/MesWmItemReceiptLineSaveReqVO.java
@@ -29,8 +29,17 @@
    @NotNull(message = "物料编号不能为空")
    private Long itemId;
    @Schema(description = "入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00")
    @NotNull(message = "入库数量不能为空")
    @Schema(description = "物料型号编号(选择型号后按其单位与换算比率换算为主单位数量)", example = "1")
    private Long itemModelId;
    @Schema(description = "录入单位编号(不传或传物料主单位时按主单位录入)", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入数量(按录入单位计,系统据此换算出入库数量)", example = "10.00")
    @DecimalMin(value = "0", inclusive = false, message = "录入数量必须大于 0")
    private BigDecimal enterQuantity;
    @Schema(description = "入库数量(物料主单位口径;传了录入数量时由系统换算,无需填写)", example = "40.00")
    @DecimalMin(value = "0", inclusive = false, message = "入库数量必须大于 0")
    private BigDecimal receivedQuantity;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/MesWmMiscIssueLineController.java
@@ -9,6 +9,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.line.MesWmMiscIssueLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.line.MesWmMiscIssueLineSaveReqVO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueLineDO;
@@ -32,8 +34,10 @@
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -49,6 +53,8 @@
    private MesWmMiscIssueLineService miscIssueLineService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
@@ -112,9 +118,12 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmMiscIssueLineDO::getItemId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(
                convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)).getCheckedData()
                .stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Set<Long> unitMeasureIds = new HashSet<>(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId));
        unitMeasureIds.addAll(convertSet(list, MesWmMiscIssueLineDO::getEnterUnitMeasureId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(unitMeasureIds)
                .getCheckedData().stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Map<Long, MdmItemModelRespDTO> itemModelMap = mdmItemModelApi.getItemModelMap(
                convertSet(list, MesWmMiscIssueLineDO::getItemModelId));
        Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(
                convertSet(list, MesWmMiscIssueLineDO::getWarehouseId));
        Map<Long, MesWmWarehouseLocationDO> locationMap = warehouseLocationService.getWarehouseLocationMap(
@@ -125,10 +134,15 @@
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmMiscIssueLineRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification());
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification())
                        .setUnitMeasureId(item.getUnitMeasureId());
                MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                        unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            });
            MapUtils.findAndThen(unitMeasureMap, vo.getEnterUnitMeasureId(),
                    unitMeasure -> vo.setEnterUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemModelMap, vo.getItemModelId(),
                    itemModel -> vo.setItemModel(itemModel.getModel()));
            MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(), warehouse -> vo.setWarehouseName(warehouse.getName()));
            MapUtils.findAndThen(locationMap, vo.getLocationId(), location -> vo.setLocationName(location.getName()));
            MapUtils.findAndThen(areaMap, vo.getAreaId(), area -> vo.setAreaName(area.getName()));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/line/MesWmMiscIssueLineRespVO.java
@@ -34,10 +34,28 @@
    @Schema(description = "规格型号", example = "10mm*100mm")
    private String specification;
    @Schema(description = "计量单位名称", example = "千克")
    @Schema(description = "主计量单位编号(物料主单位)", example = "1")
    private Long unitMeasureId;
    @Schema(description = "主计量单位名称(物料主单位)", example = "千克")
    private String unitMeasureName;
    @Schema(description = "出库数量", example = "100.00")
    @Schema(description = "物料型号编号", example = "1")
    private Long itemModelId;
    @Schema(description = "物料型号名称", example = "4ç±³/æ ¹")
    private String itemModel;
    @Schema(description = "录入单位编号", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入单位名称", example = "æ ¹")
    private String enterUnitMeasureName;
    @Schema(description = "录入数量(按录入单位计)", example = "10.00")
    private BigDecimal enterQuantity;
    @Schema(description = "出库数量(物料主单位口径)", example = "100.00")
    private BigDecimal quantity;
    @Schema(description = "批次编号", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/vo/line/MesWmMiscIssueLineSaveReqVO.java
@@ -28,8 +28,17 @@
    @NotNull(message = "物料编号不能为空")
    private Long itemId;
    @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
    @NotNull(message = "出库数量不能为空")
    @Schema(description = "物料型号编号(选择型号后按其单位与换算比率换算为主单位数量)", example = "1")
    private Long itemModelId;
    @Schema(description = "录入单位编号(不传或传物料主单位时按主单位录入)", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入数量(按录入单位计,系统据此换算出入库数量)", example = "10.00")
    @DecimalMin(value = "0", inclusive = false, message = "录入数量必须大于 0")
    private BigDecimal enterQuantity;
    @Schema(description = "出库数量(物料主单位口径;传了录入数量时由系统换算,无需填写)", example = "100.00")
    @DecimalMin(value = "0.01", message = "出库数量必须大于0")
    private BigDecimal quantity;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueLineController.java
@@ -9,6 +9,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.line.MesWmOutsourceIssueLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.line.MesWmOutsourceIssueLineSaveReqVO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
@@ -28,6 +30,7 @@
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -47,6 +50,8 @@
    private MesWmOutsourceIssueLineService outsourceIssueLineService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
@@ -116,9 +121,12 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmOutsourceIssueLineDO::getItemId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(
                convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)).getCheckedData()
                .stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Set<Long> unitMeasureIds = new HashSet<>(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId));
        unitMeasureIds.addAll(convertSet(list, MesWmOutsourceIssueLineDO::getEnterUnitMeasureId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(unitMeasureIds)
                .getCheckedData().stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Map<Long, MdmItemModelRespDTO> itemModelMap = mdmItemModelApi.getItemModelMap(
                convertSet(list, MesWmOutsourceIssueLineDO::getItemModelId));
        // 1.2 èŽ·å¾—æ‰¹æ¬¡æ•°æ®
        Set<Long> batchIds = convertSet(list, MesWmOutsourceIssueLineDO::getBatchId);
        batchIds.remove(null);
@@ -127,10 +135,15 @@
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmOutsourceIssueLineRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification());
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification())
                        .setUnitMeasureId(item.getUnitMeasureId());
                MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                        unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            });
            MapUtils.findAndThen(unitMeasureMap, vo.getEnterUnitMeasureId(),
                    unitMeasure -> vo.setEnterUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemModelMap, vo.getItemModelId(),
                    itemModel -> vo.setItemModel(itemModel.getModel()));
            MapUtils.findAndThen(batchMap, vo.getBatchId(), batch -> vo.setBatchCode(batch.getCode()));
        });
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/line/MesWmOutsourceIssueLineRespVO.java
@@ -28,10 +28,28 @@
    @Schema(description = "规格型号", example = "10mm*100mm")
    private String specification;
    @Schema(description = "计量单位名称", example = "千克")
    @Schema(description = "主计量单位编号(物料主单位)", example = "1")
    private Long unitMeasureId;
    @Schema(description = "主计量单位名称(物料主单位)", example = "千克")
    private String unitMeasureName;
    @Schema(description = "发料数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00")
    @Schema(description = "物料型号编号", example = "1")
    private Long itemModelId;
    @Schema(description = "物料型号名称", example = "4ç±³/æ ¹")
    private String itemModel;
    @Schema(description = "录入单位编号", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入单位名称", example = "æ ¹")
    private String enterUnitMeasureName;
    @Schema(description = "录入数量(按录入单位计)", example = "10.00")
    private BigDecimal enterQuantity;
    @Schema(description = "发料数量(物料主单位口径)", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00")
    private BigDecimal quantity;
    @Schema(description = "库存ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/vo/line/MesWmOutsourceIssueLineSaveReqVO.java
@@ -22,8 +22,17 @@
    @NotNull(message = "物料ID不能为空")
    private Long itemId;
    @Schema(description = "发料数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00")
    @NotNull(message = "发料数量不能为空")
    @Schema(description = "物料型号编号(选择型号后按其单位与换算比率换算为主单位数量)", example = "1")
    private Long itemModelId;
    @Schema(description = "录入单位编号(不传或传物料主单位时按主单位录入)", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入数量(按录入单位计,系统据此换算出发料数量)", example = "10.00")
    @DecimalMin(value = "0", inclusive = false, message = "录入数量必须大于 0")
    private BigDecimal enterQuantity;
    @Schema(description = "发料数量(物料主单位口径;传了录入数量时由系统换算,无需填写)", example = "500.00")
    @DecimalMin(value = "0.01", message = "发料数量必须大于 0")
    private BigDecimal quantity;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/MesWmProductIssueLineController.java
@@ -10,6 +10,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineReturnableRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.line.MesWmProductIssueLineSaveReqVO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
@@ -34,9 +36,11 @@
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -59,6 +63,8 @@
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    private MesProTaskService taskService;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @PostMapping("/create")
    @Operation(summary = "创建领料出库单行")
@@ -174,17 +180,26 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MesMdItemDO> itemMap = mdmItemService.getItemMap(
                convertSet(list, MesWmProductIssueLineDO::getItemId));
        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
        // ç‰©æ–™ä¸»å•位与录入单位都要解析名称
        Set<Long> unitMeasureIds = new HashSet<>(convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
        unitMeasureIds.addAll(convertSet(list, MesWmProductIssueLineDO::getEnterUnitMeasureId));
        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(unitMeasureIds);
        Map<Long, MdmItemModelRespDTO> itemModelMap = mdmItemModelApi.getItemModelMap(
                convertSet(list, MesWmProductIssueLineDO::getItemModelId));
        Map<Long, MesProTaskDO> taskMap = taskService.getTaskMap(
                convertSet(list, MesWmProductIssueLineDO::getTaskId));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmProductIssueLineRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification()).setMdmItemId(item.getMdmItemId());
                vo.setUnitMeasureId(item.getUnitMeasureId());
                MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                        unit -> vo.setUnitMeasureName(unit.getName()));
            });
            MapUtils.findAndThen(unitMeasureMap, vo.getEnterUnitMeasureId(),
                    unit -> vo.setEnterUnitMeasureName(unit.getName()));
            MapUtils.findAndThen(itemModelMap, vo.getItemModelId(),
                    itemModel -> vo.setItemModel(itemModel.getModel()));
            MapUtils.findAndThen(taskMap, vo.getTaskId(), task ->
                    vo.setTaskCode(task.getCode()).setTaskName(task.getName()));
        });
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/line/MesWmProductIssueLineRespVO.java
@@ -37,10 +37,28 @@
    @Schema(description = "规格型号", example = "10mm*100mm")
    private String specification;
    @Schema(description = "计量单位名称", example = "千克")
    @Schema(description = "主计量单位编号(物料主单位)", example = "1")
    private Long unitMeasureId;
    @Schema(description = "主计量单位名称", example = "千克")
    private String unitMeasureName;
    @Schema(description = "领料数量", example = "100.00")
    @Schema(description = "物料型号编号", example = "1")
    private Long itemModelId;
    @Schema(description = "物料型号名称", example = "4ç±³/æ ¹")
    private String itemModel;
    @Schema(description = "录入单位编号", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入单位名称", example = "æ ¹")
    private String enterUnitMeasureName;
    @Schema(description = "录入数量(按录入单位计)", example = "10.00")
    private BigDecimal enterQuantity;
    @Schema(description = "领料数量(物料主单位口径)", example = "100.00")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/vo/line/MesWmProductIssueLineSaveReqVO.java
@@ -25,9 +25,18 @@
    @NotNull(message = "物料ID不能为空")
    private Long itemId;
    @Schema(description = "领料数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
    @NotNull(message = "领料数量不能为空")
    @DecimalMin(value = "0", inclusive = false, message = "领料数量必须大于 0")
    @Schema(description = "物料型号编号(选择型号后按其单位与换算比率换算为主单位数量)", example = "1")
    private Long itemModelId;
    @Schema(description = "录入单位编号(不传或传物料主单位时按主单位录入)", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入数量(按录入单位计,系统据此换算出领料数量)", example = "10.00")
    @DecimalMin(value = "0", inclusive = false, message = "录入数量必须大于 0")
    private BigDecimal enterQuantity;
    @Schema(description = "领料数量(物料主单位口径;传了录入数量时由系统换算,无需填写)", example = "100.00")
    @DecimalMin(value = "0.01", message = "领料数量必须大于 0")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/MesWmProductSalesLineController.java
@@ -9,6 +9,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -28,8 +30,10 @@
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -45,6 +49,8 @@
    private MesWmProductSalesLineService productSalesLineService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
@@ -103,17 +109,25 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmProductSalesLineDO::getItemId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(
                        convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)).getCheckedData()
                .stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        // ç‰©æ–™ä¸»å•位与录入单位都要解析名称
        Set<Long> unitMeasureIds = new HashSet<>(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId));
        unitMeasureIds.addAll(convertSet(list, MesWmProductSalesLineDO::getEnterUnitMeasureId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(unitMeasureIds)
                .getCheckedData().stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Map<Long, MdmItemModelRespDTO> itemModelMap = mdmItemModelApi.getItemModelMap(
                convertSet(list, MesWmProductSalesLineDO::getItemModelId));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmProductSalesLineRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName()).setSpecification(item.getSpecification());
                vo.setUnitMeasureId(item.getUnitMeasureId());
                MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                        unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            });
            MapUtils.findAndThen(unitMeasureMap, vo.getEnterUnitMeasureId(),
                    unitMeasure -> vo.setEnterUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemModelMap, vo.getItemModelId(),
                    itemModel -> vo.setItemModel(itemModel.getModel()));
        });
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineRespVO.java
@@ -31,10 +31,28 @@
    @Schema(description = "规格型号", example = "100g")
    private String specification;
    @Schema(description = "单位名称", example = "个")
    @Schema(description = "主计量单位编号(物料主单位)", example = "1")
    private Long unitMeasureId;
    @Schema(description = "主计量单位名称", example = "个")
    private String unitMeasureName;
    @Schema(description = "出库数量", example = "100")
    @Schema(description = "物料型号编号", example = "1")
    private Long itemModelId;
    @Schema(description = "物料型号名称", example = "4ç±³/æ ¹")
    private String itemModel;
    @Schema(description = "录入单位编号", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入单位名称", example = "æ ¹")
    private String enterUnitMeasureName;
    @Schema(description = "录入数量(按录入单位计)", example = "10.00")
    private BigDecimal enterQuantity;
    @Schema(description = "出库数量(物料主单位口径)", example = "100")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -24,8 +25,18 @@
    @NotNull(message = "物料不能为空")
    private Long itemId;
    @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
    @NotNull(message = "出库数量不能为空")
    @Schema(description = "物料型号编号(选择型号后按其单位与换算比率换算为主单位数量)", example = "1")
    private Long itemModelId;
    @Schema(description = "录入单位编号(不传或传物料主单位时按主单位录入)", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入数量(按录入单位计,系统据此换算出库数量)", example = "10.00")
    @DecimalMin(value = "0", inclusive = false, message = "录入数量必须大于 0")
    private BigDecimal enterQuantity;
    @Schema(description = "出库数量(物料主单位口径;传了录入数量时由系统换算,无需填写)", example = "100")
    @DecimalMin(value = "0.01", message = "出库数量必须大于 0")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorLineController.java
@@ -9,6 +9,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.line.MesWmReturnVendorLineRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.line.MesWmReturnVendorLineSaveReqVO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorLineDO;
@@ -26,8 +28,10 @@
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -43,6 +47,8 @@
    private MesWmReturnVendorLineService returnVendorLineService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
@@ -110,18 +116,27 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmReturnVendorLineDO::getItemId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(
                convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)).getCheckedData()
                .stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        // ç‰©æ–™ä¸»å•位与录入单位都要解析名称
        Set<Long> unitMeasureIds = new HashSet<>(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId));
        unitMeasureIds.addAll(convertSet(list, MesWmReturnVendorLineDO::getEnterUnitMeasureId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureList(unitMeasureIds)
                .getCheckedData().stream().collect(Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e));
        Map<Long, MdmItemModelRespDTO> itemModelMap = mdmItemModelApi.getItemModelMap(
                convertSet(list, MesWmReturnVendorLineDO::getItemModelId));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmReturnVendorLineRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode());
                vo.setItemName(item.getName());
                vo.setSpecification(item.getSpecification());
                vo.setUnitMeasureId(item.getUnitMeasureId());
                MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
                        unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
            });
            MapUtils.findAndThen(unitMeasureMap, vo.getEnterUnitMeasureId(),
                    unitMeasure -> vo.setEnterUnitMeasureName(unitMeasure.getName()));
            MapUtils.findAndThen(itemModelMap, vo.getItemModelId(),
                    itemModel -> vo.setItemModel(itemModel.getModel()));
        });
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/line/MesWmReturnVendorLineRespVO.java
@@ -28,10 +28,28 @@
    @Schema(description = "规格型号", example = "10mm*100mm")
    private String specification;
    @Schema(description = "计量单位名称", example = "千克")
    @Schema(description = "主计量单位编号(物料主单位)", example = "1")
    private Long unitMeasureId;
    @Schema(description = "主计量单位名称", example = "千克")
    private String unitMeasureName;
    @Schema(description = "退货数量", example = "100.00")
    @Schema(description = "物料型号编号", example = "1")
    private Long itemModelId;
    @Schema(description = "物料型号名称", example = "4ç±³/æ ¹")
    private String itemModel;
    @Schema(description = "录入单位编号", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入单位名称", example = "æ ¹")
    private String enterUnitMeasureName;
    @Schema(description = "录入数量(按录入单位计)", example = "10.00")
    private BigDecimal enterQuantity;
    @Schema(description = "退货数量(物料主单位口径)", example = "100.00")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/line/MesWmReturnVendorLineSaveReqVO.java
@@ -22,9 +22,18 @@
    @NotNull(message = "物料ID不能为空")
    private Long itemId;
    @Schema(description = "退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
    @NotNull(message = "退货数量不能为空")
    @DecimalMin(value = "0", inclusive = false, message = "退货数量必须大于 0")
    @Schema(description = "物料型号编号(选择型号后按其单位与换算比率换算为主单位数量)", example = "1")
    private Long itemModelId;
    @Schema(description = "录入单位编号(不传或传物料主单位时按主单位录入)", example = "7")
    private Long enterUnitMeasureId;
    @Schema(description = "录入数量(按录入单位计,系统据此换算出库数量)", example = "10.00")
    @DecimalMin(value = "0", inclusive = false, message = "录入数量必须大于 0")
    private BigDecimal enterQuantity;
    @Schema(description = "退货数量(物料主单位口径;传了录入数量时由系统换算,无需填写)", example = "100.00")
    @DecimalMin(value = "0.01", message = "退货数量必须大于 0")
    private BigDecimal quantity;
    @Schema(description = "批次ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/itemreceipt/MesWmItemReceiptLineDO.java
@@ -55,7 +55,30 @@
     */
    private Long itemId;
    /**
     * å…¥åº“数量
     * ç‰©æ–™åž‹å·ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO#getId()}
     */
    private Long itemModelId;
    /**
     * ç‰©æ–™åž‹å·åç§°
     */
    private String itemModel;
    /**
     * å½•入单位编号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO#getId()}
     * ä¸ºç©ºæˆ–等于物料主单位时,说明本次按主单位录入
     */
    private Long enterUnitMeasureId;
    /**
     * å½•入数量(按录入单位计)
     *
     * æŒ‰ä¸»å•位录入时与 {@link #receivedQuantity} ç›¸åŒ
     */
    private BigDecimal enterQuantity;
    /**
     * å…¥åº“数量(物料主单位口径,库存与流水均以该数量为准)
     */
    private BigDecimal receivedQuantity;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/miscissue/MesWmMiscIssueLineDO.java
@@ -52,7 +52,26 @@
     */
    private Long itemId;
    /**
     * å‡ºåº“数量
     * ç‰©æ–™åž‹å·ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO#getId()}
     */
    private Long itemModelId;
    /**
     * å½•入单位编号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO#getId()}
     * ä¸ºç©ºæˆ–等于物料主单位时,说明本次按主单位录入
     */
    private Long enterUnitMeasureId;
    /**
     * å½•入数量(按录入单位计)
     *
     * æŒ‰ä¸»å•位录入时与 {@link #quantity} ç›¸åŒ
     */
    private BigDecimal enterQuantity;
    /**
     * å‡ºåº“数量(物料主单位口径,库存与流水均以该数量为准)
     */
    private BigDecimal quantity;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/outsourceissue/MesWmOutsourceIssueLineDO.java
@@ -46,7 +46,26 @@
     */
    private Long itemId;
    /**
     * å‘料数量
     * ç‰©æ–™åž‹å·ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO#getId()}
     */
    private Long itemModelId;
    /**
     * å½•入单位编号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO#getId()}
     * ä¸ºç©ºæˆ–等于物料主单位时,说明本次按主单位录入
     */
    private Long enterUnitMeasureId;
    /**
     * å½•入数量(按录入单位计)
     *
     * æŒ‰ä¸»å•位录入时与 {@link #quantity} ç›¸åŒ
     */
    private BigDecimal enterQuantity;
    /**
     * å‘料数量(物料主单位口径,库存与流水均以该数量为准)
     */
    private BigDecimal quantity;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productissue/MesWmProductIssueLineDO.java
@@ -47,7 +47,26 @@
     */
    private Long itemId;
    /**
     * é¢†æ–™æ•°é‡
     * ç‰©æ–™åž‹å·ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO#getId()}
     */
    private Long itemModelId;
    /**
     * å½•入单位编号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO#getId()}
     * ä¸ºç©ºæˆ–等于物料主单位时,说明本次按主单位录入
     */
    private Long enterUnitMeasureId;
    /**
     * å½•入数量(按录入单位计)
     *
     * æŒ‰ä¸»å•位录入时与 {@link #quantity} ç›¸åŒ
     */
    private BigDecimal enterQuantity;
    /**
     * é¢†æ–™æ•°é‡ï¼ˆç‰©æ–™ä¸»å•位口径,库存与流水均以该数量为准)
     */
    private BigDecimal quantity;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java
@@ -58,7 +58,26 @@
     */
    private Long itemId;
    /**
     * å‡ºåº“数量
     * ç‰©æ–™åž‹å·ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO#getId()}
     */
    private Long itemModelId;
    /**
     * å½•入单位编号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO#getId()}
     * ä¸ºç©ºæˆ–等于物料主单位时,说明本次按主单位录入
     */
    private Long enterUnitMeasureId;
    /**
     * å½•入数量(按录入单位计)
     *
     * æŒ‰ä¸»å•位录入时与 {@link #quantity} ç›¸åŒ
     */
    private BigDecimal enterQuantity;
    /**
     * å‡ºåº“数量(物料主单位口径,库存与流水均以该数量为准)
     */
    private BigDecimal quantity;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorLineDO.java
@@ -41,7 +41,26 @@
     */
    private Long itemId;
    /**
     * é€€è´§æ•°é‡
     * ç‰©æ–™åž‹å·ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemModelDO#getId()}
     */
    private Long itemModelId;
    /**
     * å½•入单位编号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO#getId()}
     * ä¸ºç©ºæˆ–等于物料主单位时,说明本次按主单位录入
     */
    private Long enterUnitMeasureId;
    /**
     * å½•入数量(按录入单位计)
     *
     * æŒ‰ä¸»å•位录入时与 {@link #quantity} ç›¸åŒ
     */
    private BigDecimal enterQuantity;
    /**
     * é€€è´§æ•°é‡ï¼ˆç‰©æ–™ä¸»å•位口径,库存与流水均以该数量为准)
     */
    private BigDecimal quantity;
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -395,6 +395,7 @@
    ErrorCode QC_IQC_CHECK_RESULT_EMPTY = new ErrorCode(1_040_603_007, "完成检验单前,检测结果必须填写");
    ErrorCode QC_IQC_TEMPLATE_IS_DISABLE = new ErrorCode(1_040_603_009, "所选质检方案【{}】已禁用,请重新选择已启用的质检方案");
    ErrorCode QC_IQC_TEMPLATE_TYPE_NOT_MATCH = new ErrorCode(1_040_603_010, "所选质检方案【{}】的检测种类不包含来料检验,请重新选择检测种类包含来料检验的质检方案");
    ErrorCode QC_IQC_NO_ENABLED_TEMPLATE = new ErrorCode(1_040_603_011, "系统中没有启用的来料检验(IQC)质检方案,无法自动生成检验单:请先在【质量管理-质检方案】中新建或启用一个检测种类包含来料检验的方案");
    // ========== MES è´¨é‡ç®¡ç†-来料检验行(1-040-603-100) ==========
    ErrorCode QC_IQC_LINE_NOT_EXISTS = new ErrorCode(1_040_603_100, "来料检验行不存在");
@@ -514,6 +515,18 @@
    ErrorCode WM_MATERIAL_STOCK_IMPORT_OPERATION_INVALID = new ErrorCode(1_040_703_027, "操作类型({})不支持");
    ErrorCode WM_MATERIAL_STOCK_IMPORT_STOCK_NOT_EXISTS = new ErrorCode(1_040_703_028, "库存记录不存在,无法执行该操作");
    // ---- ä¸»è¾…单位换算(结合物料型号) ----
    ErrorCode WM_ITEM_UNIT_CONVERT_NOT_CONFIGURED = new ErrorCode(1_040_703_029,
            "物料【{}-{}】没有配置计量单位【{}】到主单位【{}】的换算比率,无法换算,请先到【基础数据-物料主数据-型号】为该物料新增型号并填写换算比率");
    ErrorCode WM_ITEM_UNIT_CONVERT_MODEL_UNIT_MISMATCH = new ErrorCode(1_040_703_030,
            "所选型号【{}】的计量单位是【{}】,与本次录入单位【{}】不一致,请重新选择型号或改回型号对应的计量单位");
    ErrorCode WM_ITEM_UNIT_CONVERT_QUANTITY_REQUIRED = new ErrorCode(1_040_703_031,
            "计量单位【{}】不是物料【{}-{}】的主单位,按该单位录入时录入数量不能为空,请填写录入数量或改用主单位【{}】");
    ErrorCode WM_ITEM_UNIT_CONVERT_MODEL_REQUIRED = new ErrorCode(1_040_703_032,
            "计量单位【{}】的换算比率由型号决定,请先选择物料【{}-{}】的型号后再录入数量");
    ErrorCode WM_ITEM_UNIT_CONVERT_RATE_INVALID = new ErrorCode(1_040_703_033,
            "型号【{}】的换算比率为【{}】不合理,1 ä¸ªåž‹å·å•位必须对应大于 0 çš„主单位数量,请到【基础数据-物料主数据-型号】修正该型号的换算比率");
    // ========== MES ä»“库管理-到货通知单(1-040-704-000) ==========
    ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在");
    ErrorCode WM_ARRIVAL_NOTICE_CODE_DUPLICATE = new ErrorCode(1_040_704_001, "到货通知单编码已存在");
@@ -543,6 +556,8 @@
            "入库单关联了到货通知单,必须选择到货通知单行");
    ErrorCode WM_ITEM_RECEIPT_LINE_ARRIVAL_NOTICE_LINE_NOT_ALLOWED = new ErrorCode(1_040_705_102,
            "入库单未关联到货通知单,不能选择到货通知单行");
    ErrorCode WM_ITEM_RECEIPT_LINE_QUANTITY_REQUIRED = new ErrorCode(1_040_705_103,
            "请填写入库数量,或选择录入单位并填写录入数量后由系统自动换算");
    ErrorCode WM_ITEM_RECEIPT_DETAIL_NOT_EXISTS = new ErrorCode(1_040_705_200, "采购入库明细不存在");
    ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_705_202, "上架明细总数量不能超过行入库数量");
    ErrorCode WM_ITEM_RECEIPT_BATCH_REQUIRED = new ErrorCode(1_040_705_203, "物料【{}-{}】已开启批次管理,上架明细的批次号不能为空");
@@ -563,6 +578,8 @@
    ErrorCode WM_OUTSOURCE_ISSUE_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_707_007, "已完成或已取消的发料单不允许取消");
    ErrorCode WM_OUTSOURCE_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_707_100, "外协发料单行不存在");
    ErrorCode WM_OUTSOURCE_ISSUE_LINE_ITEM_NOT_IN_BOM = new ErrorCode(1_040_707_101, "发料单行对应的物料不在当前工单的 BOM åˆ—表中");
    ErrorCode WM_OUTSOURCE_ISSUE_LINE_QUANTITY_REQUIRED = new ErrorCode(1_040_707_102,
            "请填写出库数量,或选择录入单位并填写录入数量后由系统自动换算");
    ErrorCode WM_OUTSOURCE_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_707_200, "外协发料单明细不存在");
    ErrorCode WM_OUTSOURCE_ISSUE_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_707_201, "拣货明细不属于指定的外协发料单");
    ErrorCode WM_OUTSOURCE_ISSUE_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_707_202, "拣货明细的物料与外协发料单行的物料不一致");
@@ -579,6 +596,8 @@
    ErrorCode WM_PRODUCT_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_708_100, "生产领料出库单行不存在");
    ErrorCode WM_PRODUCT_ISSUE_LINE_ITEM_NOT_IN_BOM = new ErrorCode(1_040_708_101, "当前物料不在生产工单的 BOM ç‰©æ–™æ¸…单中");
    ErrorCode WM_PRODUCT_ISSUE_CODE_DUPLICATE = new ErrorCode(1_040_708_102, "领料出库单编码已存在");
    ErrorCode WM_PRODUCT_ISSUE_LINE_QUANTITY_REQUIRED = new ErrorCode(1_040_708_103,
            "请填写出库数量,或选择录入单位并填写录入数量后由系统自动换算");
    ErrorCode WM_PRODUCT_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_708_200, "生产领料出库单明细不存在");
    ErrorCode WM_PRODUCT_ISSUE_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_708_201, "拣货明细的物料与领料单行的物料不一致");
    ErrorCode WM_PRODUCT_ISSUE_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_708_202, "拣货明细不属于指定的领料出库单");
@@ -639,6 +658,8 @@
    ErrorCode WM_RETURN_VENDOR_NO_DETAIL = new ErrorCode(1_040_711_006, "供应商退货单没有拣货明细,无法执行退货");
    ErrorCode WM_RETURN_VENDOR_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_711_007, "拣货明细的物料与退货单行的物料不一致");
    ErrorCode WM_RETURN_VENDOR_LINE_NOT_EXISTS = new ErrorCode(1_040_711_100, "供应商退货单行不存在");
    ErrorCode WM_RETURN_VENDOR_LINE_QUANTITY_REQUIRED = new ErrorCode(1_040_711_101,
            "请填写退货数量,或选择录入单位并填写录入数量后由系统自动换算");
    ErrorCode WM_RETURN_VENDOR_DETAIL_NOT_EXISTS = new ErrorCode(1_040_711_200, "供应商退货单明细不存在");
    ErrorCode WM_RETURN_VENDOR_DETAIL_QUANTITY_INVALID = new ErrorCode(1_040_711_201, "退货明细数量必须大于 0");
    ErrorCode WM_RETURN_VENDOR_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_711_202, "拣货明细不属于指定的供应商退货单");
@@ -735,6 +756,8 @@
            "出库行批次号与发货通知单行批次号不一致");
    ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_OQC_MISMATCH = new ErrorCode(1_040_714_033,
            "出库行 OQC æ£€éªŒæ ‡è¯†ä¸Žå‘货通知单行不一致");
    ErrorCode WM_PRODUCT_SALES_LINE_QUANTITY_REQUIRED = new ErrorCode(1_040_714_034,
            "请填写出库数量,或选择录入单位并填写录入数量后由系统自动换算");
    // ========== MES ä»“库管理-杂项出库单(1-040-715-000) ==========
    ErrorCode WM_MISC_ISSUE_NOT_EXISTS = new ErrorCode(1_040_715_000, "杂项出库单不存在");
@@ -744,6 +767,8 @@
    ErrorCode WM_MISC_ISSUE_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_715_004, "杂项出库单已完成或已取消,无法取消");
    ErrorCode WM_MISC_ISSUE_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_715_005, "杂项出库单行数量与明细数量不一致");
    ErrorCode WM_MISC_ISSUE_LINE_NOT_EXISTS = new ErrorCode(1_040_715_100, "杂项出库单行不存在");
    ErrorCode WM_MISC_ISSUE_LINE_QUANTITY_REQUIRED = new ErrorCode(1_040_715_101,
            "请填写出库数量,或选择录入单位并填写录入数量后由系统自动换算");
    ErrorCode WM_MISC_ISSUE_DETAIL_NOT_EXISTS = new ErrorCode(1_040_715_200, "杂项出库单明细不存在");
    // ========== MES ä»“库管理-杂项入库单(1-040-716-000) ==========
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryService.java
@@ -45,6 +45,13 @@
    void deleteMachinery(Long id);
    /**
     * æ‰¹é‡åˆ é™¤è®¾å¤‡
     *
     * @param ids ç¼–号数组
     */
    void deleteMachineryList(Collection<Long> ids);
    /**
     * èŽ·å¾—è®¾å¤‡
     *
     * @param id ç¼–号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
@@ -4,6 +4,7 @@
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.MesDvMachineryImportExcelVO;
@@ -109,26 +110,54 @@
    @Override
    public void deleteMachinery(Long id) {
        // æ ¡éªŒå­˜åœ¨
        validateMachineryExists(id);
        MesDvMachineryDO machinery = machineryMapper.selectById(id);
        if (machinery == null) {
            throw exception(DV_MACHINERY_NOT_EXISTS);
        }
        // æ ¡éªŒå…³è”数据
        if (checkPlanMachineryService.getCheckPlanMachineryCountByMachineryId(id) > 0) {
            throw exception(DV_MACHINERY_HAS_CHECK_PLAN);
        }
        if (checkRecordService.getCheckRecordCountByMachineryId(id) > 0) {
            throw exception(DV_MACHINERY_HAS_CHECK_RECORD);
        }
        if (maintenRecordService.getMaintenRecordCountByMachineryId(id) > 0) {
            throw exception(DV_MACHINERY_HAS_MAINTEN_RECORD);
        }
        if (repairService.getRepairCountByMachineryId(id) > 0) {
            throw exception(DV_MACHINERY_HAS_REPAIR);
        }
        validateMachineryDeletable(machinery);
        // åˆ é™¤
        machineryMapper.deleteById(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMachineryList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // é€ä¸ªåˆ é™¤ï¼ˆå¤ç”¨å•条删除逻辑,保持校验),任一失败则整体回滚
        ids.forEach(this::deleteMachinery);
    }
    /**
     * æ ¡éªŒè®¾å¤‡æ˜¯å¦å¯åˆ é™¤ï¼ˆå…³è”点检计划/点检记录/保养记录/维修记录任一存在则不允许)
     *
     * @param machinery è®¾å¤‡
     */
    private void validateMachineryDeletable(MesDvMachineryDO machinery) {
        Long id = machinery.getId();
        String label = StrUtil.format("{}({})", machinery.getName(), machinery.getCode());
        if (checkPlanMachineryService.getCheckPlanMachineryCountByMachineryId(id) > 0) {
            throw new ServiceException(DV_MACHINERY_HAS_CHECK_PLAN.getCode(),
                    StrUtil.format("设备{}已关联点检计划,无法删除;请先在点检计划中移除该设备后重试", label));
        }
        if (checkRecordService.getCheckRecordCountByMachineryId(id) > 0) {
            throw new ServiceException(DV_MACHINERY_HAS_CHECK_RECORD.getCode(),
                    StrUtil.format("设备{}已存在点检记录,无法删除;如需删除请先处理该设备的点检记录", label));
        }
        if (maintenRecordService.getMaintenRecordCountByMachineryId(id) > 0) {
            throw new ServiceException(DV_MACHINERY_HAS_MAINTEN_RECORD.getCode(),
                    StrUtil.format("设备{}已存在保养记录,无法删除;如需删除请先处理该设备的保养记录", label));
        }
        if (repairService.getRepairCountByMachineryId(id) > 0) {
            throw new ServiceException(DV_MACHINERY_HAS_REPAIR.getCode(),
                    StrUtil.format("设备{}已存在维修记录,无法删除;如需删除请先处理该设备的维修记录", label));
        }
    }
    @Override
    public void validateMachineryExists(Long id) {
        if (machineryMapper.selectById(id) == null) {
            throw exception(DV_MACHINERY_NOT_EXISTS);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -106,7 +106,7 @@
        // 1.2 æ ¡éªŒæ¥æºå•据参数,并验证数据存在
        String sourceDocCode = validateAndGetSourceDocCode(
                createReqVO.getSourceDocType(), createReqVO.getSourceDocId(), createReqVO.getSourceLineId());
        // 1.3 ç¡®å®šè´¨æ£€æ–¹æ¡ˆï¼šæ˜¾å¼æŒ‡å®šæ—¶æ ¡éªŒæ–¹æ¡ˆæœ¬èº«å¯ç”¨ï¼ˆä¸è¦æ±‚与产品物料绑定);未指定时按物料已绑定方案兜底
        // 1.3 ç¡®å®šè´¨æ£€æ–¹æ¡ˆï¼šè´¨æ£€æ–¹æ¡ˆä¸Žäº§å“ç‰©æ–™ä¸ç»‘定,显式指定时只校验方案本身可用;未指定时走默认方案解析
        Long templateId;
        if (createReqVO.getTemplateId() != null) {
            MesQcTemplateDO template = templateService.getTemplate(createReqVO.getTemplateId());
@@ -122,8 +122,7 @@
            }
            templateId = template.getId();
        } else {
            templateId = templateItemService.getRequiredTemplateByItemIdAndType(
                    createReqVO.getItemId(), MesQcTypeEnum.IQC.getType()).getTemplateId();
            templateId = resolveDefaultIqcTemplateId(createReqVO.getItemId());
        }
        // 2. æ’入主表
@@ -385,17 +384,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId) {
        // 1. æ ¡éªŒç‰©æ–™æ˜¯å¦é…ç½®äº† IQC æ£€éªŒæ¨¡æ¿
        MesQcTemplateItemDO templateItem;
        try {
            templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                    line.getItemId(), MesQcTypeEnum.IQC.getType());
        } catch (Exception e) {
            // èŽ·å–ç‰©æ–™ä¿¡æ¯ï¼Œæä¾›æ›´æ˜Žç¡®çš„é”™è¯¯æç¤º
            String itemInfo = getItemInfo(line.getItemId());
            throw exception(QC_NO_TEMPLATE_FOR_ITEM, itemInfo);
        }
        Long templateId = templateItem.getTemplateId();
        // 1. ç¡®å®šè´¨æ£€æ–¹æ¡ˆï¼šè´¨æ£€æ–¹æ¡ˆä¸Žäº§å“ç‰©æ–™ä¸ç»‘定,物料未绑定方案时取任一启用的来料检验方案
        Long templateId = resolveDefaultIqcTemplateId(line.getItemId());
        // 2. ç”Ÿæˆæ£€éªŒå•编码
        String code = "IQC" + System.currentTimeMillis();
@@ -426,21 +416,28 @@
    }
    /**
     * èŽ·å–ç‰©æ–™ä¿¡æ¯ï¼ˆç¼–ç +名称)
     * è§£æžæ¥æ–™æ£€éªŒçš„默认质检方案:质检方案与产品物料不绑定。
     *
     * 1. ç‰©æ–™å·²ç»‘定来料检验方案时,优先沿用(兼容存量配置);
     * 2. ç‰©æ–™æœªç»‘定时,取任一启用且检测种类包含来料检验的方案(按编号取最早创建的一个)。
     *
     * @param itemId äº§å“ç‰©æ–™ç¼–号
     * @return è´¨æ£€æ–¹æ¡ˆç¼–号
     */
    private String getItemInfo(Long itemId) {
        if (itemId == null) {
            return "未知物料";
    private Long resolveDefaultIqcTemplateId(Long itemId) {
        List<MesQcTemplateItemDO> boundList = templateItemService.getTemplateItemList(
                itemId, MesQcTypeEnum.IQC.getType(), null, null);
        if (CollUtil.isNotEmpty(boundList)) {
            return boundList.get(0).getTemplateId();
        }
        try {
            cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData();
            if (item != null) {
                return item.getCode() + "-" + item.getName();
        List<MesQcTemplateDO> enabledTemplates = templateService.getEnabledTemplateListByType(MesQcTypeEnum.IQC.getType());
        if (CollUtil.isEmpty(enabledTemplates)) {
            throw exception(QC_IQC_NO_ENABLED_TEMPLATE);
            }
        } catch (Exception e) {
            // å¿½ç•¥å¼‚常
        }
        return "物料ID:" + itemId;
        return enabledTemplates.stream()
                .map(MesQcTemplateDO::getId)
                .min(Comparator.naturalOrder())
                .orElseThrow(() -> exception(QC_IQC_NO_ENABLED_TEMPLATE));
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptLineServiceImpl.java
@@ -12,12 +12,14 @@
import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemreceipt.MesWmItemReceiptLineMapper;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeLineService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.unitconvert.MesWmItemUnitConvertService;
import jakarta.annotation.Resource;
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.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -44,6 +46,8 @@
    private MesWmArrivalNoticeLineService arrivalNoticeLineService;
    @Resource
    private MesWmBatchService batchService;
    @Resource
    private MesWmItemUnitConvertService itemUnitConvertService;
    @Override
    public Long createItemReceiptLine(MesWmItemReceiptLineSaveReqVO createReqVO) {
@@ -54,13 +58,15 @@
        // 3.1 åˆ›å»ºå…¥åº“单行
        MesWmItemReceiptLineDO line = BeanUtils.toBean(createReqVO, MesWmItemReceiptLineDO.class);
        // 3.2 ç”Ÿæˆæˆ–获取批次
        // 3.2 åŒå•位换算
        fillReceivedQuantity(line, createReqVO);
        // 3.3 ç”Ÿæˆæˆ–获取批次
        MesWmBatchDO batch = generateOrGetBatch(receipt, createReqVO);
        if (batch != null) {
            line.setBatchId(batch.getId());
            line.setBatchCode(batch.getCode());
        }
        // 3.3 æ’入数据库
        // 3.4 æ’入数据库
        itemReceiptLineMapper.insert(line);
        return line.getId();
    }
@@ -76,15 +82,36 @@
        // 2.1 æ›´æ–°
        MesWmItemReceiptLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmItemReceiptLineDO.class);
        // 2.2 ç”Ÿæˆæˆ–获取批次
        // 2.2 åŒå•位换算
        fillReceivedQuantity(updateObj, updateReqVO);
        // 2.3 ç”Ÿæˆæˆ–获取批次
        MesWmBatchDO batch = generateOrGetBatch(receipt, updateReqVO);
        if (batch != null) {
            updateObj.setBatchId(batch.getId()).setBatchCode(batch.getCode());
        }
        // 2.3 æ›´æ–°æ•°æ®åº“
        // 2.4 æ›´æ–°æ•°æ®åº“
        itemReceiptLineMapper.updateById(updateObj);
    }
    /**
     * åŒå•位换算:按录入单位与所选型号,把录入数量换算为物料主单位的入库数量
     *
     * æœªé€‰æ‹©å½•入单位时,说明本次按物料主单位录入,直接使用传入的入库数量。
     */
    private void fillReceivedQuantity(MesWmItemReceiptLineDO line, MesWmItemReceiptLineSaveReqVO reqVO) {
        if (reqVO.getEnterUnitMeasureId() != null) {
            BigDecimal primaryQuantity = itemUnitConvertService.convertToPrimaryUnit(
                    reqVO.getItemId(), reqVO.getItemModelId(),
                    reqVO.getEnterUnitMeasureId(), reqVO.getEnterQuantity());
            if (primaryQuantity != null) {
                line.setReceivedQuantity(primaryQuantity);
            }
        }
        if (line.getReceivedQuantity() == null) {
            throw exception(WM_ITEM_RECEIPT_LINE_QUANTITY_REQUIRED);
        }
    }
    private void validateItemReceiptLineSaveData(MesWmItemReceiptDO receipt, MesWmItemReceiptLineSaveReqVO reqVO) {
        // æ ¡éªŒå…³è”到货通知单行
        validateArrivalNoticeLine(receipt, reqVO.getArrivalNoticeLineId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueLineServiceImpl.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscissue.MesWmMiscIssueLineMapper;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.unitconvert.MesWmItemUnitConvertService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
@@ -14,10 +15,12 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MISC_ISSUE_LINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MISC_ISSUE_LINE_QUANTITY_REQUIRED;
/**
 * MES æ‚项出库单行 Service å®žçŽ°ç±»
@@ -39,6 +42,8 @@
    private MesMdmItemService mdmItemService;
    @Resource
    private MesWmWarehouseAreaService warehouseAreaService;
    @Resource
    private MesWmItemUnitConvertService itemUnitConvertService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -46,11 +51,14 @@
        // 1. æ ¡éªŒæ•°æ®
        validateMiscIssueLineSaveData(createReqVO.getIssueId(), createReqVO);
        // 2. æ–°å¢žè¡Œ
        // 2. åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(createReqVO);
        // 3. æ–°å¢žè¡Œ
        MesWmMiscIssueLineDO line = BeanUtils.toBean(createReqVO, MesWmMiscIssueLineDO.class);
        miscIssueLineMapper.insert(line);
        // 3.自动创建明细
        // 4.自动创建明细
        createReqVO.setId(line.getId());
        miscIssueDetailService.createMiscIssueDetail(createReqVO);
        return line.getId();
@@ -64,14 +72,36 @@
        // 1.2 æ ¡éªŒæ•°æ®
        validateMiscIssueLineSaveData(line.getIssueId(), updateReqVO);
        // 2. æ›´æ–°è¡Œ
        // 2. åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(updateReqVO);
        // 3. æ›´æ–°è¡Œ
        MesWmMiscIssueLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmMiscIssueLineDO.class);
        miscIssueLineMapper.updateById(updateObj);
        // 3. æ›´æ–°æ˜Žç»†ï¼ˆåŸºäºŽè¡Œä¿¡æ¯ï¼‰
        // 4. æ›´æ–°æ˜Žç»†ï¼ˆåŸºäºŽè¡Œä¿¡æ¯ï¼‰
        miscIssueDetailService.updateMiscIssueDetail(updateReqVO);
    }
    /**
     * åŒå•位换算:选择了录入单位时,按「录入数量 Ã— æ¢ç®—比率」得到物料主单位口径的出库数量
     *
     * æ¢ç®—结果写回请求参数,使行与 1:1 æ˜Žç»†çš„口径保持一致;未按辅单位录入时必须直接填写出库数量
     */
    private void fillPrimaryQuantity(MesWmMiscIssueLineSaveReqVO reqVO) {
        if (reqVO.getEnterUnitMeasureId() != null) {
            BigDecimal primaryQuantity = itemUnitConvertService.convertToPrimaryUnit(
                    reqVO.getItemId(), reqVO.getItemModelId(),
                    reqVO.getEnterUnitMeasureId(), reqVO.getEnterQuantity());
            if (primaryQuantity != null) {
                reqVO.setQuantity(primaryQuantity);
            }
        }
        if (reqVO.getQuantity() == null) {
            throw exception(WM_MISC_ISSUE_LINE_QUANTITY_REQUIRED);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMiscIssueLine(Long id) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueLineServiceImpl.java
@@ -14,16 +14,19 @@
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.unitconvert.MesWmItemUnitConvertService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_OUTSOURCE_ISSUE_LINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_OUTSOURCE_ISSUE_LINE_ITEM_NOT_IN_BOM;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_OUTSOURCE_ISSUE_LINE_QUANTITY_REQUIRED;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_OUTSOURCE_ISSUE_NOT_EXISTS;
/**
@@ -49,6 +52,8 @@
    private MesWmBatchService batchService;
    @Resource
    private MesProWorkOrderBomService workOrderBomService;
    @Resource
    private MesWmItemUnitConvertService itemUnitConvertService;
    @Override
    public Long createOutsourceIssueLine(MesWmOutsourceIssueLineSaveReqVO createReqVO) {
@@ -56,6 +61,8 @@
        validateOutsourceIssueLineSaveData(createReqVO);
        // æ ¹æ® batchCode è§£æž batchId
        fillBatchId(createReqVO);
        // åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(createReqVO);
        // æ’å…¥
        MesWmOutsourceIssueLineDO line = BeanUtils.toBean(createReqVO, MesWmOutsourceIssueLineDO.class);
@@ -71,12 +78,33 @@
        validateOutsourceIssueLineSaveData(updateReqVO);
        // æ ¹æ® batchCode è§£æž batchId
        fillBatchId(updateReqVO);
        // åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(updateReqVO);
        // æ›´æ–°
        MesWmOutsourceIssueLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmOutsourceIssueLineDO.class);
        outsourceIssueLineMapper.updateById(updateObj);
    }
    /**
     * åŒå•位换算:选择了录入单位时,按「录入数量 Ã— æ¢ç®—比率」得到物料主单位口径的发料数量
     *
     * æœªæŒ‰è¾…单位录入时必须直接填写发料数量
     */
    private void fillPrimaryQuantity(MesWmOutsourceIssueLineSaveReqVO reqVO) {
        if (reqVO.getEnterUnitMeasureId() != null) {
            BigDecimal primaryQuantity = itemUnitConvertService.convertToPrimaryUnit(
                    reqVO.getItemId(), reqVO.getItemModelId(),
                    reqVO.getEnterUnitMeasureId(), reqVO.getEnterQuantity());
            if (primaryQuantity != null) {
                reqVO.setQuantity(primaryQuantity);
            }
        }
        if (reqVO.getQuantity() == null) {
            throw exception(WM_OUTSOURCE_ISSUE_LINE_QUANTITY_REQUIRED);
        }
    }
    @Override
    public void deleteOutsourceIssueLine(Long id) {
        // æ ¡éªŒå­˜åœ¨
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java
@@ -24,6 +24,7 @@
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueLineService;
import cn.iocoder.yudao.module.mes.service.wm.productissue.MesWmProductIssueService;
import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueLineService;
import cn.iocoder.yudao.module.mes.service.wm.unitconvert.MesWmItemUnitConvertService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -63,10 +64,14 @@
    private MesWmProductIssueDetailService issueDetailService;
    @Resource
    private MesProWorkOrderBomService workOrderBomService;
    @Resource
    private MesWmItemUnitConvertService itemUnitConvertService;
    @Override
    public Long createProductIssueLine(MesWmProductIssueLineSaveReqVO createReqVO) {
        validateProductIssueLineSaveData(createReqVO);
        // åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(createReqVO);
        // æ’å…¥
        MesWmProductIssueLineDO line = BeanUtils.toBean(createReqVO, MesWmProductIssueLineDO.class);
@@ -79,12 +84,33 @@
        // æ ¡éªŒå­˜åœ¨
        validateProductIssueLineExists(updateReqVO.getId());
        validateProductIssueLineSaveData(updateReqVO);
        // åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(updateReqVO);
        // æ›´æ–°
        MesWmProductIssueLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductIssueLineDO.class);
        issueLineMapper.updateById(updateObj);
    }
    /**
     * åŒå•位换算:选择了录入单位时,按「录入数量 Ã— æ¢ç®—比率」得到物料主单位口径的领料数量
     *
     * æœªæŒ‰è¾…单位录入时必须直接填写领料数量
     */
    private void fillPrimaryQuantity(MesWmProductIssueLineSaveReqVO reqVO) {
        if (reqVO.getEnterUnitMeasureId() != null) {
            BigDecimal primaryQuantity = itemUnitConvertService.convertToPrimaryUnit(
                    reqVO.getItemId(), reqVO.getItemModelId(),
                    reqVO.getEnterUnitMeasureId(), reqVO.getEnterQuantity());
            if (primaryQuantity != null) {
                reqVO.setQuantity(primaryQuantity);
            }
        }
        if (reqVO.getQuantity() == null) {
            throw exception(WM_PRODUCT_ISSUE_LINE_QUANTITY_REQUIRED);
        }
    }
    @Override
    public void deleteProductIssueLine(Long id) {
        // æ ¡éªŒå­˜åœ¨
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
@@ -18,6 +18,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO;
import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeLineService;
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService;
import cn.iocoder.yudao.module.mes.service.wm.unitconvert.MesWmItemUnitConvertService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
@@ -25,6 +26,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
@@ -60,9 +62,13 @@
    @Resource
    @Lazy
    private MesQcOqcService oqcService;
    @Resource
    private MesWmItemUnitConvertService itemUnitConvertService;
    @Override
    public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) {
        // åŒå•位换算:按录入单位换算为物料主单位数量(必须先换算,再校验与发货通知单行的数量一致性)
        fillPrimaryQuantity(createReqVO);
        // æ ¡éªŒæ•°æ®
        validateLineSaveData(createReqVO);
@@ -76,8 +82,10 @@
    public void updateProductSalesLine(MesWmProductSalesLineSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨
        MesWmProductSalesLineDO oldLine = validateProductSalesLineExists(updateReqVO.getId());
        // æ ¡éªŒæ•°æ®
        updateReqVO.setSalesId(oldLine.getSalesId());
        // åŒå•位换算:按录入单位换算为物料主单位数量(必须先换算,再校验与发货通知单行的数量一致性)
        fillPrimaryQuantity(updateReqVO);
        // æ ¡éªŒæ•°æ®
        validateLineSaveData(updateReqVO);
        // æ›´æ–°
@@ -167,6 +175,25 @@
    }
    /**
     * åŒå•位换算:选择了录入单位时,按「录入数量 Ã— æ¢ç®—比率」得到物料主单位口径的出库数量
     *
     * æœªæŒ‰è¾…单位录入时必须直接填写出库数量
     */
    private void fillPrimaryQuantity(MesWmProductSalesLineSaveReqVO reqVO) {
        if (reqVO.getEnterUnitMeasureId() != null) {
            BigDecimal primaryQuantity = itemUnitConvertService.convertToPrimaryUnit(
                    reqVO.getItemId(), reqVO.getItemModelId(),
                    reqVO.getEnterUnitMeasureId(), reqVO.getEnterQuantity());
            if (primaryQuantity != null) {
                reqVO.setQuantity(primaryQuantity);
            }
        }
        if (reqVO.getQuantity() == null) {
            throw exception(WM_PRODUCT_SALES_LINE_QUANTITY_REQUIRED);
        }
    }
    /**
     * æ ¹æ® batchCode è§£æž batchId
     */
    private void fillBatchId(MesWmProductSalesLineSaveReqVO reqVO) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorLineServiceImpl.java
@@ -18,18 +18,21 @@
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnvendor.MesWmReturnVendorLineMapper;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.unitconvert.MesWmItemUnitConvertService;
import jakarta.annotation.Resource;
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.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_VENDOR_LINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_VENDOR_LINE_QUANTITY_REQUIRED;
/**
 * MES ä¾›åº”商退货单行 Service å®žçŽ°ç±»
@@ -53,6 +56,8 @@
    private MesWmBatchService batchService;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    @Resource
    private MesWmItemUnitConvertService itemUnitConvertService;
    @Override
    public Long createReturnVendorLine(MesWmReturnVendorLineSaveReqVO createReqVO) {
@@ -60,6 +65,8 @@
        MesWmReturnVendorDO returnVendor = validateReturnVendorLineSaveData(createReqVO);
        // é€€è´§å•若已关联采购订单,物料必须属于该采购订单,禁止跨订单随意选料
        validateReturnVendorItemInPurchaseOrder(returnVendor, createReqVO.getItemId(), null);
        // åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(createReqVO);
        // æ’å…¥
        MesWmReturnVendorLineDO line = BeanUtils.toBean(createReqVO, MesWmReturnVendorLineDO.class);
@@ -77,6 +84,8 @@
        MesWmReturnVendorDO returnVendor = validateReturnVendorLineSaveData(updateReqVO);
        // å…³è”采购订单时,不允许把行物料改成订单外的物料;仅改数量等操作不受影响
        validateReturnVendorItemInPurchaseOrder(returnVendor, updateReqVO.getItemId(), oldLine.getItemId());
        // åŒå•位换算:按录入单位换算为物料主单位数量
        fillPrimaryQuantity(updateReqVO);
        // æ›´æ–°
        MesWmReturnVendorLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmReturnVendorLineDO.class);
@@ -139,6 +148,25 @@
    }
    /**
     * åŒå•位换算:选择了录入单位时,按「录入数量 Ã— æ¢ç®—比率」得到物料主单位口径的退货数量
     *
     * æœªæŒ‰è¾…单位录入时必须直接填写退货数量
     */
    private void fillPrimaryQuantity(MesWmReturnVendorLineSaveReqVO reqVO) {
        if (reqVO.getEnterUnitMeasureId() != null) {
            BigDecimal primaryQuantity = itemUnitConvertService.convertToPrimaryUnit(
                    reqVO.getItemId(), reqVO.getItemModelId(),
                    reqVO.getEnterUnitMeasureId(), reqVO.getEnterQuantity());
            if (primaryQuantity != null) {
                reqVO.setQuantity(primaryQuantity);
            }
        }
        if (reqVO.getQuantity() == null) {
            throw exception(WM_RETURN_VENDOR_LINE_QUANTITY_REQUIRED);
        }
    }
    /**
     * æ ¡éªŒé€€è´§è¡Œç‰©æ–™å±žäºŽæ‰€å…³è”采购订单(退货单已关联采购订单时)。
     *
     * é‡‡è´­é€€è´§çš„物料应取自关联采购订单,此处为后端兜底,防止通过接口跨订单随意选料。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/unitconvert/MesWmItemUnitConvertService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.mes.service.wm.unitconvert;
import java.math.BigDecimal;
/**
 * MES ç‰©æ–™ä¸»è¾…单位换算 Service æŽ¥å£
 *
 * å‡ºå…¥åº“单据行的数量统一按物料主单位记账,本服务负责把「按辅单位/型号单位录入的数量」
 * æ¢ç®—成主单位数量。换算比率来源优先级:
 * 1. è¡Œä¸ŠæŒ‡å®šçš„物料型号(型号上配置的单位 + æ¯”率);
 * 2. ç‰©æ–™ä¸»æ•°æ®ä¸Šçš„辅单位 1 / è¾…单位 2(存量配置);
 * 3. ç‰©æ–™åž‹å·ä¸­è¯¥å•位下唯一的比率。
 *
 * @author è¶…级管理员
 */
public interface MesWmItemUnitConvertService {
    /**
     * è§£æžæ¢ç®—比率:1 ä¸ªæŒ‡å®šå•位 = N ä¸ªç‰©æ–™ä¸»å•位
     *
     * @param itemId        ç‰©æ–™ç¼–号
     * @param itemModelId   ç‰©æ–™åž‹å·ç¼–号(可为空)
     * @param unitMeasureId å½•入单位编号(可为空,为空视为主单位)
     * @return æ¢ç®—比率,主单位或未指定单位时返回 1
     */
    BigDecimal resolveRateToPrimaryUnit(Long itemId, Long itemModelId, Long unitMeasureId);
    /**
     * å°†å½•入数量换算为物料主单位数量
     *
     * @param itemId        ç‰©æ–™ç¼–号
     * @param itemModelId   ç‰©æ–™åž‹å·ç¼–号(可为空)
     * @param unitMeasureId å½•入单位编号(可为空,为空视为主单位)
     * @param enterQuantity å½•入数量
     * @return ä¸»å•位数量;录入单位即主单位时原样返回
     */
    BigDecimal convertToPrimaryUnit(Long itemId, Long itemModelId, Long unitMeasureId, BigDecimal enterQuantity);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/unitconvert/MesWmItemUnitConvertServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
package cn.iocoder.yudao.module.mes.service.wm.unitconvert;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemModelApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemModelRespDTO;
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 jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
 * MES ç‰©æ–™ä¸»è¾…单位换算 Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class MesWmItemUnitConvertServiceImpl implements MesWmItemUnitConvertService {
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmItemModelApi mdmItemModelApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Override
    public BigDecimal resolveRateToPrimaryUnit(Long itemId, Long itemModelId, Long unitMeasureId) {
        MdmItemRespDTO item = mdmItemApi.validateItemExists(itemId).getCheckedData();
        // 1. æœªæŒ‡å®šå½•入单位,或录入单位就是物料主单位 â†’ æ¯”率 1
        if (unitMeasureId == null || ObjUtil.equal(item.getUnitMeasureId(), unitMeasureId)) {
            return BigDecimal.ONE;
        }
        // 2. æŒ‡å®šäº†åž‹å· â†’ ä»¥åž‹å·ä¸Šé…ç½®çš„单位与比率为准
        if (itemModelId != null) {
            MdmItemModelRespDTO model = mdmItemModelApi.validateItemModelExistsAndEnable(itemModelId, itemId)
                    .getCheckedData();
            if (ObjUtil.notEqual(model.getUnitMeasureId(), unitMeasureId)) {
                throw new ServiceException(WM_ITEM_UNIT_CONVERT_MODEL_UNIT_MISMATCH.getCode(), String.format(
                        "所选型号【%s】的计量单位是【%s】,与本次录入单位【%s】不一致,请重新选择型号或改回型号对应的计量单位",
                        model.getModel(), unitName(model.getUnitMeasureId()), unitName(unitMeasureId)));
            }
            return validateRate(model);
        }
        // 3. æœªæŒ‡å®šåž‹å· â†’ å…ˆåŒ¹é…ç‰©æ–™ä¸»æ•°æ®ä¸Šçš„辅单位 1 / è¾…单位 2
        if (ObjUtil.equal(item.getUnitMeasureId2(), unitMeasureId) && item.getUnitMeasureRate1() != null) {
            return item.getUnitMeasureRate1();
        }
        if (ObjUtil.equal(item.getUnitMeasureId3(), unitMeasureId) && item.getUnitMeasureRate2() != null) {
            return item.getUnitMeasureRate2();
        }
        // 4. å†ä»Žç‰©æ–™åž‹å·ä¸­æ‰¾è¯¥å•位:比率唯一可直接使用,比率有多个取值则必须指定型号
        List<MdmItemModelRespDTO> models = mdmItemModelApi.getItemModelListByItemId(itemId).getCheckedData().stream()
                .filter(model -> ObjUtil.equal(model.getUnitMeasureId(), unitMeasureId))
                .filter(model -> CommonStatusEnum.ENABLE.getStatus().equals(model.getStatus()))
                .toList();
        List<BigDecimal> distinctRates = models.stream()
                .map(MdmItemModelRespDTO::getUnitMeasureRate)
                .filter(java.util.Objects::nonNull)
                .distinct()
                .toList();
        if (distinctRates.size() > 1) {
            throw new ServiceException(WM_ITEM_UNIT_CONVERT_MODEL_REQUIRED.getCode(), String.format(
                    "物料【%s-%s】下使用计量单位【%s】的型号有多个且换算比率不同,请先选择型号后再录入数量",
                    item.getCode(), item.getName(), unitName(unitMeasureId)));
        }
        if (distinctRates.size() == 1) {
            return validateRate(distinctRates.get(0), models.get(0).getModel());
        }
        // 5. ä¸»æ•°æ®ä¸Žåž‹å·å‡æœªé…ç½®è¯¥å•位的换算比率
        throw new ServiceException(WM_ITEM_UNIT_CONVERT_NOT_CONFIGURED.getCode(), String.format(
                "物料【%s-%s】没有配置计量单位【%s】到主单位【%s】的换算比率,无法换算,"
                        + "请先到【基础数据-物料主数据-型号】为该物料新增型号并填写换算比率",
                item.getCode(), item.getName(), unitName(unitMeasureId), unitName(item.getUnitMeasureId())));
    }
    @Override
    public BigDecimal convertToPrimaryUnit(Long itemId, Long itemModelId, Long unitMeasureId, BigDecimal enterQuantity) {
        BigDecimal rate = resolveRateToPrimaryUnit(itemId, itemModelId, unitMeasureId);
        // 1. å½•入单位即主单位 â†’ æ•°é‡åŽŸæ ·è¿”å›ž
        if (BigDecimal.ONE.compareTo(rate) == 0) {
            return enterQuantity;
        }
        // 2. æŒ‰éžä¸»å•位录入时,录入数量必填
        if (enterQuantity == null) {
            MdmItemRespDTO item = mdmItemApi.validateItemExists(itemId).getCheckedData();
            throw new ServiceException(WM_ITEM_UNIT_CONVERT_QUANTITY_REQUIRED.getCode(), String.format(
                    "计量单位【%s】不是物料【%s-%s】的主单位,按该单位录入时录入数量不能为空,请填写录入数量或改用主单位【%s】",
                    unitName(unitMeasureId), item.getCode(), item.getName(), unitName(item.getUnitMeasureId())));
        }
        // 3. è¾…单位数量 Ã— æ¢ç®—比率 = ä¸»å•位数量
        return enterQuantity.multiply(rate);
    }
    private BigDecimal validateRate(MdmItemModelRespDTO model) {
        return validateRate(model.getUnitMeasureRate(), model.getModel());
    }
    private BigDecimal validateRate(BigDecimal rate, String modelName) {
        if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException(WM_ITEM_UNIT_CONVERT_RATE_INVALID.getCode(), String.format(
                    "型号【%s】的换算比率为【%s】不合理,1 ä¸ªåž‹å·å•位必须对应大于 0 çš„主单位数量,"
                            + "请到【基础数据-物料主数据-型号】修正该型号的换算比率",
                    modelName, rate));
        }
        return rate;
    }
    private String unitName(Long unitMeasureId) {
        if (unitMeasureId == null) {
            return "未指定";
        }
        MdmUnitMeasureRespDTO unit = mdmUnitMeasureApi.getUnitMeasure(unitMeasureId).getCheckedData();
        return unit != null ? unit.getName() : "ID=" + unitMeasureId;
    }
}