feat(mes): 更新IPQC过程检验功能
- 移除生产工单、生产任务、工位、工序相关字段
- 新增批号字段并设为必填
- 更新数据库表结构包括SQL导出文件和H2测试脚本
- 修改IPQC相关的控制器、服务和数据对象
- 调整AI服务中的查询逻辑适配新的字段结构
- 更新文档说明去除生产相关信息增加批号和MDM物料说明
- 添加批号必填的错误码常量
- 替换MES内部物料服务调用为MDM物料API调用
| | |
| | | {"name": "库存台账", "module": "仓储", "table": "mes_wm_material_stock", "doc": "物料×仓库×库区×库位×批次×供应商六元组,数量/冻结/占用/在途四量", "upstream": ["全部入库单"], "downstream": ["全部出库单", "事务流水"], "keywords": "库存 台账 stock 可用量"}, |
| | | {"name": "批次", "module": "仓储", "table": "mes_wm_batch", "doc": "关联物料/工单/供应商/客户/质量状态;SN 码含批次 uuid;条码支持18种业务对象", "upstream": ["入库单据"], "downstream": ["库存台账"], "keywords": "批次 batch SN 条码 追溯"}, |
| | | {"name": "IQC来料检验", "module": "质量", "table": "mes_qc_iqc", "doc": "到货通知/外协入库触发,草稿0→完成4,结论回写来源单据;行操作「出报告」仅在已完成时可见", "upstream": ["到货通知", "外协入库"], "downstream": ["采购入库", "供应商退货", "NCR", "报告实例(出报告)"], "keywords": "IQC 来料检验 出报告"}, |
| | | {"name": "IPQC过程检验", "module": "质量", "table": "mes_qc_ipqc", "doc": "报工质检工序产出行触发,完成回调推进报工与产出入库;行操作「出报告」仅在已完成时可见", "upstream": ["报工"], "downstream": ["产出入库", "NCR", "报告实例(出报告)"], "keywords": "IPQC 过程检验 巡检 出报告"}, |
| | | {"name": "IPQC过程检验", "module": "质量", "table": "mes_qc_ipqc", "doc": "报工质检工序产出行触发,完成回调推进报工与产出入库;表单只留「来源单据 + 产品物料(前端手选 MDM 物料) + 批号(必填) + 数量 + 检验信息」,已去生产订单/生产任务/工位/工序,批号随报告落款;行操作「出报告」仅在已完成时可见", "upstream": ["报工"], "downstream": ["产出入库", "NCR", "报告实例(出报告)"], "keywords": "IPQC 过程检验 巡检 出报告 批号 batchCode MDM物料"}, |
| | | {"name": "OQC出货检验", "module": "质量", "table": "mes_qc_oqc", "doc": "销售出库提交触发,完成推进出库单待拣货;行操作「出报告」仅在已完成时可见", "upstream": ["销售出库单"], "downstream": ["销售出库单(推进)", "报告实例(出报告)"], "keywords": "OQC 出货检验 出厂检验 出报告"}, |
| | | {"name": "RQC退货检验", "module": "质量", "table": "mes_qc_rqc", "doc": "生产退料/销售退货/售后退货触发,结论回调售后;行操作「出报告」仅在已完成时可见。措辞已统一为「退货检验」(原菜单父项与 mes_qc_type 字典写「退料检验」,与后端枚举/表注释/菜单权限子项不一致;RQC 三者都覆盖,故取「退货检验」)", "upstream": ["退料", "退货"], "downstream": ["退货入库", "售后回调", "报告实例(出报告)"], "keywords": "RQC 退货检验 退料检验 出报告"}, |
| | | {"name": "NCR不合格品", "module": "质量", "table": "mes_qc_ncr", "doc": "待评审0→已评审6→已处置7→已关闭8,处置:退货/报废/返工/让步接收/降级", "upstream": ["IQC/IPQC/OQC/RQC"], "downstream": ["退货/报废/返工(台账)"], "keywords": "NCR 不合格 处置 特采 报废"}, |
| | |
| | | {"name": "库存台账", "module": "仓储", "table": "mes_wm_material_stock", "doc": "物料×仓库×库区×库位×批次×供应商六元组,数量/冻结/占用/在途四量", "upstream": ["全部入库单"], "downstream": ["全部出库单", "事务流水"], "keywords": "库存 台账 stock 可用量"}, |
| | | {"name": "批次", "module": "仓储", "table": "mes_wm_batch", "doc": "关联物料/工单/供应商/客户/质量状态;SN 码含批次 uuid;条码支持18种业务对象", "upstream": ["入库单据"], "downstream": ["库存台账"], "keywords": "批次 batch SN 条码 追溯"}, |
| | | {"name": "IQC来料检验", "module": "质量", "table": "mes_qc_iqc", "doc": "到货通知/外协入库触发,草稿0→完成4,结论回写来源单据;行操作「出报告」仅在已完成时可见", "upstream": ["到货通知", "外协入库"], "downstream": ["采购入库", "供应商退货", "NCR", "报告实例(出报告)"], "keywords": "IQC 来料检验 出报告"}, |
| | | {"name": "IPQC过程检验", "module": "质量", "table": "mes_qc_ipqc", "doc": "报工质检工序产出行触发,完成回调推进报工与产出入库;行操作「出报告」仅在已完成时可见", "upstream": ["报工"], "downstream": ["产出入库", "NCR", "报告实例(出报告)"], "keywords": "IPQC 过程检验 巡检 出报告"}, |
| | | {"name": "IPQC过程检验", "module": "质量", "table": "mes_qc_ipqc", "doc": "报工质检工序产出行触发,完成回调推进报工与产出入库;表单只留「来源单据 + 产品物料(前端手选 MDM 物料) + 批号(必填) + 数量 + 检验信息」,已去生产订单/生产任务/工位/工序,批号随报告落款;行操作「出报告」仅在已完成时可见", "upstream": ["报工"], "downstream": ["产出入库", "NCR", "报告实例(出报告)"], "keywords": "IPQC 过程检验 巡检 出报告 批号 batchCode MDM物料"}, |
| | | {"name": "OQC出货检验", "module": "质量", "table": "mes_qc_oqc", "doc": "销售出库提交触发,完成推进出库单待拣货;行操作「出报告」仅在已完成时可见", "upstream": ["销售出库单"], "downstream": ["销售出库单(推进)", "报告实例(出报告)"], "keywords": "OQC 出货检验 出厂检验 出报告"}, |
| | | {"name": "RQC退货检验", "module": "质量", "table": "mes_qc_rqc", "doc": "生产退料/销售退货/售后退货触发,结论回调售后;行操作「出报告」仅在已完成时可见。措辞已统一为「退货检验」(原菜单父项与 mes_qc_type 字典写「退料检验」,与后端枚举/表注释/菜单权限子项不一致;RQC 三者都覆盖,故取「退货检验」)", "upstream": ["退料", "退货"], "downstream": ["退货入库", "售后回调", "报告实例(出报告)"], "keywords": "RQC 退货检验 退料检验 出报告"}, |
| | | {"name": "NCR不合格品", "module": "质量", "table": "mes_qc_ncr", "doc": "待评审0→已评审6→已处置7→已关闭8,处置:退货/报废/返工/让步接收/降级", "upstream": ["IQC/IPQC/OQC/RQC"], "downstream": ["退货/报废/返工(台账)"], "keywords": "NCR 不合格 处置 特采 报废"}, |
| | |
| | | -- ruoyi-vue-pro.system_menu WHERE deleted=b'0'`,是执行期跨库全量拷贝, |
| | | -- 只要源库 ruoyi-vue-pro 有这几行就会被带过去 —— 与阶段一的字面量 INSERT 不同。 |
| | | -- 在此登记只为说明「这版文件确实覆盖了该变更」,避免后来者以为漏了。 |
| | | -- ⑥ 补 116 个按钮权限 + 删 1 个失效菜单(2026-09-22,前后端菜单/权限对齐): |
| | | -- 116 个按钮权限是「后端 @PreAuthorize 注解里有、system_menu 里没有」的差集 |
| | | -- (A 类 19 个:前后端都在用而库缺失,属真缺;B 类 97 个:仅后端定义), |
| | | -- 按「模块:实体」前缀分别挂到对应 type=2 的菜单下(如 erp:supplier:* → 602525 供应商档案)。 |
| | | -- 同时软删 1063009 仓库坐标配置及其 2 个按钮 1063091/1063092 —— |
| | | -- 该菜单 component 指向 bi/config/warehouse-coordinate/index,前端并无此页面(历史残留), |
| | | -- 并物理清掉这 3 个菜单的 9 条 system_role_menu 孤儿授权。 |
| | | -- **本文件同样无需为此改动**:阶段② 跨库全量拷贝 system_menu(deleted=b'0') 与 |
| | | -- system_role_menu,源库 ruoyi-vue-pro 已写入,执行期自会带过去。理由同 ⑤。 |
| | | -- ⑦ 薪酬按钮权限码改名对齐 + 删除「ERP 系统」整个菜单(2026-09-22): |
| | | -- ① 前端 hrm/salary/calculation/index.vue、payment/index.vue 原先用 |
| | | -- hrm:salary-calculation:* / hrm:salary-payment:*,而后端 |
| | | -- HrmSalaryCalculationController / HrmSalaryPaymentController 用的是 hrm:salary:*, |
| | | -- 两边对不上(按钮要么压根不显示,要么显示了被后端 403)。已改前端对齐后端, |
| | | -- 并软删库中 5 条旧码按钮(60212/60213/60214/60244/60245)及其 15 条 role_menu。 |
| | | -- 注:旧按钮原本挂在「超级管理员/高层管理/销售人员」3 个角色上(销售人员持有薪资发放权限, |
| | | -- 疑为角色页全选误授),删除后未迁移授权,新按钮 hrm:salary:* 目前无任何角色授权, |
| | | -- 需在角色管理页按实际岗位重新勾选。 |
| | | -- ② 软删「ERP 系统」(id=2563, path=/erp) 整棵子树共 83 个节点 —— |
| | | -- 产品管理 17 + 库存管理 43 + 财务管理 20 + ERP首页 1 + 目录节点 2, |
| | | -- 并物理清掉其 83 条 system_role_menu(该子树仅超级管理员一个角色挂有授权)。 |
| | | -- 其中产品管理 / 库存管理在后端 yudao-module-erp 已无任何控制器 |
| | | -- (erp:product* / erp:warehouse* / erp:stock* 全部不存在),属历史残留; |
| | | -- 财务管理(结算账户/付款单/收款单)后端控制器仍在,删后这两个页面失去菜单入口 |
| | | -- (付款单在「采购管理」下另有重复入口 1063116)。「采购管理」(2602)、 |
| | | -- 「营销管理」(2617) 两个根目录未动,其中的 ERP 页面照常保留。 |
| | | -- ⚠ 这是**已知且已确认接受的代价**:删除后 13 个仍在使用的权限码失去 system_menu 载体 —— |
| | | -- erp:account:create/update/delete/export(A 类:前后端都在用)、 |
| | | -- erp:finance-receipt:create/update/delete/export/query/update-status(A 类)、 |
| | | -- erp:account:query、erp:statistics:query(B 类:仅后端 @PreAuthorize)。 |
| | | -- 即结算账户、收款单两页在库中已无权限行,所有角色按钮全隐藏且后端返回 403; |
| | | -- 日后若要恢复这两个页面,需重建菜单并补回上述 13 个权限码。 |
| | | -- **本文件同样无需为此改动**,理由同 ⑤⑥。 |
| | | -- ⑧ 过程检验单 mes_qc_ipqc 字段调整(2026-09-22,IPQC 去生产信息 + 批号必填): |
| | | -- 删 work_order_id / task_id / workstation_id / process_id 四列, |
| | | -- 在 source_doc_code 之后新增 batch_code varchar(100) COMMENT '批号'。 |
| | | -- 同步改了本地 H2 单测建表脚本 create_tables.sql(否则 MesQcIpqcServiceImplTest 全挂)。 |
| | | -- **本文件必须改**:阶段一是全库 CREATE TABLE 字面量导出,不重导出就会建出旧结构。 |
| | | -- 源库 ruoyi-vue-pro 已执行同样的 DDL,结构以本文件为准。 |
| | | -- ⑨ 补 18 个按钮权限(2026-09-22,C2 类:「前端在用、后端 @PreAuthorize 未定义、库中亦无」): |
| | | -- 1075533~1075550,分别为「模板导出」(system:notify-template:export → 2145)、 |
| | | -- 「离职交接」(hrm:resignation:handover → 6005)、「杂项入库单」6 个动作 |
| | | -- (mes:wm-misc-receipt:create/update/delete/export/cancel/finish → 5888)、 |
| | | -- 「图纸审核」1 个、「设计任务」5 个、「版本变更」4 个动作 (mes:pd-* → 602572)。 |
| | | -- 补录时 C2 共 93 个(删「ERP 系统」后由 54 涨上来),其中仅这 18 个能定位到 type=2 父菜单; |
| | | -- 已全部落库并逐个复核「父菜单 type=2、status=0」通过。 |
| | | -- 剩余 75 个**刻意未补**,分三类: |
| | | -- ① 67 个 erp:*(product / product-category / product-unit / warehouse / stock / |
| | | -- stock-in / stock-out / stock-move / stock-check / stock-record / purchase-in / |
| | | -- purchase-return / sale-out / sale-return)—— 对应页面**只有前端、没有后端**: |
| | | -- yudao-module-erp 无任何这些控制器,全仓 grep 也无对应 @PreAuthorize。 |
| | | -- 其中 42 个的父菜单(产品管理/库存管理子树)已随 ⑦ 一并删除,另 25 个 |
| | | -- (采购入库/采购退货/销售出库/销售退货)自建库起就没有菜单。属历史残留,按既定口径跳过。 |
| | | -- ② 7 个 crm:product:* / crm:product-category:* —— 主工作区 yudao-module-crm 下并无 |
| | | -- CrmProductController / CrmProductCategoryController,只存在于两个并行 agent 的 |
| | | -- worktree 副本里(尚未合并);且即便合并也缺「商品」菜单,单补按钮无意义,留给该分支自己补。 |
| | | -- ③ 1 个 mes:wm-approval:save —— 前端 views/wls/approval/config/index.vue 的**码写错了**: |
| | | -- 后端 /mes/wms/approval/config/save 实际要求 mes:wm-warehouse:update(库中已有,id=5784), |
| | | -- 与 ⑦① 的薪酬漂移同类问题。正确修法是改前端这一行,因未获授权改前端故未动。 |
| | | -- (另:该页所挂的父目录 6002「人事审批」是个没有子菜单的空 type=1,本就没有菜单挂到该页面) |
| | | -- **本文件同样无需为此改动**,理由同 ⑤⑥。 |
| | | -- 验证:本文件已在全新空库(MySQL 8.4.6)整份执行通过 —— 419 张表、6 张 qc_report_*、 |
| | | -- 6 个字典类型 + 21 条字典数据、admin 账号 / 1258 条菜单 / 2349 条 role_menu / |
| | | -- 默认 OAuth2 客户端均正确落库。 |
| | | -- 2026-09-22 上述变更全部落地后,源库 ruoyi-vue-pro 现有有效菜单 1308 条(其中按钮 1021 条)、 |
| | | -- 阶段② 将拷贝的 system_role_menu 1708 条(= admin 名下「超级管理员」852 + 「普通角色」856; |
| | | -- 该段子查询不带 deleted 过滤,实测这两角色下无软删行,故 1708 即最终行数),执行时应与此一致。 |
| | | -- 范围:全库 BASE TABLE 共 419 张,全部输出 CREATE TABLE; |
| | | -- 仅以下配置类表带数据(INSERT),其余业务/系统/运行历史表只保留空表结构: |
| | | -- ① 协同审批流程(引擎元数据+定义,仅激活流程,剔历史/运行/身份):act_ge_property、act_id_property、act_re_procdef、act_re_deployment、act_ge_bytearray、act_re_model、bpm_process_definition_info |
| | |
| | | `source_doc_id` bigint DEFAULT NULL COMMENT '来源单据编号', |
| | | `source_line_id` bigint DEFAULT NULL COMMENT '来源单据行编号', |
| | | `source_doc_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源单据编号', |
| | | `work_order_id` bigint DEFAULT NULL COMMENT '生产工单编号', |
| | | `task_id` bigint DEFAULT NULL COMMENT '生产任务编号', |
| | | `workstation_id` bigint DEFAULT NULL COMMENT '工位编号', |
| | | `process_id` bigint DEFAULT NULL COMMENT '工序编号', |
| | | `batch_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '批号', |
| | | `item_id` bigint DEFAULT NULL COMMENT '产品物料编号', |
| | | `check_quantity` decimal(24,6) DEFAULT NULL COMMENT '检测数量', |
| | | `qualified_quantity` decimal(24,6) DEFAULT NULL COMMENT '合格品数量', |
| | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.qc.dto.MesQcReportItemRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.qc.dto.MesQcReportRespDTO; |
| | | 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.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcIndicatorItemTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcResultValueTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcLineService; |
| | |
| | | private MesQcIndicatorService indicatorService; |
| | | |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | @Resource |
| | | private MesProWorkOrderService workOrderService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | |
| | | order.getSourceDocType(), order.getSourceDocCode(), order.getItemId(), order.getCheckQuantity(), |
| | | order.getQualifiedQuantity(), order.getUnqualifiedQuantity(), order.getCheckResult(), |
| | | order.getInspectDate(), order.getStatus(), order.getRemark()); |
| | | resp.setWorkOrderCode(workOrderCode(order.getWorkOrderId())); |
| | | resp.setBatchCode(defaultStr(order.getBatchCode())); |
| | | lines = loadIpqcLines(qcId); |
| | | inspectorUserId = order.getInspectorUserId(); |
| | | } else if (Objects.equals(qcType, MesQcTypeEnum.OQC.getType())) { |
| | |
| | | throw new IllegalArgumentException("暂不支持 qcType=" + qcType); |
| | | } |
| | | |
| | | // 物料与单位:单位优先取行上的(检验时实际用的单位),缺失才回退物料的默认单位 |
| | | MesMdItemDO item = resp.getItemId() == null ? null |
| | | : itemService.getItemMap(Collections.singletonList(resp.getItemId())).get(resp.getItemId()); |
| | | // 物料与单位:物料 ID 来自 MDM;单位优先取行上的(检验时实际用的单位),缺失才回退物料的默认单位 |
| | | MdmItemRespDTO item = resp.getItemId() == null ? null |
| | | : mdmItemApi.getItemMap(Collections.singletonList(resp.getItemId())).get(resp.getItemId()); |
| | | if (item != null) { |
| | | resp.setItemCode(defaultStr(item.getCode())); |
| | | resp.setItemName(defaultStr(item.getName())); |
| | | resp.setItemSpecification(defaultStr(item.getSpecification())); |
| | | } |
| | | Map<Long, MesMdUnitMeasureDO> unitMap = unitMapOf(lines, item); |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMap = unitMapOf(lines, item); |
| | | if (item != null) { |
| | | MesMdUnitMeasureDO itemUnit = unitMap.get(item.getUnitMeasureId()); |
| | | MdmUnitMeasureRespDTO itemUnit = unitMap.get(item.getUnitMeasureId()); |
| | | resp.setUnitName(itemUnit == null ? "" : defaultStr(itemUnit.getName())); |
| | | } |
| | | resp.setInspectorName(userName(inspectorUserId)); |
| | |
| | | * (组名与公式是分组检验项最不该丢的两样东西)。 |
| | | */ |
| | | private BuiltItems buildItems(Long qcId, Integer qcType, List<LineRow> lines, |
| | | Map<Long, MesMdUnitMeasureDO> unitMap, MesMdItemDO item) { |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMap, MdmItemRespDTO item) { |
| | | if (CollUtil.isEmpty(lines)) { |
| | | return new BuiltItems(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), 0); |
| | | } |
| | |
| | | private List<MesQcReportItemRespDTO> expandRows(MesQcIndicatorDO indicator, LineRow line, |
| | | List<MesQcIndicatorResultDO> samples, |
| | | Map<Long, List<MesQcIndicatorResultDetailDO>> detailMap, |
| | | Map<Long, MesMdUnitMeasureDO> unitMap, MesMdItemDO item) { |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMap, MdmItemRespDTO item) { |
| | | List<MesQcReportItemRespDTO> rows = new ArrayList<>(); |
| | | for (MesQcIndicatorResultDO sample : samples) { |
| | | for (MesQcIndicatorResultDetailDO detail : detailMap.getOrDefault(sample.getId(), |
| | |
| | | } |
| | | |
| | | private MesQcReportItemRespDTO toItem(MesQcIndicatorDO indicator, LineRow line, String sampleCode, String value, |
| | | String detailRemark, Map<Long, MesMdUnitMeasureDO> unitMap, |
| | | MesMdItemDO item) { |
| | | MesMdUnitMeasureDO unit = unitMap.get(line.unitMeasureId()); |
| | | String detailRemark, Map<Long, MdmUnitMeasureRespDTO> unitMap, |
| | | MdmItemRespDTO item) { |
| | | MdmUnitMeasureRespDTO unit = unitMap.get(line.unitMeasureId()); |
| | | if (unit == null && item != null) { |
| | | unit = unitMap.get(item.getUnitMeasureId()); |
| | | } |
| | |
| | | |
| | | // ==================== 关联名称 ==================== |
| | | |
| | | private Map<Long, MesMdUnitMeasureDO> unitMapOf(List<LineRow> lines, MesMdItemDO item) { |
| | | private Map<Long, MdmUnitMeasureRespDTO> unitMapOf(List<LineRow> lines, MdmItemRespDTO item) { |
| | | List<Long> unitIds = new ArrayList<>(); |
| | | for (LineRow line : lines) { |
| | | if (line.unitMeasureId() != null) { |
| | |
| | | if (item != null && item.getUnitMeasureId() != null) { |
| | | unitIds.add(item.getUnitMeasureId()); |
| | | } |
| | | return unitIds.isEmpty() ? Collections.emptyMap() : unitMeasureService.getUnitMeasureMap(unitIds); |
| | | return unitIds.isEmpty() ? Collections.emptyMap() : mdmUnitMeasureApi.getUnitMeasureMap(unitIds); |
| | | } |
| | | |
| | | private String supplierName(Long vendorId) { |
| | |
| | | } |
| | | CrmCustomerRespDTO client = customerApi.getCustomerMap(Collections.singleton(clientId)).get(clientId); |
| | | return client == null ? "" : defaultStr(client.getName()); |
| | | } |
| | | |
| | | private String workOrderCode(Long workOrderId) { |
| | | if (workOrderId == null) { |
| | | return ""; |
| | | } |
| | | MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId); |
| | | return workOrder == null ? "" : defaultStr(workOrder.getCode()); |
| | | } |
| | | |
| | | private String userName(Long userId) { |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO; |
| | | 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.md.workstation.MesMdWorkstationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcLineMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService; |
| | |
| | | @Resource |
| | | private MesQcIpqcService ipqcService; |
| | | @Resource |
| | | private MesProWorkOrderService workOrderService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdWorkstationService workstationService; |
| | | @Resource |
| | | private MesProProcessService processService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private MesQcIndicatorResultService indicatorResultService; |
| | | @Resource |
| | |
| | | if (CollUtil.isEmpty(list)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 批量查询工单 |
| | | Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap( |
| | | convertSet(list, MesQcIpqcDO::getWorkOrderId)); |
| | | // 批量查询工位 |
| | | Map<Long, MesMdWorkstationDO> workstationMap = workstationService.getWorkstationMap( |
| | | convertSet(list, MesQcIpqcDO::getWorkstationId)); |
| | | // 批量查询工序 |
| | | Map<Long, MesProProcessDO> processMap = processService.getProcessMap( |
| | | convertSet(list, MesQcIpqcDO::getProcessId)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcIpqcDO::getItemId)); |
| | | // 批量查询计量单位( |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)); |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcIpqcDO::getInspectorUserId)); |
| | |
| | | Map<Long, String> indicatorSummaryMap = buildIndicatorSummaryMap(list); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcIpqcRespVO.class, vo -> { |
| | | findAndThen(workOrderMap, vo.getWorkOrderId(), workOrder -> vo |
| | | .setWorkOrderCode(workOrder.getCode()).setWorkOrderName(workOrder.getName())); |
| | | findAndThen(workstationMap, vo.getWorkstationId(), |
| | | ws -> vo.setWorkstationName(ws.getName())); |
| | | findAndThen(processMap, vo.getProcessId(), |
| | | process -> vo.setProcessName(process.getName())); |
| | | findAndThen(itemMap, vo.getItemId(), item -> { |
| | | vo.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification()); |
| | | findAndThen(unitMeasureMap, item.getUnitMeasureId(), |
| | |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcIpqcDO::getInspectorUserId)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcIpqcDO::getItemId)); |
| | | // 批量查询行明细 |
| | | List<MesQcIpqcLineDO> lines = ipqcLineMapper.selectListByIpqcIds(convertSet(list, MesQcIpqcDO::getId)); |
| | |
| | | for (MesQcIpqcDO ipqc : list) { |
| | | MesQcQualityExportVO vo = new MesQcQualityExportVO(); |
| | | vo.setDocName(ipqc.getName()); |
| | | MesMdItemDO item = itemMap.get(ipqc.getItemId()); |
| | | MdmItemRespDTO item = itemMap.get(ipqc.getItemId()); |
| | | vo.setItemName(item != null ? item.getName() : ""); |
| | | // IPQC 无批次号字段,暂用来源单据编号(一般为生产工单号)作为批号 |
| | | vo.setBatchNo(ipqc.getSourceDocCode()); |
| | | vo.setBatchNo(ipqc.getBatchCode()); |
| | | vo.setInspectDate(MesQcQualityExportVO.formatDateTime(ipqc.getInspectDate())); |
| | | List<String> indicatorNames = new ArrayList<>(); |
| | | List<String> judgementSegments = new ArrayList<>(); |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLinePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLineRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcLineService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得过程检验单行") |
| | |
| | | // 批量查询检测指标 |
| | | Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap( |
| | | convertSet(list, MesQcIpqcLineDO::getIndicatorId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(list, MesQcIpqcLineDO::getUnitMeasureId)); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcIpqcLineRespVO.class, vo -> { |
| | |
| | | @Schema(description = "IPQC 检验类型", example = "1") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "生产工单 ID", example = "10") |
| | | private Long workOrderId; |
| | | @Schema(description = "批号(模糊匹配)", example = "BC2025") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "产品物料 ID", example = "50") |
| | | private Long itemId; |
| | |
| | | @Schema(description = "来源单据编号", example = "FB20250101001") |
| | | private String sourceDocCode; |
| | | |
| | | // ========== 生产关联 ========== |
| | | |
| | | @Schema(description = "生产工单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | private Long workOrderId; |
| | | |
| | | @Schema(description = "工单编号", example = "WO20250101001") |
| | | @ExcelProperty("工单编号") |
| | | private String workOrderCode; |
| | | |
| | | @Schema(description = "工单名称", example = "生产工单A") |
| | | private String workOrderName; |
| | | |
| | | @Schema(description = "生产任务 ID", example = "20") |
| | | private Long taskId; |
| | | |
| | | @Schema(description = "工位 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "30") |
| | | private Long workstationId; |
| | | |
| | | @Schema(description = "工位名称", example = "工位A") |
| | | @ExcelProperty("工位名称") |
| | | private String workstationName; |
| | | |
| | | @Schema(description = "工序 ID", example = "40") |
| | | private Long processId; |
| | | |
| | | @Schema(description = "工序名称", example = "组装") |
| | | @ExcelProperty("工序名称") |
| | | private String processName; |
| | | |
| | | // ========== 物料 ========== |
| | | |
| | | @Schema(description = "批号", requiredMode = Schema.RequiredMode.REQUIRED, example = "BC20250101") |
| | | @ExcelProperty("批号") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "产品物料 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") |
| | | private Long itemId; |
| | | |
| | |
| | | import jakarta.validation.constraints.DecimalMin; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | |
| | | @Schema(description = "来源单据行 ID", example = "300") |
| | | private Long sourceLineId; |
| | | |
| | | // ========== 生产关联 ========== |
| | | |
| | | @Schema(description = "生产工单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @NotNull(message = "生产工单不能为空") |
| | | private Long workOrderId; |
| | | |
| | | @Schema(description = "生产任务 ID", example = "20") |
| | | private Long taskId; |
| | | |
| | | @Schema(description = "工位 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "30") |
| | | @NotNull(message = "工位不能为空") |
| | | private Long workstationId; |
| | | |
| | | @Schema(description = "工序 ID", example = "40") |
| | | private Long processId; |
| | | |
| | | // ========== 物料 ========== |
| | | |
| | | @Schema(description = "产品物料 ID", example = "50") |
| | | @Schema(description = "批号", requiredMode = Schema.RequiredMode.REQUIRED, example = "BC20250101") |
| | | @NotEmpty(message = "批号不能为空") |
| | | @Size(max = 100, message = "批号长度不能超过 100 个字符") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "产品物料 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") |
| | | @NotNull(message = "产品物料不能为空") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "质检方案 ID", example = "5") |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; |
| | | 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.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcLineMapper; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private MesQcIqcLineMapper iqcLineMapper; |
| | | |
| | |
| | | Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList( |
| | | convertSet(list, MesQcIqcDO::getVendorId)).getCheckedData().stream() |
| | | .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcIqcDO::getItemId)); |
| | | // 批量查询计量单位(通过物料的 unitMeasureId) |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | // 批量查询计量单位(来自 MDM,通过物料的 unitMeasureId) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)); |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcIqcDO::getInspectorUserId)); |
| | |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcIqcDO::getInspectorUserId)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcIqcDO::getItemId)); |
| | | // 批量查询行明细 |
| | | List<MesQcIqcLineDO> lines = iqcLineMapper.selectListByIqcIds(convertSet(list, MesQcIqcDO::getId)); |
| | |
| | | for (MesQcIqcDO iqc : list) { |
| | | MesQcQualityExportVO vo = new MesQcQualityExportVO(); |
| | | vo.setDocName(iqc.getName()); |
| | | MesMdItemDO item = itemMap.get(iqc.getItemId()); |
| | | MdmItemRespDTO item = itemMap.get(iqc.getItemId()); |
| | | vo.setItemName(item != null ? item.getName() : ""); |
| | | vo.setBatchNo(iqc.getVendorBatch()); |
| | | vo.setInspectDate(MesQcQualityExportVO.formatDateTime(iqc.getInspectDate())); |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.line.MesQcIqcLinePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.line.MesQcIqcLineRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcLineService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得来料检验单行") |
| | |
| | | // 批量查询检测指标 |
| | | Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap( |
| | | convertSet(list, MesQcIqcLineDO::getIndicatorId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(list, MesQcIqcLineDO::getUnitMeasureId)); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcIqcLineRespVO.class, vo -> { |
| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.*; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcNcrService; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | |
| | | private MesQcNcrService ncrService; |
| | | |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | |
| | | if (CollUtil.isEmpty(list)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcNcrDO::getItemId)); |
| | | // 批量查询评审人 |
| | | Set<Long> reviewerUserIds = convertSet(list, MesQcNcrDO::getReviewerUserId); |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | 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.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcLineMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService; |
| | |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private MesQcIndicatorResultService indicatorResultService; |
| | | @Resource |
| | |
| | | // 批量查询客户 |
| | | Map<Long, CrmCustomerRespDTO> clientMap = customerApi.getCustomerMap( |
| | | convertSet(list, MesQcOqcDO::getClientId)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcOqcDO::getItemId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)); |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcOqcDO::getInspectorUserId)); |
| | |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcOqcDO::getInspectorUserId)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcOqcDO::getItemId)); |
| | | // 批量查询行明细 |
| | | List<MesQcOqcLineDO> lines = oqcLineMapper.selectListByOqcIds(convertSet(list, MesQcOqcDO::getId)); |
| | |
| | | for (MesQcOqcDO oqc : list) { |
| | | MesQcQualityExportVO vo = new MesQcQualityExportVO(); |
| | | vo.setDocName(oqc.getName()); |
| | | MesMdItemDO item = itemMap.get(oqc.getItemId()); |
| | | MdmItemRespDTO item = itemMap.get(oqc.getItemId()); |
| | | vo.setItemName(item != null ? item.getName() : ""); |
| | | vo.setBatchNo(oqc.getBatchCode()); |
| | | vo.setInspectDate(MesQcQualityExportVO.formatDateTime(oqc.getInspectDate())); |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.line.MesQcOqcLinePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.line.MesQcOqcLineRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcLineService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得出货检验单行") |
| | |
| | | // 批量查询检测指标 |
| | | Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap( |
| | | convertSet(list, MesQcOqcLineDO::getIndicatorId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(list, MesQcOqcLineDO::getUnitMeasureId)); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcOqcLineRespVO.class, vo -> { |
| | |
| | | |
| | | // ========== 客户 ========== |
| | | |
| | | @Schema(description = "客户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @Schema(description = "客户 ID", example = "10") |
| | | private Long clientId; |
| | | |
| | | @Schema(description = "客户简称", example = "客户A") |
| | |
| | | @Schema(description = "最大不合格数", example = "0") |
| | | private Integer maxUnqualifiedQuantity; |
| | | |
| | | @Schema(description = "本次出货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") |
| | | @Schema(description = "本次出货数量", example = "100") |
| | | @ExcelProperty("出货数量") |
| | | private BigDecimal outQuantity; |
| | | |
| | |
| | | |
| | | // ========== 客户 ========== |
| | | |
| | | @Schema(description = "客户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @NotNull(message = "客户不能为空") |
| | | @Schema(description = "客户 ID", example = "10") |
| | | private Long clientId; |
| | | |
| | | // ========== 物料 ========== |
| | |
| | | |
| | | // ========== 数量 ========== |
| | | |
| | | @Schema(description = "本次出货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") |
| | | @NotNull(message = "本次出货数量不能为空") |
| | | @Schema(description = "本次出货数量", example = "100") |
| | | @DecimalMin(value = "0", message = "本次出货数量不能小于 0") |
| | | private BigDecimal outQuantity; |
| | | |
| | | @Schema(description = "本次检测数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @NotNull(message = "本次检测数量不能为空") |
| | | @Schema(description = "本次检测数量", example = "10") |
| | | @DecimalMin(value = "0", message = "本次检测数量不能小于 0") |
| | | private BigDecimal checkQuantity; |
| | | |
| | |
| | | @Schema(description = "检测结果", example = "1") |
| | | private Integer checkResult; |
| | | |
| | | @Schema(description = "出货日期", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotNull(message = "出货日期不能为空") |
| | | @Schema(description = "出货日期") |
| | | private LocalDateTime outDate; |
| | | |
| | | @Schema(description = "检测日期", requiredMode = Schema.RequiredMode.REQUIRED) |
| | |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; |
| | | 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.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.pendinginspect.MesQcPendingInspectService; |
| | |
| | | @Resource |
| | | private MesQcPendingInspectService pendingInspectService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private SrmSupplierApi srmSupplierApi; |
| | | @Resource |
| | |
| | | if (CollUtil.isEmpty(list)) { |
| | | return; |
| | | } |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcPendingInspectRespVO::getItemId)); |
| | | // 批量查询计量单位(通过物料的 unitMeasureId) |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | // 批量查询计量单位(来自 MDM,通过物料的 unitMeasureId) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)); |
| | | // 批量查询供应商(IQC 场景) |
| | | Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList( |
| | | convertSet(list, MesQcPendingInspectRespVO::getVendorId)).getCheckedData().stream() |
| | |
| | | @Schema(description = "来源单据编号", example = "AN2025001") |
| | | private String sourceDocCode; |
| | | |
| | | @Schema(description = "批号(仅 IPQC/OQC/RQC 来源单据有值)", example = "BC20250101") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "检验类型(MesQcTypeEnum)", example = "1") |
| | | private Integer qcType; |
| | | |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.vo.MesQcQualityExportVO; |
| | | 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.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcLineMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService; |
| | |
| | | @Resource |
| | | private MesQcRqcService rqcService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private MesQcIndicatorResultService indicatorResultService; |
| | | @Resource |
| | |
| | | if (CollUtil.isEmpty(list)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcRqcDO::getItemId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)); |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcRqcDO::getInspectorUserId)); |
| | |
| | | // 批量查询检测人员 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( |
| | | convertSet(list, MesQcRqcDO::getInspectorUserId)); |
| | | // 批量查询物料 |
| | | Map<Long, MesMdItemDO> itemMap = itemService.getItemMap( |
| | | // 批量查询物料(物料 ID 来自 MDM) |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcRqcDO::getItemId)); |
| | | // 批量查询行明细 |
| | | List<MesQcRqcLineDO> lines = rqcLineMapper.selectListByRqcIds(convertSet(list, MesQcRqcDO::getId)); |
| | |
| | | for (MesQcRqcDO rqc : list) { |
| | | MesQcQualityExportVO vo = new MesQcQualityExportVO(); |
| | | vo.setDocName(rqc.getName()); |
| | | MesMdItemDO item = itemMap.get(rqc.getItemId()); |
| | | MdmItemRespDTO item = itemMap.get(rqc.getItemId()); |
| | | vo.setItemName(item != null ? item.getName() : ""); |
| | | vo.setBatchNo(rqc.getBatchCode()); |
| | | vo.setInspectDate(MesQcQualityExportVO.formatDateTime(rqc.getInspectDate())); |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.line.MesQcRqcLinePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.line.MesQcRqcLineRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcLineService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得退货检验单行") |
| | |
| | | // 批量查询检测指标 |
| | | Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap( |
| | | convertSet(list, MesQcRqcLineDO::getIndicatorId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | // 批量查询计量单位(来自 MDM) |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(list, MesQcRqcLineDO::getUnitMeasureId)); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcRqcLineRespVO.class, vo -> { |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateIndicatorService; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "创建质检方案-检测指标项") |
| | |
| | | Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap( |
| | | convertSet(list, MesQcTemplateIndicatorDO::getIndicatorId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(list, MesQcTemplateIndicatorDO::getUnitMeasureId)); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcTemplateIndicatorRespVO.class, vo -> { |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; |
| | | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | |
| | | @Resource |
| | | private MesQcTemplateService templateService; |
| | | @Resource |
| | | private MesMdmItemService mdmItemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "创建质检方案-产品关联") |
| | |
| | | } |
| | | |
| | | @GetMapping("/list-by-item-and-type") |
| | | @Operation(summary = "根据物料或工单/任务和检测种类获取可用质检方案") |
| | | @Operation(summary = "根据物料和检测种类获取可用质检方案") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-template:query')") |
| | | public CommonResult<List<MesQcTemplateItemOptionRespVO>> getTemplateItemOptionList( |
| | | @RequestParam(value = "qcType", required = false) Integer qcType, |
| | | @RequestParam(value = "itemId", required = false) Long itemId, |
| | | @RequestParam(value = "workOrderId", required = false) Long workOrderId, |
| | | @RequestParam(value = "taskId", required = false) Long taskId) { |
| | | List<MesQcTemplateItemDO> list = templateItemService.getTemplateItemList( |
| | | itemId, qcType, workOrderId, taskId); |
| | | @RequestParam(value = "itemId", required = false) Long itemId) { |
| | | List<MesQcTemplateItemDO> list = templateItemService.getTemplateItemList(itemId, qcType); |
| | | if (CollUtil.isEmpty(list)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | |
| | | return Collections.emptyList(); |
| | | } |
| | | // 批量查询物料(itemId 存的是 mdm_item_id,关联 mdm_item 表) |
| | | Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap( |
| | | Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap( |
| | | convertSet(list, MesQcTemplateItemDO::getItemId)); |
| | | // 批量查询计量单位 |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdmItemDO::getUnitMeasureId)); |
| | | Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)); |
| | | // 拼装 VO |
| | | return BeanUtils.toBean(list, MesQcTemplateItemRespVO.class, vo -> { |
| | | findAndThen(itemMap, vo.getItemId(), item -> { |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcSourceDocTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | |
| | | * 来源单据编号(冗余) |
| | | */ |
| | | private String sourceDocCode; |
| | | |
| | | // ========== 生产关联 ========== |
| | | |
| | | /** |
| | | * 生产工单 ID |
| | | * |
| | | * 关联 {@link MesProWorkOrderDO#getId()} |
| | | * 批号 |
| | | */ |
| | | private Long workOrderId; |
| | | /** |
| | | * 生产任务 ID |
| | | * |
| | | * 关联 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO#getId()} |
| | | */ |
| | | private Long taskId; |
| | | /** |
| | | * 工位 ID |
| | | * |
| | | * 关联 {@link MesMdWorkstationDO#getId()} |
| | | */ |
| | | private Long workstationId; |
| | | /** |
| | | * 工序 ID |
| | | * |
| | | * 关联 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO#getId()} |
| | | */ |
| | | private Long processId; |
| | | private String batchCode; |
| | | |
| | | // ========== 物料 ========== |
| | | |
| | | /** |
| | | * 产品物料 ID |
| | | * |
| | | * 关联 {@link MesMdItemDO#getId()} |
| | | * 关联 MDM 物料 {@link cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO#getId()} |
| | | */ |
| | | private Long itemId; |
| | | |
| | |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<MesQcIpqcDO>() |
| | | .likeIfPresent(MesQcIpqcDO::getCode, reqVO.getCode()) |
| | | .eqIfPresent(MesQcIpqcDO::getType, reqVO.getType()) |
| | | .eqIfPresent(MesQcIpqcDO::getWorkOrderId, reqVO.getWorkOrderId()) |
| | | .likeIfPresent(MesQcIpqcDO::getBatchCode, reqVO.getBatchCode()) |
| | | .eqIfPresent(MesQcIpqcDO::getItemId, reqVO.getItemId()) |
| | | .eqIfPresent(MesQcIpqcDO::getCheckResult, reqVO.getCheckResult()) |
| | | .eqIfPresent(MesQcIpqcDO::getStatus, reqVO.getStatus()) |
| | |
| | | .betweenIfPresent(MesQcOqcDO::getInspectDate, reqVO.getInspectDate()) |
| | | .orderByDesc(MesQcOqcDO::getId); |
| | | if (permittedClientIds != null) { |
| | | // 未填客户的单据不属于任何客户,不受客户权限遮挡 |
| | | if (CollUtil.isEmpty(permittedClientIds)) { |
| | | return PageResult.empty(); |
| | | wrapper.isNull(MesQcOqcDO::getClientId); |
| | | } else { |
| | | wrapper.and(w -> w.isNull(MesQcOqcDO::getClientId) |
| | | .or().in(MesQcOqcDO::getClientId, permittedClientIds)); |
| | | } |
| | | wrapper.in(MesQcOqcDO::getClientId, permittedClientIds); |
| | | } |
| | | return selectPage(reqVO, wrapper); |
| | | } |
| | |
| | | ErrorCode QC_IPQC_SOURCE_LINE_NOT_BELONG = new ErrorCode(1_040_604_009, "来源单据行不属于该报工单"); |
| | | ErrorCode QC_IPQC_SOURCE_LINE_REQUIRED = new ErrorCode(1_040_604_010, "来源单据类型为报工时,来源产出行 ID 不能为空"); |
| | | ErrorCode QC_IPQC_SOURCE_LINE_NOT_PENDING = new ErrorCode(1_040_604_011, "来源产出行不是待检验状态"); |
| | | ErrorCode QC_IPQC_BATCH_CODE_REQUIRED = new ErrorCode(1_040_604_012, "批号不能为空"); |
| | | // ========== MES 质量管理-过程检验行(1-040-604-100) ========== |
| | | ErrorCode QC_IPQC_LINE_NOT_EXISTS = new ErrorCode(1_040_604_100, "过程检验行不存在"); |
| | | |
| | |
| | | data.put("已生产数量", workOrder.getQuantityProduced()); |
| | | fillItemInfo(data, workOrder.getProductId()); |
| | | |
| | | // 质量风险 - IPQC |
| | | List<MesQcIpqcDO> ipqcList = ipqcMapper.selectList( |
| | | new LambdaQueryWrapper<MesQcIpqcDO>() |
| | | .eq(MesQcIpqcDO::getWorkOrderId, workOrderId)); |
| | | // 质量风险 - IPQC(过程检验单已无工单字段,经来源报工单反查本工单下的检验记录) |
| | | List<Long> feedbackIds = feedbackMapper.selectList( |
| | | new LambdaQueryWrapper<MesProFeedbackDO>() |
| | | .eq(MesProFeedbackDO::getWorkOrderId, workOrderId)) |
| | | .stream().map(MesProFeedbackDO::getId).collect(Collectors.toList()); |
| | | List<MesQcIpqcDO> ipqcList = CollUtil.isEmpty(feedbackIds) ? Collections.emptyList() |
| | | : ipqcMapper.selectList(new LambdaQueryWrapper<MesQcIpqcDO>() |
| | | .in(MesQcIpqcDO::getSourceDocId, feedbackIds)); |
| | | if (CollUtil.isNotEmpty(ipqcList)) { |
| | | List<Map<String, Object>> qcData = ipqcList.stream().map(ipqc -> { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | |
| | | data.put("检测数量", ipqc.getCheckQuantity()); |
| | | data.put("合格数量", ipqc.getQualifiedQuantity()); |
| | | data.put("不合格数量", ipqc.getUnqualifiedQuantity()); |
| | | data.put("工单ID", ipqc.getWorkOrderId()); |
| | | data.put("工序ID", ipqc.getProcessId()); |
| | | data.put("批号", ipqc.getBatchCode()); |
| | | fillItemInfo(data, ipqc.getItemId()); |
| | | } else if (MesBizTypeConstants.QC_OQC == qcType) { |
| | | data.put("质检类型", "出货检验"); |
| | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService; |
| | |
| | | @Slf4j |
| | | public class MesQcIpqcServiceImpl implements MesQcIpqcService { |
| | | |
| | | /** |
| | | * IPQC 保存数据校验上下文 |
| | | * |
| | | * @param workstation 工位 |
| | | * @param task 生产任务(可选,指定 taskId 时非空) |
| | | */ |
| | | private record SaveDataContext(MesMdWorkstationDO workstation, MesProTaskDO task) {} |
| | | |
| | | @Resource |
| | | private MesQcIpqcMapper ipqcMapper; |
| | | |
| | |
| | | private MesQcIpqcLineService ipqcLineService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProWorkOrderService workOrderService; |
| | | @Resource |
| | | @Lazy |
| | | private MesMdWorkstationService workstationService; |
| | | @Resource |
| | | @Lazy |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | @Lazy |
| | | private MesQcDefectRecordService defectRecordService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProRouteProductService routeProductService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProRouteProcessService routeProcessService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProTaskService taskService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProFeedbackService feedbackService; |
| | |
| | | private MesQcNcrService ncrService; |
| | | |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createIpqc(MesQcIpqcSaveReqVO createReqVO) { |
| | | // 1.1 校验数据 |
| | | SaveDataContext ctx = validateIpqcSaveData(createReqVO); |
| | | // 1.2 校验工单存在 |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()); |
| | | // 1.3 确定检验物料:有任务时取任务的物料,无任务时取工单的产品 |
| | | Long itemId = (ctx.task() != null) ? ctx.task().getItemId() : workOrder.getProductId(); |
| | | // 1.4 根据物料 + IPQC 类型查找可用质检方案(支持前端显式指定) |
| | | validateIpqcSaveData(createReqVO); |
| | | // 1.2 产品物料由前端指定(MDM 物料),校验其存在 |
| | | Long itemId = createReqVO.getItemId(); |
| | | // 1.3 根据物料 + IPQC 类型查找可用质检方案(支持前端显式指定) |
| | | List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList( |
| | | itemId, MesQcTypeEnum.IPQC.getType(), null, null); |
| | | itemId, MesQcTypeEnum.IPQC.getType()); |
| | | if (CollUtil.isEmpty(templateItems)) { |
| | | throw exception(QC_NO_TEMPLATE); |
| | | } |
| | |
| | | // 2. 插入主表 |
| | | MesQcIpqcDO ipqc = BeanUtils.toBean(createReqVO, MesQcIpqcDO.class) |
| | | .setItemId(itemId) |
| | | .setProcessId(getProcessId(itemId, ctx.workstation().getProcessId())) |
| | | .setTemplateId(templateId) |
| | | .setSourceDocCode(sourceDocCode) |
| | | .setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | |
| | | // 1.1 校验存在 + 草稿状态 |
| | | validateIpqcStatusPrepare(updateReqVO.getId()); |
| | | // 1.2 校验数据 |
| | | SaveDataContext ctx = validateIpqcSaveData(updateReqVO); |
| | | validateIpqcSaveData(updateReqVO); |
| | | |
| | | // 2. 更新主表 |
| | | MesQcIpqcDO existIpqc = validateIpqcExists(updateReqVO.getId()); |
| | | MesQcIpqcDO updateObj = BeanUtils.toBean(updateReqVO, MesQcIpqcDO.class); |
| | | updateObj.setSourceDocType(null).setSourceDocId(null).setSourceLineId(null); // 不允许修改来源单据 |
| | | updateObj.setTemplateId(null); // 不允许修改模板 |
| | | updateObj.setItemId(null); // 不允许修改物料 |
| | | updateObj.setWorkOrderId(null); // 不允许修改工单 |
| | | updateObj.setProcessId(getProcessId(existIpqc.getItemId(), ctx.workstation().getProcessId())); |
| | | ipqcMapper.updateById(updateObj); |
| | | } |
| | | |
| | | private SaveDataContext validateIpqcSaveData(MesQcIpqcSaveReqVO reqVO) { |
| | | private void validateIpqcSaveData(MesQcIpqcSaveReqVO reqVO) { |
| | | // 校验编号唯一 |
| | | validateIpqcCodeUnique(reqVO.getId(), reqVO.getCode()); |
| | | // 校验工位、检测人员存在 |
| | | MesMdWorkstationDO workstation = workstationService.validateWorkstationExists(reqVO.getWorkstationId()); |
| | | // 校验批号非空(VO 层已校验,此处兜底内部/RPC 调用绕过校验的情况) |
| | | if (StrUtil.isBlank(reqVO.getBatchCode())) { |
| | | throw exception(QC_IPQC_BATCH_CODE_REQUIRED); |
| | | } |
| | | // 校验产品物料存在(物料 ID 来自 MDM) |
| | | mdmItemApi.validateItemExists(reqVO.getItemId()); |
| | | // 校验检测人员存在 |
| | | adminUserApi.validateUser(reqVO.getInspectorUserId()); |
| | | // 校验工单存在 |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId()); |
| | | if (reqVO.getProcessId() != null && ObjUtil.notEqual(workstation.getProcessId(), reqVO.getProcessId())) { |
| | | throw exception(PRO_WORKSTATION_PROCESS_MISMATCH); |
| | | } |
| | | // 校验任务关系(如果指定了任务) |
| | | MesProTaskDO task = null; |
| | | if (reqVO.getTaskId() != null) { |
| | | task = taskService.validateTaskNotFinished(reqVO.getTaskId()); |
| | | validateTaskRelation(task, workstation, workOrder, reqVO); |
| | | } else { |
| | | // 无任务时,itemId 仍需与工单产品一致 |
| | | if (reqVO.getItemId() != null && ObjUtil.notEqual(reqVO.getItemId(), workOrder.getProductId())) { |
| | | throw exception(PRO_WORK_ORDER_PRODUCT_MISMATCH); |
| | | } |
| | | } |
| | | return new SaveDataContext(workstation, task); |
| | | } |
| | | |
| | | private void validateTaskRelation(MesProTaskDO task, MesMdWorkstationDO workstation, |
| | | MesProWorkOrderDO workOrder, MesQcIpqcSaveReqVO reqVO) { |
| | | if (ObjUtil.notEqual(task.getWorkOrderId(), workOrder.getId())) { |
| | | throw exception(PRO_TASK_WORK_ORDER_MISMATCH); |
| | | } |
| | | if (ObjUtil.notEqual(task.getWorkstationId(), workstation.getId())) { |
| | | throw exception(PRO_TASK_WORKSTATION_MISMATCH); |
| | | } |
| | | Long expectedProcessId = reqVO.getProcessId() != null ? reqVO.getProcessId() : workstation.getProcessId(); |
| | | if (expectedProcessId != null && ObjUtil.notEqual(task.getProcessId(), expectedProcessId)) { |
| | | throw exception(PRO_TASK_ROUTE_PROCESS_MISMATCH); |
| | | } |
| | | // 校验检验物料与任务物料一致(有任务时,IPQC 检验的物料应与任务加工的物料一致) |
| | | if (reqVO.getItemId() != null && ObjUtil.notEqual(reqVO.getItemId(), task.getItemId())) { |
| | | throw exception(PRO_TASK_ITEM_MISMATCH); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | // 1. 确定 sourceDocType/sourceDocId |
| | | Long sourceDocId = ipqc.getSourceDocId(); |
| | | Long sourceLineId = ipqc.getSourceLineId(); |
| | | |
| | | // 如果 IPQC 单没有设置来源,尝试通过 taskId 查找关联的待检验报工 |
| | | if (sourceDocId == null && ipqc.getTaskId() != null) { |
| | | MesProFeedbackDO feedback = feedbackService.getFeedbackListByTaskId(ipqc.getTaskId()).stream() |
| | | .filter(f -> MesProFeedbackStatusEnum.UNCHECK.getStatus().equals(f.getStatus())) |
| | | .findFirst().orElse(null); |
| | | if (feedback != null) { |
| | | sourceDocId = feedback.getId(); |
| | | log.info("[writeBackSourceDoc] IPQC({}) 未设置来源单据,通过 taskId({}) 找到待检验报工({})", |
| | | ipqc.getId(), ipqc.getTaskId(), feedback.getId()); |
| | | } |
| | | } |
| | | |
| | | if (sourceDocId == null) { |
| | | log.warn("[writeBackSourceDoc] IPQC({}) 无法确定来源报工,跳过回写", ipqc.getId()); |
| | |
| | | } |
| | | // 未知来源类型应报错,而不是静默忽略 |
| | | throw exception(QC_IPQC_SOURCE_DOC_TYPE_UNKNOWN); |
| | | } |
| | | |
| | | /** |
| | | * 推导工序 ID |
| | | * |
| | | * <p>通过产品 → 工艺路线产品 → 工艺路线 → 校验工位工序是否在该路线中 |
| | | * <p>如果工位的工序在路线中,则返回该 processId;否则返回 null |
| | | * |
| | | * @param productId 产品 ID(工单产品) |
| | | * @param workstationProcessId 工位关联的工序 ID |
| | | * @return 推导出的工序 ID,无法推导时返回 null |
| | | */ |
| | | private Long getProcessId(Long productId, Long workstationProcessId) { |
| | | if (productId == null || workstationProcessId == null) { |
| | | return null; |
| | | } |
| | | // 1. 产品 → 工艺路线 |
| | | MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(productId); |
| | | if (routeProduct == null) { |
| | | return null; |
| | | } |
| | | // 2. 校验工位的工序是否在该路线中 |
| | | if (routeProcessService.getRouteProcessByRouteIdAndProcessId( |
| | | routeProduct.getRouteId(), workstationProcessId) == null) { |
| | | return null; |
| | | } |
| | | return workstationProcessId; |
| | | } |
| | | |
| | | @Override |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService; |
| | |
| | | @Resource |
| | | private SrmSupplierApi srmSupplierApi; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | @Lazy |
| | | private MesQcIndicatorResultService indicatorResultService; |
| | | |
| | |
| | | createReqVO.getSourceDocType(), createReqVO.getSourceDocId(), createReqVO.getSourceLineId()); |
| | | // 1.3 根据产品 + IQC 类型查找可用质检方案(支持前端显式指定) |
| | | List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList( |
| | | createReqVO.getItemId(), MesQcTypeEnum.IQC.getType(), null, null); |
| | | createReqVO.getItemId(), MesQcTypeEnum.IQC.getType()); |
| | | if (CollUtil.isEmpty(templateItems)) { |
| | | throw exception(QC_NO_TEMPLATE); |
| | | } |
| | |
| | | createReqVO.getSourceDocType(), createReqVO.getSourceLineId()); |
| | | // 1.3 根据产品 + OQC 类型查找可用质检方案(支持前端显式指定) |
| | | List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList( |
| | | createReqVO.getItemId(), MesQcTypeEnum.OQC.getType(), null, null); |
| | | createReqVO.getItemId(), MesQcTypeEnum.OQC.getType()); |
| | | if (CollUtil.isEmpty(templateItems)) { |
| | | throw exception(QC_NO_TEMPLATE); |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueService; |
| | |
| | | private MesQcRqcLineService rqcLineService; |
| | | @Resource |
| | | @Lazy |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | @Lazy |
| | | private MesQcDefectRecordService defectRecordService; |
| | |
| | | validateRqcSaveData(createReqVO); |
| | | // 1.2 根据物料 + RQC 检测种类查找可用质检方案(支持前端显式指定) |
| | | List<MesQcTemplateItemDO> templateItems = templateItemService.getTemplateItemList( |
| | | createReqVO.getItemId(), MesQcTypeEnum.RQC.getType(), null, null); |
| | | createReqVO.getItemId(), MesQcTypeEnum.RQC.getType()); |
| | | if (CollUtil.isEmpty(templateItems)) { |
| | | throw exception(QC_NO_TEMPLATE); |
| | | } |
| | |
| | | private void validateRqcSaveData(MesQcRqcSaveReqVO reqVO) { |
| | | // 校验编号唯一 |
| | | validateRqcCodeUnique(reqVO.getId(), reqVO.getCode()); |
| | | // 校验物料、检测人员存在 |
| | | itemService.validateItemExists(reqVO.getItemId()); |
| | | // 校验物料(物料 ID 来自 MDM)、检测人员存在 |
| | | mdmItemApi.validateItemExists(reqVO.getItemId()); |
| | | adminUserApi.validateUser(reqVO.getInspectorUserId()); |
| | | } |
| | | |
| | |
| | | MesQcTemplateItemDO getRequiredTemplateByItemIdAndType(Long itemId, Integer qcType); |
| | | |
| | | /** |
| | | * 根据产品物料(或工单/任务)和 QC 类型,获取所有可用的质检方案产品关联列表 |
| | | * 根据产品物料和 QC 类型,获取所有可用的质检方案产品关联列表 |
| | | * |
| | | * @param itemId 产品物料 ID(mes_md_item.id);与 workOrderId/taskId 至少其一 |
| | | * @param qcType 检测种类(枚举 {@link MesQcTypeEnum}) |
| | | * @param workOrderId 生产工单 ID(可选,IPQC 场景用于解析产品物料) |
| | | * @param taskId 生产任务 ID(可选,优先用于解析产品物料) |
| | | * @param itemId 产品物料 ID(MDM 物料 ID,即 mdm_item.id) |
| | | * @param qcType 检测种类(枚举 {@link MesQcTypeEnum}) |
| | | * @return 匹配的产品关联列表(保持原顺序),无匹配返回空列表 |
| | | */ |
| | | List<MesQcTemplateItemDO> getTemplateItemList(Long itemId, Integer qcType, Long workOrderId, Long taskId); |
| | | List<MesQcTemplateItemDO> getTemplateItemList(Long itemId, Integer qcType); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateItemMapper; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | @Resource |
| | | @Lazy |
| | | private MesQcTemplateService templateService; |
| | | @Resource |
| | | private MesMdItemService mesMdItemService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProTaskService proTaskService; |
| | | @Resource |
| | | @Lazy |
| | | private MesProWorkOrderService proWorkOrderService; |
| | | |
| | | @Override |
| | | public Long createTemplateItem(MesQcTemplateItemSaveReqVO createReqVO) { |
| | |
| | | |
| | | @Override |
| | | public MesQcTemplateItemDO getRequiredTemplateByItemIdAndType(Long itemId, Integer qcType) { |
| | | List<MesQcTemplateItemDO> templateItems = getTemplateItemList(itemId, qcType, null, null); |
| | | List<MesQcTemplateItemDO> templateItems = getTemplateItemList(itemId, qcType); |
| | | if (CollUtil.isEmpty(templateItems)) { |
| | | throw exception(QC_NO_TEMPLATE); |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public List<MesQcTemplateItemDO> getTemplateItemList(Long itemId, Integer qcType, Long workOrderId, Long taskId) { |
| | | // 1. 解析真实产品物料 ID:任务优先,其次工单,最后直接用入参 |
| | | Long resolvedItemId = itemId; |
| | | if (taskId != null) { |
| | | resolvedItemId = proTaskService.validateTaskExists(taskId).getItemId(); |
| | | } else if (workOrderId != null) { |
| | | resolvedItemId = proWorkOrderService.validateWorkOrderExists(workOrderId).getProductId(); |
| | | } |
| | | if (resolvedItemId == null) { |
| | | public List<MesQcTemplateItemDO> getTemplateItemList(Long itemId, Integer qcType) { |
| | | // 1. 产品关联表 item_id 存的是 MDM 物料 ID(mdm_item.id),质检侧统一按 MDM 物料查询 |
| | | if (itemId == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 2. mes_md_item.id 转 mdm_item.id(产品关联表存的是 mdm 物料) |
| | | MesMdItemDO item = mesMdItemService.getItem(resolvedItemId); |
| | | if (item == null || item.getMdmItemId() == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 3. 查出该物料关联的所有产品关联 |
| | | List<MesQcTemplateItemDO> templateItems = templateItemMapper.selectListByItemId(item.getMdmItemId()); |
| | | List<MesQcTemplateItemDO> templateItems = templateItemMapper.selectListByItemId(itemId); |
| | | if (CollUtil.isEmpty(templateItems)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 4. 过滤:方案启用且检测种类包含 qcType,返回全部匹配项 |
| | | // 2. 过滤:方案启用且检测种类包含 qcType,返回全部匹配项 |
| | | List<MesQcTemplateDO> templates = templateService.getTemplateList( |
| | | convertSet(templateItems, MesQcTemplateItemDO::getTemplateId)); |
| | | Set<Long> matchedTemplateIds = templates.stream() |
| | |
| | | n.id AS sourceDocId, |
| | | ${@cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants@WM_ARRIVAL_NOTICE} AS sourceDocType, |
| | | n.code AS sourceDocCode, |
| | | NULL AS batchCode, |
| | | l.id AS sourceLineId, |
| | | n.arrival_date AS recordTime, |
| | | ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@IQC.type} AS qcType, |
| | |
| | | r.id AS sourceDocId, |
| | | ${@cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants@WM_OUTSOURCE_RECPT} AS sourceDocType, |
| | | r.code AS sourceDocCode, |
| | | NULL AS batchCode, |
| | | rl.id AS sourceLineId, |
| | | r.receipt_date AS recordTime, |
| | | ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@IQC.type} AS qcType, |
| | |
| | | pf.id AS sourceDocId, |
| | | ${@cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants@PRO_FEEDBACK} AS sourceDocType, |
| | | pf.code AS sourceDocCode, |
| | | ppl.batch_code AS batchCode, |
| | | ppl.id AS sourceLineId, |
| | | pf.feedback_time AS recordTime, |
| | | ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@IPQC.type} AS qcType, |
| | |
| | | s.id AS sourceDocId, |
| | | ${@cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants@WM_PRODUCT_SALES} AS sourceDocType, |
| | | s.code AS sourceDocCode, |
| | | sl.batch_code AS batchCode, |
| | | sl.id AS sourceLineId, |
| | | s.sales_date AS recordTime, |
| | | ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@OQC.type} AS qcType, |
| | |
| | | ri.id AS sourceDocId, |
| | | ${@cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants@WM_RETURN_ISSUE} AS sourceDocType, |
| | | ri.code AS sourceDocCode, |
| | | ril.batch_code AS batchCode, |
| | | ril.id AS sourceLineId, |
| | | ri.return_date AS recordTime, |
| | | ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@RQC.type} AS qcType, |
| | |
| | | r.id AS sourceDocId, |
| | | ${@cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants@WM_RETURN_SALES} AS sourceDocType, |
| | | r.code AS sourceDocCode, |
| | | rl.batch_code AS batchCode, |
| | | rl.id AS sourceLineId, |
| | | r.return_date AS recordTime, |
| | | ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@RQC.type} AS qcType, |
| | |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcNcrService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | |
| | | import org.springframework.test.context.bean.override.mockito.MockitoBean; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| | | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; |
| | |
| | | @MockitoBean |
| | | private MesQcIpqcLineService ipqcLineService; |
| | | @MockitoBean |
| | | private MesProWorkOrderService workOrderService; |
| | | @MockitoBean |
| | | private MesMdWorkstationService workstationService; |
| | | @MockitoBean |
| | | private MesMdItemService itemService; |
| | | private MdmItemApi mdmItemApi; |
| | | @MockitoBean |
| | | private MesQcDefectRecordService defectRecordService; |
| | | @MockitoBean |
| | |
| | | @MockitoBean |
| | | private MesProFeedbackService feedbackService; |
| | | @MockitoBean |
| | | private MesProRouteProductService routeProductService; |
| | | @MockitoBean |
| | | private MesProRouteProcessService routeProcessService; |
| | | @MockitoBean |
| | | private MesProTaskService taskService; |
| | | @MockitoBean |
| | | private MesQcIndicatorResultService indicatorResultService; |
| | | @MockitoBean |
| | | private MesWmProductProduceLineService productProduceLineService; |
| | | @MockitoBean |
| | | private MesQcNcrService ncrService; |
| | | |
| | | @Test |
| | | public void testFinishIpqc_writeBack_feedback() { |
| | |
| | | |
| | | @Test |
| | | public void testFinishIpqc_writeBack_sourceDocTypeDirtyData() { |
| | | // 准备数据:sourceDocType 非空但 sourceDocId 为空,应该抛异常 |
| | | // 准备数据:sourceDocType 非空但 sourceDocId 为空,跳过回写 |
| | | MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setCheckResult(1); |
| | | o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); |
| | | o.setSourceDocId(null); // 脏数据 |
| | | o.setSourceLineId(null); |
| | | o.setUnqualifiedQuantity(BigDecimal.ZERO); |
| | | }); |
| | | ipqcMapper.insert(ipqc); |
| | | |
| | | // 调用,并断言异常 |
| | | assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId())); |
| | | // 调用:不抛异常,仅跳过回写 |
| | | ipqcService.finishIpqc(ipqc.getId()); |
| | | |
| | | MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId()); |
| | | assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus()); |
| | | verify(feedbackService, never()).updateProFeedbackWhenIpqcFinish( |
| | | anyLong(), anyLong(), any(), any(), any(), any(), any()); |
| | | } |
| | | |
| | | @Test |
| | | public void testFinishIpqc_writeBack_unknownSourceDocType() { |
| | | // 准备数据:sourceDocType 无法识别 |
| | | // 准备数据:sourceDocType 无法识别,回写阶段不校验类型(仅按 sourceDocId 回写报工) |
| | | MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setCheckResult(1); |
| | | o.setSourceDocType(99999); // 无法识别的类型 |
| | | o.setSourceDocId(randomLongId()); |
| | | o.setSourceLineId(null); |
| | | o.setSourceLineId(randomLongId()); |
| | | o.setUnqualifiedQuantity(BigDecimal.ZERO); |
| | | }); |
| | | ipqcMapper.insert(ipqc); |
| | | |
| | | // 调用,并断言异常 |
| | | assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId())); |
| | | // 调用:按 sourceDocId 回写报工 |
| | | ipqcService.finishIpqc(ipqc.getId()); |
| | | |
| | | MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId()); |
| | | assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus()); |
| | | verify(feedbackService).updateProFeedbackWhenIpqcFinish( |
| | | anyLong(), anyLong(), any(), any(), any(), any(), any()); |
| | | } |
| | | |
| | | @Test |
| | |
| | | assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId())); |
| | | } |
| | | |
| | | // ==================== processId 推导行为测试 ==================== |
| | | // ==================== 批号必填校验 ==================== |
| | | |
| | | /** |
| | | * 工位有工序 + 该工序在产品工艺路线中 → processId 应该被设置为工位工序 |
| | | * 批号为 null → 创建失败 |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_processId_workstationHasProcess() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long processId = randomLongId(); |
| | | Long routeId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | public void testCreateIpqc_batchCode_null() { |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-TEST-001"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setCode("IPQC-BC-001"); |
| | | reqVO.setBatchCode(null); |
| | | reqVO.setItemId(randomLongId()); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | |
| | | // mock 工位返回有工序 |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(processId); |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | // mock 工单 |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | // mock 模板匹配 |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | // mock 工艺路线 |
| | | MesProRouteProductDO routeProduct = new MesProRouteProductDO(); |
| | | routeProduct.setRouteId(routeId); |
| | | when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct); |
| | | // mock 工序在路线中 |
| | | when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId)) |
| | | .thenReturn(new MesProRouteProcessDO()); |
| | | |
| | | // 调用 |
| | | Long ipqcId = ipqcService.createIpqc(reqVO); |
| | | |
| | | // 断言:processId 应该是工位工序 |
| | | MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId); |
| | | assertEquals(processId, ipqc.getProcessId()); |
| | | ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO)); |
| | | assertEquals(QC_IPQC_BATCH_CODE_REQUIRED.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | /** |
| | | * 工位没有工序(processId=null)→ processId 应该留 null(不回退到任务工序) |
| | | * 批号为空白串 → 创建失败(兜底内部/RPC 调用绕过 VO 校验的情况) |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_processId_workstationNoProcess_shouldBeNull() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long taskId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | public void testCreateIpqc_batchCode_blank() { |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-TEST-002"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setTaskId(taskId); // 故意传入 taskId,验证不会回退 |
| | | reqVO.setCode("IPQC-BC-002"); |
| | | reqVO.setBatchCode(" "); |
| | | reqVO.setItemId(randomLongId()); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | |
| | | // mock 工位没有工序 |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(null); // 无工序 |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | // mock 工单 |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | // mock 任务 |
| | | MesProTaskDO task = new MesProTaskDO(); |
| | | task.setId(taskId); |
| | | task.setWorkOrderId(workOrderId); |
| | | task.setWorkstationId(workstationId); |
| | | task.setItemId(productId); |
| | | when(taskService.validateTaskNotFinished(taskId)).thenReturn(task); |
| | | // mock 模板匹配 |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO)); |
| | | assertEquals(QC_IPQC_BATCH_CODE_REQUIRED.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | // ==================== 创建成功:批号 / 物料落库 ==================== |
| | | |
| | | @Test |
| | | public void testCreateIpqc_success() { |
| | | // 准备参数 |
| | | Long itemId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-OK-001"); |
| | | reqVO.setName("测试过程检验"); |
| | | reqVO.setBatchCode("BC-2025-001"); |
| | | reqVO.setItemId(itemId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | mockTemplate(itemId, templateId); |
| | | |
| | | // 调用 |
| | | Long ipqcId = ipqcService.createIpqc(reqVO); |
| | | |
| | | // 断言:processId 应该是 null,即使传入了 taskId 也不应该回退 |
| | | // 断言:批号与物料落库,模板按物料+IPQC 类型匹配 |
| | | MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId); |
| | | assertNull(ipqc.getProcessId(), |
| | | "工位无工序时 processId 应为 null,不应回退到任务工序"); |
| | | // 断言:不应该读取任务的 processId |
| | | verify(routeProductService, never()).getRouteProductByItemId(any()); |
| | | assertNotNull(ipqc); |
| | | assertEquals("BC-2025-001", ipqc.getBatchCode()); |
| | | assertEquals(itemId, ipqc.getItemId()); |
| | | assertEquals(templateId, ipqc.getTemplateId()); |
| | | verify(mdmItemApi).validateItemExists(itemId); |
| | | } |
| | | |
| | | /** |
| | | * 工位有工序但该工序不在产品工艺路线中 → processId 应该留 null |
| | | * 传入了不在匹配列表中的 templateId → 拒绝创建 |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_processId_processNotInRoute() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long processId = randomLongId(); |
| | | Long routeId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | public void testCreateIpqc_templateId_notMatched() { |
| | | Long itemId = randomLongId(); |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-TEST-003"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setCode("IPQC-TPL-001"); |
| | | reqVO.setName("指定方案不匹配"); |
| | | reqVO.setBatchCode("BC-2025-002"); |
| | | reqVO.setItemId(itemId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | reqVO.setTemplateId(randomLongId()); // 与匹配结果不一致 |
| | | mockTemplate(itemId, randomLongId()); |
| | | |
| | | // mock 工位有工序 |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(processId); |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | // mock 工单 |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | // mock 模板匹配 |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | // mock 工艺路线存在 |
| | | MesProRouteProductDO routeProduct = new MesProRouteProductDO(); |
| | | routeProduct.setRouteId(routeId); |
| | | when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct); |
| | | // mock 工序不在路线中 |
| | | when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId)) |
| | | .thenReturn(null); |
| | | |
| | | // 调用 |
| | | Long ipqcId = ipqcService.createIpqc(reqVO); |
| | | |
| | | // 断言:processId 应该是 null |
| | | MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId); |
| | | assertNull(ipqc.getProcessId()); |
| | | ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO)); |
| | | assertEquals(QC_NO_TEMPLATE.getCode(), ex.getCode()); |
| | | } |
| | | |
| | | // ==================== sourceLineId 校验测试 ==================== |
| | |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_sourceLineId_required() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long itemId = randomLongId(); |
| | | Long feedbackId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-SL-001"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); |
| | | reqVO.setSourceDocId(feedbackId); |
| | | reqVO.setSourceLineId(null); // 不传 sourceLineId |
| | | |
| | | // mock |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(null); |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-001", itemId, feedbackId, null); |
| | | mockTemplate(itemId, randomLongId()); |
| | | when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO()); |
| | | |
| | | // 调用,断言异常 |
| | |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_sourceLineId_notBelong() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long itemId = randomLongId(); |
| | | Long feedbackId = randomLongId(); |
| | | Long sourceLineId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-SL-002"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); |
| | | reqVO.setSourceDocId(feedbackId); |
| | | reqVO.setSourceLineId(sourceLineId); |
| | | |
| | | // mock |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(null); |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-002", itemId, feedbackId, sourceLineId); |
| | | mockTemplate(itemId, randomLongId()); |
| | | when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO()); |
| | | // mock:产出行存在但 feedbackId 不匹配 |
| | | MesWmProductProduceLineDO line = new MesWmProductProduceLineDO(); |
| | |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_sourceLineId_notPending() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long itemId = randomLongId(); |
| | | Long feedbackId = randomLongId(); |
| | | Long sourceLineId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-SL-003"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); |
| | | reqVO.setSourceDocId(feedbackId); |
| | | reqVO.setSourceLineId(sourceLineId); |
| | | |
| | | // mock |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(null); |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-003", itemId, feedbackId, sourceLineId); |
| | | mockTemplate(itemId, randomLongId()); |
| | | when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO()); |
| | | // mock:产出行存在且归属正确,但不是 PENDING 状态 |
| | | MesWmProductProduceLineDO line = new MesWmProductProduceLineDO(); |
| | |
| | | */ |
| | | @Test |
| | | public void testCreateIpqc_sourceLineId_success() { |
| | | // 准备参数 |
| | | Long workstationId = randomLongId(); |
| | | Long workOrderId = randomLongId(); |
| | | Long productId = randomLongId(); |
| | | Long itemId = randomLongId(); |
| | | Long feedbackId = randomLongId(); |
| | | Long sourceLineId = randomLongId(); |
| | | Long templateId = randomLongId(); |
| | | |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode("IPQC-SL-004"); |
| | | reqVO.setWorkstationId(workstationId); |
| | | reqVO.setWorkOrderId(workOrderId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); |
| | | reqVO.setSourceDocId(feedbackId); |
| | | reqVO.setSourceLineId(sourceLineId); |
| | | |
| | | // mock |
| | | MesMdWorkstationDO workstation = new MesMdWorkstationDO(); |
| | | workstation.setId(workstationId); |
| | | workstation.setProcessId(null); |
| | | when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation); |
| | | MesProWorkOrderDO workOrder = new MesProWorkOrderDO(); |
| | | workOrder.setId(workOrderId); |
| | | workOrder.setProductId(productId); |
| | | when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder); |
| | | when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder); |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem); |
| | | MesQcIpqcSaveReqVO reqVO = buildFeedbackReqVO("IPQC-SL-004", itemId, feedbackId, sourceLineId); |
| | | mockTemplate(itemId, randomLongId()); |
| | | MesProFeedbackDO feedback = new MesProFeedbackDO(); |
| | | feedback.setId(feedbackId); |
| | | feedback.setCode("FB-001"); |
| | |
| | | assertEquals("FB-001", ipqc.getSourceDocCode()); |
| | | } |
| | | |
| | | // ==================== 测试辅助 ==================== |
| | | |
| | | private MesQcIpqcSaveReqVO buildFeedbackReqVO(String code, Long itemId, Long feedbackId, Long sourceLineId) { |
| | | MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO(); |
| | | reqVO.setCode(code); |
| | | reqVO.setName("来源校验过程检验"); |
| | | reqVO.setBatchCode("BC-SL-001"); |
| | | reqVO.setItemId(itemId); |
| | | reqVO.setInspectorUserId(randomLongId()); |
| | | reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); |
| | | reqVO.setSourceDocId(feedbackId); |
| | | reqVO.setSourceLineId(sourceLineId); |
| | | return reqVO; |
| | | } |
| | | |
| | | private void mockTemplate(Long itemId, Long templateId) { |
| | | MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO(); |
| | | templateItem.setTemplateId(templateId); |
| | | when(templateItemService.getTemplateItemList(eq(itemId), anyInt())) |
| | | .thenReturn(List.of(templateItem)); |
| | | } |
| | | |
| | | } |
| | |
| | | "source_doc_id" bigint DEFAULT NULL, |
| | | "source_line_id" bigint DEFAULT NULL, |
| | | "source_doc_code" varchar(64) DEFAULT NULL, |
| | | "work_order_id" bigint DEFAULT NULL, |
| | | "task_id" bigint DEFAULT NULL, |
| | | "workstation_id" bigint DEFAULT NULL, |
| | | "process_id" bigint DEFAULT NULL, |
| | | "batch_code" varchar(100) DEFAULT NULL, |
| | | "item_id" bigint DEFAULT NULL, |
| | | "check_quantity" decimal(14,2) DEFAULT NULL, |
| | | "qualified_quantity" decimal(14,2) DEFAULT NULL, |
| | |
| | | .setProductCode(StrUtil.nullToEmpty(data.getItemCode())) |
| | | .setProductName(StrUtil.nullToEmpty(data.getItemName())) |
| | | .setSpec(StrUtil.nullToEmpty(data.getItemSpecification())) |
| | | // 来料检验的批次在供应商批号上,出货/退货在批次号上,过程检验两者都没有(只有工单) |
| | | // 过程/出货/退货检验的批次取自单据的批号,来料检验没有批号则回落到供应商批号 |
| | | .setBatchNo(firstNonBlank(data.getBatchCode(), data.getVendorBatch())) |
| | | .setWorkOrderNo(StrUtil.nullToEmpty(data.getWorkOrderCode())) |
| | | .setInspectType(StrUtil.nullToEmpty(data.getQcTypeName())) |