From dc3f3053034cec5ee8a6ca73daedb648e573e632 Mon Sep 17 00:00:00 2001 From: geht <2947093423@qq.com> Date: Mon, 25 May 2026 19:44:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B7=B7=E7=82=BC=E7=A4=BA?= =?UTF-8?q?=E6=96=B9=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=A7=8D=E7=B1=BB=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 2 + jeecg-boot/.vscode/settings.json | 2 + jeecg-boot/.vscode/tasks.json | 18 + .../jeecg-module-xslmes/doc/代码修改日志 | 93 +++- .../jeecg-module-xslmes/pom.xml | 1 + .../MesXslMixerMaterialKindCfgController.java | 177 +++++++ .../MesXslMixingSpecController.java | 55 ++ .../entity/MesXslMixerMaterialKindCfg.java | 89 ++++ .../MesXslMixerMaterialKindCfgMapper.java | 9 + .../IMesXslMixerMaterialKindCfgService.java | 26 + .../impl/MesXslFormulaSpecServiceImpl.java | 288 ++++++++++- ...MesXslMixerMaterialKindCfgServiceImpl.java | 298 +++++++++++ .../impl/MesXslMixingSpecServiceImpl.java | 472 ++++++++++++++++-- .../service/impl/SysCategoryServiceImpl.java | 28 +- ...2_102__mes_xsl_mixer_material_kind_cfg.sql | 91 ++++ ...s_xsl_mixer_material_kind_cfg_menu_fix.sql | 67 +++ .../MesMixerMaterialSysCategoryModal.vue | 8 +- .../system/category/category.constants.ts | 271 +++++++++- .../views/system/category/category.data.ts | 4 +- .../category/components/CategoryModal.vue | 6 +- .../MesXslEquipmentLedgerMultiSelectModal.vue | 1 + .../MesXslEquipmentLedgerSelectModal.vue | 3 +- .../MesXslFormulaSpec.api.ts | 5 +- .../MesXslMixerMaterialKindCfg.api.ts | 36 ++ .../MesXslMixerMaterialKindCfg.data.ts | 156 ++++++ .../MesXslMixerMaterialKindCfgList.vue | 162 ++++++ .../MesXslMixerMaterialKindCfgBatchModal.vue | 189 +++++++ .../MesXslMixerMaterialKindCfgEditModal.vue | 52 ++ .../mesXslMixingSpec/MesXslMixingSpec.api.ts | 5 +- .../mesXslMixingSpec/MesXslMixingSpec.data.ts | 394 ++++++++++++++- .../MesXslMixingMaterialCategorySetting.vue | 221 ++++++++ .../MesXslMixingMaterialSelectModal.vue | 406 +++++++++++++++ .../components/MesXslMixingSpecModal.vue | 359 ++++++++++++- qhmes.code-workspace | 2 + 34 files changed, 3892 insertions(+), 104 deletions(-) create mode 100644 jeecg-boot/.vscode/tasks.json create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialKindCfgController.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialKindCfg.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslMixerMaterialKindCfgMapper.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialKindCfgService.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialKindCfgServiceImpl.java create mode 100644 jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_102__mes_xsl_mixer_material_kind_cfg.sql create mode 100644 jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_103__mes_xsl_mixer_material_kind_cfg_menu_fix.sql create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.api.ts create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.data.ts create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList.vue create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgBatchModal.vue create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgEditModal.vue create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue diff --git a/.vscode/settings.json b/.vscode/settings.json index ed09f41..71c4c28 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,8 @@ "java.import.maven.enabled": true, "java.configuration.updateBuildConfiguration": "automatic", "java.autobuild.enabled": true, + "java.import.maven.offline.enabled": false, + "java.configuration.maven.notCoveredPluginExecutionSeverity": "ignore", "java.jdt.ls.java.home": "C:\\Program Files\\Java\\jdk-17", "java.configuration.runtimes": [ { diff --git a/jeecg-boot/.vscode/settings.json b/jeecg-boot/.vscode/settings.json index 6dcea9b..28ded36 100644 --- a/jeecg-boot/.vscode/settings.json +++ b/jeecg-boot/.vscode/settings.json @@ -4,6 +4,8 @@ "java.import.maven.enabled": true, "java.configuration.updateBuildConfiguration": "automatic", "java.autobuild.enabled": true, + "java.import.maven.offline.enabled": false, + "java.configuration.maven.notCoveredPluginExecutionSeverity": "ignore", "java.jdt.ls.java.home": "C:\\Program Files\\Java\\jdk-17", "java.configuration.runtimes": [ { diff --git a/jeecg-boot/.vscode/tasks.json b/jeecg-boot/.vscode/tasks.json new file mode 100644 index 0000000..8fd137b --- /dev/null +++ b/jeecg-boot/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Maven: 修复 Java Classpath", + "type": "shell", + "command": "mvn clean install -DskipTests -pl jeecg-boot-base-core,jeecg-boot-module/jeecg-module-print,jeecg-boot-module/jeecg-module-xslmes -am", + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build", + "isDefault": false + }, + "problemMatcher": [] + } + ] +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 index f980ca7..a8cdacb 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 @@ -186,6 +186,95 @@ jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipm jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingStepSelectCell.vue jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue --- author:cursor---date:20260522--for: 【XSLMES-20260522-A37】未选机台点击动作/组合提示请先选择机台 --- -jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingStepSelectCell.vue +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A43】混炼示方换算系数联动橡胶及配合剂单重实时计算 --- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A42】混炼示方橡胶及配合剂明细底部固定合计行 --- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A41】混炼示方橡胶及配合剂明细累计按种类分组合计 --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A44】配合示方生成混炼示方时按设备台账有效体积自动计算填充体积 --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java +jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerMultiSelectModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A45】混炼示方保存/批量生成子表改批量插入并延长前端超时 --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.api.ts +jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.api.ts + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A46】混炼示方换算系数/单重/机台有效体积联动填充体积 --- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A47】混炼示方保存分步骤性能诊断日志 --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixingSpecController.java + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A48】生成混炼示方时同步B/F段胶至密炼物料(母炼胶/A胶、终炼胶/Q胶) --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A49】删除混炼示方时同步删除B/F段胶密炼物料 --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】MES物料小类胶料标记/生成混炼示方种类映射/混炼示方选料弹窗 --- +jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysCategoryServiceImpl.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java +jeecgboot-vue3/src/views/system/category/category.constants.ts +jeecgboot-vue3/src/views/system/category/category.data.ts +jeecgboot-vue3/src/views/system/category/components/CategoryModal.vue +jeecgboot-vue3/src/views/mes/material/modules/MesMixerMaterialSysCategoryModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】选料弹窗左侧小类树为空修复(对齐密炼物料列表树加载/异步展开/隐藏配置重置) --- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】选料弹窗小类设置无明细/统一分类加载函数 ----------- +jeecgboot-vue3/src/views/system/category/category.constants.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】选料弹窗分类加载改为getChildListBatch两级查询 ----------- +jeecgboot-vue3/src/views/system/category/category.constants.ts + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】选料弹窗openModal未传data导致初始化未执行 ----------- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】选料弹窗移出父Modal+useModalInner初始化 ----------- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】小类展示分组勾选互不覆盖 ----------- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A50】选料弹窗隐藏ERP列并新增自动/人工称量列 ----------- +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A51】密炼物料种类配置表及列表批量新增 ----------- +jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_102__mes_xsl_mixer_material_kind_cfg.sql +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialKindCfg.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslMixerMaterialKindCfgMapper.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialKindCfgService.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialKindCfgServiceImpl.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialKindCfgController.java +jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.data.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.api.ts +jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgBatchModal.vue +jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgEditModal.vue + +-- author:cursor---date:20260525--for: 【XSLMES-20260525-A51】密炼物料种类配置菜单ID冲突修复 ----------- +jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_102__mes_xsl_mixer_material_kind_cfg.sql +jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_103__mes_xsl_mixer_material_kind_cfg_menu_fix.sql diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/pom.xml b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/pom.xml index e3170f1..e6b3b7f 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/pom.xml +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/pom.xml @@ -17,6 +17,7 @@ org.jeecgframework.boot3 jeecg-boot-base-core + ${jeecgboot.version} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialKindCfgController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialKindCfgController.java new file mode 100644 index 0000000..f1e86f9 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerMaterialKindCfgController.java @@ -0,0 +1,177 @@ +package org.jeecg.modules.xslmes.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.jeecg.common.api.vo.Result; +import org.jeecg.common.aspect.annotation.AutoLog; +import org.jeecg.common.constant.CommonConstant; +import org.jeecg.common.system.base.controller.JeecgController; +import org.jeecg.common.system.query.QueryGenerator; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialKindCfg; +import org.jeecg.modules.xslmes.service.IMesXslMixerMaterialKindCfgService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; + +/** + * MES 密炼物料种类配置 + */ +@Tag(name = "MES密炼物料种类配置") +@RestController +@RequestMapping("/xslmes/mesXslMixerMaterialKindCfg") +@Slf4j +public class MesXslMixerMaterialKindCfgController + extends JeecgController { + + @Autowired + private IMesXslMixerMaterialKindCfgService mesXslMixerMaterialKindCfgService; + + @Operation(summary = "MES密炼物料种类配置-分页列表查询") + @GetMapping(value = "/list") + public Result> queryPageList( + MesXslMixerMaterialKindCfg model, + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest req) { + QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(model, req.getParameterMap()); + queryWrapper.orderByAsc("priority").orderByDesc("create_time"); + Page page = new Page<>(pageNo, pageSize); + IPage pageList = mesXslMixerMaterialKindCfgService.page(page, queryWrapper); + return Result.OK(pageList); + } + + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置展开与批量保存----------- + @Operation(summary = "MES密炼物料种类配置-按根字典/分类展开明细") + @GetMapping(value = "/expandLines") + public Result> expandLines( + @RequestParam(name = "sourceType") String sourceType, + @RequestParam(name = "sourceRootCode") String sourceRootCode, + @RequestParam(name = "tenantId", required = false) Integer tenantId) { + List lines = + mesXslMixerMaterialKindCfgService.expandLines(sourceType, sourceRootCode, tenantId); + return Result.OK(lines); + } + + @AutoLog(value = "MES密炼物料种类配置-批量添加") + @Operation(summary = "MES密炼物料种类配置-批量添加") + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:add") + @PostMapping(value = "/addBatch") + public Result addBatch(@RequestBody List lines) { + mesXslMixerMaterialKindCfgService.saveBatchLines(lines, null); + return Result.OK("添加成功!"); + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置展开与批量保存----------- + + @AutoLog(value = "MES密炼物料种类配置-添加") + @Operation(summary = "MES密炼物料种类配置-添加") + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:add") + @PostMapping(value = "/add") + public Result add(@RequestBody MesXslMixerMaterialKindCfg model) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置保存校验----------- + String err = validateForSave(model, false); + if (err != null) { + return Result.error(err); + } + model.setDelFlag(CommonConstant.DEL_FLAG_0); + mesXslMixerMaterialKindCfgService.save(model); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置保存校验----------- + return Result.OK("添加成功!"); + } + + @AutoLog(value = "MES密炼物料种类配置-编辑") + @Operation(summary = "MES密炼物料种类配置-编辑") + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:edit") + @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST}) + public Result edit(@RequestBody MesXslMixerMaterialKindCfg model) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置保存校验----------- + String err = validateForSave(model, true); + if (err != null) { + return Result.error(err); + } + mesXslMixerMaterialKindCfgService.updateById(model); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置保存校验----------- + return Result.OK("编辑成功!"); + } + + @AutoLog(value = "MES密炼物料种类配置-删除") + @Operation(summary = "MES密炼物料种类配置-通过id删除") + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:delete") + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam(name = "id", required = true) String id) { + mesXslMixerMaterialKindCfgService.removeById(id); + return Result.OK("删除成功!"); + } + + @AutoLog(value = "MES密炼物料种类配置-批量删除") + @Operation(summary = "MES密炼物料种类配置-批量删除") + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:deleteBatch") + @DeleteMapping(value = "/deleteBatch") + public Result deleteBatch(@RequestParam(name = "ids", required = true) String ids) { + mesXslMixerMaterialKindCfgService.removeByIds(Arrays.asList(ids.split(","))); + return Result.OK("批量删除成功!"); + } + + @Operation(summary = "MES密炼物料种类配置-通过id查询") + @GetMapping(value = "/queryById") + public Result queryById(@RequestParam(name = "id", required = true) String id) { + MesXslMixerMaterialKindCfg entity = mesXslMixerMaterialKindCfgService.getById(id); + if (entity == null) { + return Result.error("未找到对应数据"); + } + return Result.OK(entity); + } + + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:exportXls") + @RequestMapping(value = "/exportXls") + public ModelAndView exportXls(HttpServletRequest request, MesXslMixerMaterialKindCfg model) { + return super.exportXls(request, model, MesXslMixerMaterialKindCfg.class, "密炼物料种类配置"); + } + + @RequiresPermissions("xslmes:mes_xsl_mixer_material_kind_cfg:importExcel") + @RequestMapping(value = "/importExcel", method = RequestMethod.POST) + public Result> importExcel(HttpServletRequest request, HttpServletResponse response) { + return super.importExcel(request, response, MesXslMixerMaterialKindCfg.class); + } + + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置保存校验----------- + private String validateForSave(MesXslMixerMaterialKindCfg model, boolean isUpdate) { + if (model == null) { + return "参数不能为空"; + } + if (oConvertUtils.isEmpty(model.getKindKey())) { + return "种类键值不能为空"; + } + if (oConvertUtils.isEmpty(model.getKindName())) { + return "种类名称不能为空"; + } + if (oConvertUtils.isEmpty(model.getSourceType())) { + return "数据源类型不能为空"; + } + if (oConvertUtils.isEmpty(model.getCategoryRefId())) { + return "对应分类不能为空"; + } + if (model.getPriority() == null) { + model.setPriority(999); + } + if (isUpdate && oConvertUtils.isEmpty(model.getId())) { + return "主键不能为空"; + } + try { + mesXslMixerMaterialKindCfgService.checkDuplicate(model, isUpdate ? model.getId() : null); + } catch (Exception e) { + return e.getMessage(); + } + return null; + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置保存校验----------- +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixingSpecController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixingSpecController.java index 52700e2..f35e9ef 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixingSpecController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixingSpecController.java @@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.List; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.jeecg.common.api.vo.Result; import org.jeecg.common.aspect.annotation.AutoLog; @@ -30,6 +31,7 @@ import org.springframework.web.servlet.ModelAndView; @Tag(name = "MES混炼示方") @RestController @RequestMapping("/xslmes/mesXslMixingSpec") +@Slf4j public class MesXslMixingSpecController extends JeecgController { @Autowired @@ -59,13 +61,36 @@ public class MesXslMixingSpecController extends JeecgController add(@RequestBody MesXslMixingSpecPage page) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + long requestStartMs = System.currentTimeMillis(); + log.info( + "[混炼示方保存性能] 接口/add 收到请求 specName={}, payload=[material={}, step={}, downStep={}, tcu={}]", + page != null ? page.getSpecName() : null, + countChildRows(page != null ? page.getMaterialList() : null), + countChildRows(page != null ? page.getStepList() : null), + countChildRows(page != null ? page.getDownStepList() : null), + countChildRows(page != null ? page.getTcuList() : null)); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- String err = validateMain(page); if (err != null) { return Result.error(err); } + long validateMs = System.currentTimeMillis(); MesXslMixingSpec main = new MesXslMixingSpec(); BeanUtils.copyProperties(page, main); + long copyMs = System.currentTimeMillis(); mesXslMixingSpecService.saveMain(main, page.getMaterialList(), page.getStepList(), page.getDownStepList(), page.getTcuList()); + long serviceMs = System.currentTimeMillis(); + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + log.info( + "[混炼示方保存性能] 接口/add 完成 specName={}, mainId={}, validate={}ms, copy={}ms, service={}ms, total={}ms(返回响应前,不含AutoLog等切面)", + main.getSpecName(), + main.getId(), + validateMs - requestStartMs, + copyMs - validateMs, + serviceMs - copyMs, + serviceMs - requestStartMs); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- return Result.OK("添加成功!"); } @@ -74,13 +99,37 @@ public class MesXslMixingSpecController extends JeecgController edit(@RequestBody MesXslMixingSpecPage page) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + long requestStartMs = System.currentTimeMillis(); + log.info( + "[混炼示方保存性能] 接口/edit 收到请求 id={}, specName={}, payload=[material={}, step={}, downStep={}, tcu={}]", + page != null ? page.getId() : null, + page != null ? page.getSpecName() : null, + countChildRows(page != null ? page.getMaterialList() : null), + countChildRows(page != null ? page.getStepList() : null), + countChildRows(page != null ? page.getDownStepList() : null), + countChildRows(page != null ? page.getTcuList() : null)); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- String err = validateMain(page); if (err != null) { return Result.error(err); } + long validateMs = System.currentTimeMillis(); MesXslMixingSpec main = new MesXslMixingSpec(); BeanUtils.copyProperties(page, main); + long copyMs = System.currentTimeMillis(); mesXslMixingSpecService.updateMain(main, page.getMaterialList(), page.getStepList(), page.getDownStepList(), page.getTcuList()); + long serviceMs = System.currentTimeMillis(); + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + log.info( + "[混炼示方保存性能] 接口/edit 完成 id={}, specName={}, validate={}ms, copy={}ms, service={}ms, total={}ms(返回响应前,不含AutoLog等切面)", + main.getId(), + main.getSpecName(), + validateMs - requestStartMs, + copyMs - validateMs, + serviceMs - copyMs, + serviceMs - requestStartMs); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- return Result.OK("编辑成功!"); } @@ -151,5 +200,11 @@ public class MesXslMixingSpecController extends JeecgController rows) { + return rows == null ? 0 : rows.size(); + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A17】混炼示方主子保存校验----------- } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialKindCfg.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialKindCfg.java new file mode 100644 index 0000000..f1b5188 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerMaterialKindCfg.java @@ -0,0 +1,89 @@ +package org.jeecg.modules.xslmes.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.jeecgframework.poi.excel.annotation.Excel; +import org.springframework.format.annotation.DateTimeFormat; + +/** + * MES 密炼物料种类配置 + */ +@Data +@TableName("mes_xsl_mixer_material_kind_cfg") +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +@Schema(description = "MES密炼物料种类配置") +public class MesXslMixerMaterialKindCfg implements Serializable { + + private static final long serialVersionUID = 1L; + + public static final String SOURCE_TYPE_DICT = "dict"; + public static final String SOURCE_TYPE_CATEGORY = "category"; + + @TableId(type = IdType.ASSIGN_ID) + private String id; + + @Excel(name = "种类键值", width = 15) + @Schema(description = "种类键值") + private String kindKey; + + @Excel(name = "种类名称", width = 20) + @Schema(description = "种类名称") + private String kindName; + + @Excel(name = "数据源类型", width = 12) + @Schema(description = "数据源类型:dict/category") + private String sourceType; + + @Excel(name = "根编码", width = 15) + @Schema(description = "根字典编码或分类pcode") + private String sourceRootCode; + + @Excel(name = "根名称", width = 20) + @Schema(description = "根名称冗余") + private String sourceRootName; + + @Schema(description = "对应分类/字典项ID") + private String categoryRefId; + + @Excel(name = "对应编码", width = 15) + @Schema(description = "对应分类编码/字典项值") + private String categoryRefCode; + + @Excel(name = "对应分类", width = 20) + @Schema(description = "对应分类名称冗余") + private String categoryRefName; + + @Excel(name = "优先级", width = 10) + @Schema(description = "优先级(数字越小越优先)") + private Integer priority; + + @Schema(description = "租户ID") + private Integer tenantId; + + private String sysOrgCode; + + @Excel(name = "创建人", width = 15) + private String createBy; + + @Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private String updateBy; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + private Integer delFlag; +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslMixerMaterialKindCfgMapper.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslMixerMaterialKindCfgMapper.java new file mode 100644 index 0000000..b938f65 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslMixerMaterialKindCfgMapper.java @@ -0,0 +1,9 @@ +package org.jeecg.modules.xslmes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialKindCfg; + +/** + * MES 密炼物料种类配置 + */ +public interface MesXslMixerMaterialKindCfgMapper extends BaseMapper {} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialKindCfgService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialKindCfgService.java new file mode 100644 index 0000000..83a7593 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerMaterialKindCfgService.java @@ -0,0 +1,26 @@ +package org.jeecg.modules.xslmes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import java.util.List; +import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialKindCfg; + +/** + * MES 密炼物料种类配置 + */ +public interface IMesXslMixerMaterialKindCfgService extends IService { + + /** + * 按根字典/分类展开明细行(未持久化) + */ + List expandLines(String sourceType, String sourceRootCode, Integer tenantId); + + /** + * 批量新增 + */ + void saveBatchLines(List lines, Integer tenantId); + + /** + * 校验租户内种类键值/对应分类是否重复 + */ + void checkDuplicate(MesXslMixerMaterialKindCfg line, String excludeId); +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java index 883b7f0..5f96f9a 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslFormulaSpecServiceImpl.java @@ -24,9 +24,11 @@ import org.jeecg.modules.mes.material.entity.MesMaterial; import org.jeecg.modules.mes.material.entity.MesMixerMaterial; import org.jeecg.modules.mes.material.service.IMesMaterialService; import org.jeecg.modules.mes.material.service.IMesMixerMaterialService; +import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.system.entity.SysCategory; import org.jeecg.modules.system.service.ISysCategoryService; +import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger; import org.jeecg.modules.xslmes.entity.MesXslFormulaSpec; import org.jeecg.modules.xslmes.entity.MesXslFormulaSpecLine; import org.jeecg.modules.xslmes.entity.MesXslMixerPsCompile; @@ -34,6 +36,7 @@ import org.jeecg.modules.xslmes.entity.MesXslMixingSpec; import org.jeecg.modules.xslmes.entity.MesXslMixingSpecMaterial; import org.jeecg.modules.xslmes.mapper.MesXslFormulaSpecLineMapper; import org.jeecg.modules.xslmes.mapper.MesXslFormulaSpecMapper; +import org.jeecg.modules.xslmes.service.IMesXslEquipmentLedgerService; import org.jeecg.modules.xslmes.service.IMesXslFormulaSpecService; import org.jeecg.modules.xslmes.service.IMesXslFormulaSpecSettingService; import org.jeecg.modules.xslmes.service.IMesXslMixingSpecService; @@ -44,16 +47,24 @@ import org.jeecg.modules.xslmes.vo.MesXslFormulaMixingGenerateRequestVO; import org.jeecg.modules.xslmes.vo.MesXslFormulaMixingGenerateRowVO; import org.jeecg.modules.system.entity.SysUser; import org.jeecg.modules.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @Service +@Slf4j public class MesXslFormulaSpecServiceImpl extends ServiceImpl implements IMesXslFormulaSpecService { private static final Set RUBBER_CATEGORY_KEYS = Set.of("S", "P", "T", "C"); private static final Pattern RUBBER_CODE_VERSION_PATTERN = Pattern.compile("([A-Z])01$"); + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料----------- + private static final String CATEGORY_MASTER_MAJOR = "XSLMES_MATERIAL_MASTER"; + private static final String CATEGORY_MASTER_MINOR_A = "XSLMES_MATERIAL_MASTER_A"; + private static final String CATEGORY_FINAL_MAJOR = "XSLMES_MATERIAL_FINAL"; + private static final String CATEGORY_FINAL_MINOR_Q = "XSLMES_MATERIAL_FINAL_Q"; + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料----------- @Resource private MesXslFormulaSpecLineMapper lineMapper; @@ -73,6 +84,9 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl mixerCache = new HashMap<>(); Map categoryNameCache = new HashMap<>(); Map categoryRubberCache = new HashMap<>(); + Map equipmentCache = new HashMap<>(); + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A44】F段终炼胶比重按配合示方整体PHR/体积汇总----------- + BigDecimal compoundSpecificGravity = resolveCompoundSpecificGravity(formula, lines); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A44】F段终炼胶比重按配合示方整体PHR/体积汇总----------- int created = 0; int totalStages = normalizeMixingStages(formula.getMixingStages()); Date today = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()); + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料----------- + Set syncedRubberSpecCodes = new HashSet<>(); + Map categoryIdCache = new HashMap<>(); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料----------- for (MesXslFormulaMixingGenerateRowVO row : request.getRows()) { if (row == null || oConvertUtils.isEmpty(row.getSpecCode()) || row.getStageIndex() == null) { continue; @@ -525,6 +547,11 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl materials = buildMixingMaterials( @@ -547,11 +574,25 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl lines) { + if (CollectionUtils.isEmpty(lines)) { + return null; + } + BigDecimal totalPhr = formula != null && formula.getTotalPhr() != null && formula.getTotalPhr().compareTo(BigDecimal.ZERO) > 0 + ? formula.getTotalPhr() + : BigDecimal.ZERO; + if (totalPhr.compareTo(BigDecimal.ZERO) <= 0) { + for (MesXslFormulaSpecLine line : lines) { + if (line != null && line.getPhr() != null) { + totalPhr = totalPhr.add(line.getPhr()); + } + } + } + BigDecimal totalVolume = BigDecimal.ZERO; + for (MesXslFormulaSpecLine line : lines) { + BigDecimal volume = resolveLineVolume(line); + if (volume != null) { + totalVolume = totalVolume.add(volume); + } + } + if (totalPhr.compareTo(BigDecimal.ZERO) <= 0 || totalVolume.compareTo(BigDecimal.ZERO) <= 0) { + return null; + } + return totalPhr.divide(totalVolume, 6, RoundingMode.HALF_UP); + } + + /** 解析设备台账有效体积(支持纯数字或带单位的字符串) */ + private BigDecimal parseEffectiveVolume(String raw, String machineLabel) { + if (StringUtils.isBlank(raw)) { + throw new IllegalArgumentException("机台「" + machineLabel + "」未维护有效体积,无法计算填充体积"); + } + String trimmed = raw.trim(); + try { + BigDecimal value = new BigDecimal(trimmed); + if (value.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("机台「" + machineLabel + "」有效体积必须大于 0"); + } + return value; + } catch (NumberFormatException ignored) { + Matcher matcher = Pattern.compile("([0-9]+(?:\\.[0-9]+)?)").matcher(trimmed); + if (matcher.find()) { + BigDecimal value = new BigDecimal(matcher.group(1)); + if (value.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("机台「" + machineLabel + "」有效体积必须大于 0"); + } + return value; + } + throw new IllegalArgumentException("机台「" + machineLabel + "」有效体积格式无效:" + raw); + } + } + + /** 橡胶及配合剂明细单重合计 */ + private BigDecimal sumMaterialUnitWeight(List materials) { + if (CollectionUtils.isEmpty(materials)) { + return BigDecimal.ZERO; + } + BigDecimal total = BigDecimal.ZERO; + for (MesXslMixingSpecMaterial material : materials) { + if (material != null && material.getUnitWeight() != null) { + total = total.add(material.getUnitWeight()); + } + } + return total; + } + + /** + * 填充体积(%) = 本段单重合计 ÷ 本段比重 ÷ 机台有效体积(L) × 100 × 换算系数 + */ + private BigDecimal calcFillVolume( + BigDecimal totalWeight, + BigDecimal specificGravity, + BigDecimal effectiveVolume, + BigDecimal convertFactor) { + if (totalWeight == null || totalWeight.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("本段橡胶及配合剂单重合计无效,无法计算填充体积"); + } + if (specificGravity == null || specificGravity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("本段比重无效,无法计算填充体积"); + } + if (effectiveVolume == null || effectiveVolume.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("机台有效体积无效,无法计算填充体积"); + } + BigDecimal factor = convertFactor == null || convertFactor.compareTo(BigDecimal.ZERO) <= 0 + ? BigDecimal.ONE + : convertFactor; + BigDecimal materialVolume = totalWeight.divide(specificGravity, 6, RoundingMode.HALF_UP); + return materialVolume + .divide(effectiveVolume, 6, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)) + .multiply(factor) + .setScale(6, RoundingMode.HALF_UP); + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A44】生成混炼示方时按设备有效体积计算填充体积----------- + + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料----------- + /** + * 生成混炼示方时,将 B 段/F 段胶料同步写入密炼物料: + * B 段 -> 物料大类「母炼胶」+ 小类「A胶」;F 段 -> 物料大类「终炼胶」+ 小类「Q胶」。 + */ + private void syncGeneratedRubberMixerMaterial( + MesXslFormulaMixingGenerateRowVO row, + MesXslFormulaSpec formula, + BigDecimal compoundSpecificGravity, + Map categoryIdCache) { + if (row == null || StringUtils.isBlank(row.getSpecCode())) { + return; + } + String specCode = row.getSpecCode().trim(); + boolean isFinalStage = "Q".equalsIgnoreCase(resolveMixingMaterialStep(row)); + String majorCode = isFinalStage ? CATEGORY_FINAL_MAJOR : CATEGORY_MASTER_MAJOR; + String minorCode = isFinalStage ? CATEGORY_FINAL_MINOR_Q : CATEGORY_MASTER_MINOR_A; + String majorCategoryId = resolveCategoryIdByCode(majorCode, categoryIdCache); + String minorCategoryId = resolveCategoryIdByCode(minorCode, categoryIdCache); + if (StringUtils.isBlank(majorCategoryId) || StringUtils.isBlank(minorCategoryId)) { + throw new IllegalArgumentException( + "未找到物料分类「" + (isFinalStage ? "终炼胶/Q胶" : "母炼胶/A胶") + "」,请先维护 MES 物料分类字典"); + } + BigDecimal specificGravity = isFinalStage ? compoundSpecificGravity : formula.getARubberSg(); + String rubberName = resolveRubberName(formula); + String materialDesc = StringUtils.isNotBlank(formula.getPurpose()) ? formula.getPurpose().trim() : rubberName; + + MesMixerMaterial existing = findMixerMaterialByCodeOrName(specCode); + Date now = new Date(); + if (existing != null) { + existing.setMajorCategoryId(majorCategoryId); + existing.setMinorCategoryId(minorCategoryId); + existing.setSpecificGravity(specificGravity); + if (StringUtils.isNotBlank(materialDesc)) { + existing.setMaterialDesc(materialDesc); + } + if (StringUtils.isBlank(existing.getMaterialCode())) { + existing.setMaterialCode(specCode); + } + if (StringUtils.isBlank(existing.getMaterialName())) { + existing.setMaterialName(specCode); + } + existing.setUseStatus(existing.getUseStatus() == null ? 1 : existing.getUseStatus()); + existing.setUpdateTime(now); + mesMixerMaterialService.updateById(existing); + log.info( + "[混炼示方生成] 更新密炼物料 id={}, code={}, major={}, minor={}, sg={}", + existing.getId(), + specCode, + majorCode, + minorCode, + specificGravity); + return; + } + + MesMixerMaterial material = new MesMixerMaterial(); + material.setMaterialCode(specCode); + material.setMaterialName(specCode); + material.setMaterialDesc(materialDesc); + material.setAliasName(StringUtils.isNotBlank(rubberName) ? rubberName : null); + material.setMajorCategoryId(majorCategoryId); + material.setMinorCategoryId(minorCategoryId); + material.setSpecificGravity(specificGravity); + material.setFeedManageStatus(0); + material.setUseStatus(1); + material.setDelFlag(CommonConstant.DEL_FLAG_0); + material.setCreateTime(now); + material.setUpdateTime(now); + mesMixerMaterialService.save(material); + log.info( + "[混炼示方生成] 新增密炼物料 id={}, code={}, major={}, minor={}, sg={}", + material.getId(), + specCode, + majorCode, + minorCode, + specificGravity); + } + + private MesMixerMaterial findMixerMaterialByCodeOrName(String specCode) { + MesMixerMaterial byCode = mesMixerMaterialService.getOne( + new LambdaQueryWrapper() + .eq(MesMixerMaterial::getMaterialCode, specCode) + .and(w -> w.eq(MesMixerMaterial::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesMixerMaterial::getDelFlag)) + .last("limit 1")); + if (byCode != null) { + return byCode; + } + return mesMixerMaterialService.getOne( + new LambdaQueryWrapper() + .eq(MesMixerMaterial::getMaterialName, specCode) + .and(w -> w.eq(MesMixerMaterial::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesMixerMaterial::getDelFlag)) + .last("limit 1")); + } + + private String resolveCategoryIdByCode(String categoryCode, Map cache) { + if (StringUtils.isBlank(categoryCode)) { + return null; + } + if (cache != null && cache.containsKey(categoryCode)) { + return cache.get(categoryCode); + } + SysCategory category = sysCategoryService.getOne( + new LambdaQueryWrapper().eq(SysCategory::getCode, categoryCode.trim()).last("limit 1")); + String categoryId = category != null ? category.getId() : null; + if (cache != null) { + cache.put(categoryCode, categoryId); + } + return categoryId; + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A48】生成混炼示方同步B/F段胶至密炼物料----------- + private int normalizeMixingStages(Integer mixingStages) { if (mixingStages == null || mixingStages <= 0) { return 0; @@ -861,7 +1110,7 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl mixerCache, Map categoryNameCache, Map categoryRubberCache) { @@ -894,18 +1144,27 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl categoryRubberCache) { + String weighMode, String minorCategoryId, String minorCategoryName, Map categoryRubberCache) { + String weighKind = resolveWeighModeMaterialKind(weighMode); + if (StringUtils.isNotBlank(weighKind)) { + return weighKind; + } if (isCategoryRubber(minorCategoryId, categoryRubberCache)) { return "胶料"; } @@ -915,6 +1174,23 @@ public class MesXslFormulaSpecServiceImpl extends ServiceImpl 混炼示方种类 */ + private String resolveWeighModeMaterialKind(String weighMode) { + if (StringUtils.isBlank(weighMode)) { + return null; + } + String normalized = weighMode.trim(); + String lower = normalized.toLowerCase(); + if (lower.startsWith("auto") || normalized.contains("自动")) { + return "自动"; + } + if ("manual".equals(lower) || normalized.contains("人工")) { + return "人工"; + } + return null; + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】生成混炼示方时称量方式优先映射种类----------- + private boolean isCategoryRubber(String categoryId, Map cache) { if (oConvertUtils.isEmpty(categoryId)) { return false; diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialKindCfgServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialKindCfgServiceImpl.java new file mode 100644 index 0000000..306c3e4 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerMaterialKindCfgServiceImpl.java @@ -0,0 +1,298 @@ +package org.jeecg.modules.xslmes.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.jeecg.common.constant.CommonConstant; +import org.jeecg.common.exception.JeecgBootException; +import org.jeecg.common.util.SpringContextUtils; +import org.jeecg.common.util.TokenUtils; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.common.config.TenantContext; +import org.jeecg.config.mybatis.MybatisPlusSaasConfig; +import org.jeecg.modules.system.entity.SysCategory; +import org.jeecg.modules.system.entity.SysDict; +import org.jeecg.modules.system.entity.SysDictItem; +import org.jeecg.modules.system.service.ISysCategoryService; +import org.jeecg.modules.system.service.ISysDictItemService; +import org.jeecg.modules.system.service.ISysDictService; +import org.jeecg.modules.xslmes.entity.MesXslMixerMaterialKindCfg; +import org.jeecg.modules.xslmes.mapper.MesXslMixerMaterialKindCfgMapper; +import org.jeecg.modules.xslmes.service.IMesXslMixerMaterialKindCfgService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * MES 密炼物料种类配置 + */ +@Service +public class MesXslMixerMaterialKindCfgServiceImpl + extends ServiceImpl + implements IMesXslMixerMaterialKindCfgService { + + @Autowired + private ISysDictService sysDictService; + + @Autowired + private ISysDictItemService sysDictItemService; + + @Autowired + private ISysCategoryService sysCategoryService; + + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置展开与批量保存----------- + @Override + public List expandLines(String sourceType, String sourceRootCode, Integer tenantId) { + if (oConvertUtils.isEmpty(sourceType) || oConvertUtils.isEmpty(sourceRootCode)) { + throw new JeecgBootException("请选择数据源类型与根字典/分类"); + } + String normalizedType = sourceType.trim().toLowerCase(); + String rootCode = sourceRootCode.trim(); + if (MesXslMixerMaterialKindCfg.SOURCE_TYPE_DICT.equals(normalizedType)) { + return expandFromDict(rootCode, tenantId); + } + if (MesXslMixerMaterialKindCfg.SOURCE_TYPE_CATEGORY.equals(normalizedType)) { + return expandFromCategory(rootCode, tenantId); + } + throw new JeecgBootException("不支持的数据源类型:" + sourceType); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveBatchLines(List lines, Integer tenantId) { + if (lines == null || lines.isEmpty()) { + throw new JeecgBootException("请至少维护一条种类配置明细"); + } + Integer resolvedTenantId = resolveTenantId(tenantId); + Set kindKeys = new HashSet<>(); + Set categoryRefIds = new HashSet<>(); + for (MesXslMixerMaterialKindCfg line : lines) { + String err = validateLine(line, kindKeys, categoryRefIds); + if (err != null) { + throw new JeecgBootException(err); + } + line.setTenantId(resolvedTenantId); + line.setDelFlag(CommonConstant.DEL_FLAG_0); + checkDuplicate(line, null); + } + saveBatch(lines); + } + + private List expandFromDict(String dictCode, Integer tenantId) { + LambdaQueryWrapper dictQw = new LambdaQueryWrapper<>(); + dictQw.eq(SysDict::getDictCode, dictCode); + SysDict dict = sysDictService.getOne(dictQw, false); + if (dict == null) { + throw new JeecgBootException("数据字典不存在:" + dictCode); + } + LambdaQueryWrapper itemQw = new LambdaQueryWrapper<>(); + itemQw.eq(SysDictItem::getDictId, dict.getId()); + itemQw.eq(SysDictItem::getStatus, 1); + itemQw.orderByAsc(SysDictItem::getSortOrder).orderByAsc(SysDictItem::getItemValue); + List items = sysDictItemService.list(itemQw); + if (items == null || items.isEmpty()) { + throw new JeecgBootException("该数据字典下没有可用的字典项"); + } + Set existsRefIds = loadExistingCategoryRefIds(MesXslMixerMaterialKindCfg.SOURCE_TYPE_DICT, dictCode, tenantId); + List result = new ArrayList<>(); + int priorityBase = 10; + int index = 0; + for (SysDictItem item : items) { + if (item == null || oConvertUtils.isEmpty(item.getId())) { + continue; + } + if (existsRefIds.contains(item.getId())) { + continue; + } + MesXslMixerMaterialKindCfg cfg = new MesXslMixerMaterialKindCfg(); + cfg.setSourceType(MesXslMixerMaterialKindCfg.SOURCE_TYPE_DICT); + cfg.setSourceRootCode(dictCode); + cfg.setSourceRootName(dict.getDictName()); + cfg.setCategoryRefId(item.getId()); + cfg.setCategoryRefCode(item.getItemValue()); + cfg.setCategoryRefName(item.getItemText()); + cfg.setKindKey(item.getItemValue()); + cfg.setKindName(item.getItemText()); + cfg.setPriority(item.getSortOrder() != null ? item.getSortOrder() : priorityBase + index * 10); + cfg.setTenantId(resolveTenantId(tenantId)); + result.add(cfg); + index++; + } + if (result.isEmpty()) { + throw new JeecgBootException("该数据字典下的字典项均已配置,无需重复带出"); + } + return result; + } + + private List expandFromCategory(String rootCode, Integer tenantId) { + LambdaQueryWrapper rootQw = new LambdaQueryWrapper<>(); + rootQw.eq(SysCategory::getCode, rootCode); + SysCategory root = sysCategoryService.getOne(rootQw, false); + if (root == null) { + throw new JeecgBootException("分类字典不存在:" + rootCode); + } + List descendants = new ArrayList<>(); + Deque queue = new ArrayDeque<>(); + queue.add(root.getId()); + while (!queue.isEmpty()) { + String parentId = queue.poll(); + LambdaQueryWrapper childQw = new LambdaQueryWrapper<>(); + childQw.eq(SysCategory::getPid, parentId); + childQw.orderByAsc(SysCategory::getCode); + List children = sysCategoryService.list(childQw); + if (children == null || children.isEmpty()) { + continue; + } + for (SysCategory child : children) { + descendants.add(child); + queue.add(child.getId()); + } + } + if (descendants.isEmpty()) { + throw new JeecgBootException("该分类下没有可用的子分类"); + } + Set existsRefIds = loadExistingCategoryRefIds(MesXslMixerMaterialKindCfg.SOURCE_TYPE_CATEGORY, rootCode, tenantId); + List result = new ArrayList<>(); + int priorityBase = 10; + for (int i = 0; i < descendants.size(); i++) { + SysCategory category = descendants.get(i); + if (category == null || oConvertUtils.isEmpty(category.getId())) { + continue; + } + if (existsRefIds.contains(category.getId())) { + continue; + } + String refCode = oConvertUtils.isNotEmpty(category.getCode()) ? category.getCode() : category.getId(); + MesXslMixerMaterialKindCfg cfg = new MesXslMixerMaterialKindCfg(); + cfg.setSourceType(MesXslMixerMaterialKindCfg.SOURCE_TYPE_CATEGORY); + cfg.setSourceRootCode(rootCode); + cfg.setSourceRootName(root.getName()); + cfg.setCategoryRefId(category.getId()); + cfg.setCategoryRefCode(refCode); + cfg.setCategoryRefName(category.getName()); + cfg.setKindKey(refCode); + cfg.setKindName(category.getName()); + cfg.setPriority(priorityBase + i * 10); + cfg.setTenantId(resolveTenantId(tenantId)); + result.add(cfg); + } + if (result.isEmpty()) { + throw new JeecgBootException("该分类下的子分类均已配置,无需重复带出"); + } + return result; + } + + private Set loadExistingCategoryRefIds(String sourceType, String sourceRootCode, Integer tenantId) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(MesXslMixerMaterialKindCfg::getSourceType, sourceType); + qw.eq(MesXslMixerMaterialKindCfg::getSourceRootCode, sourceRootCode); + qw.and(q -> q.eq(MesXslMixerMaterialKindCfg::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesXslMixerMaterialKindCfg::getDelFlag)); + Integer resolvedTenantId = resolveTenantId(tenantId); + if (resolvedTenantId != null && MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) { + qw.eq(MesXslMixerMaterialKindCfg::getTenantId, resolvedTenantId); + } + qw.select(MesXslMixerMaterialKindCfg::getCategoryRefId); + List exists = list(qw); + Set ids = new HashSet<>(); + if (exists != null) { + for (MesXslMixerMaterialKindCfg row : exists) { + if (row != null && oConvertUtils.isNotEmpty(row.getCategoryRefId())) { + ids.add(row.getCategoryRefId()); + } + } + } + return ids; + } + + private String validateLine(MesXslMixerMaterialKindCfg line, Set kindKeys, Set categoryRefIds) { + if (line == null) { + return "存在空的明细行"; + } + if (oConvertUtils.isEmpty(line.getKindKey())) { + return "种类键值不能为空"; + } + if (oConvertUtils.isEmpty(line.getKindName())) { + return "种类名称不能为空"; + } + if (oConvertUtils.isEmpty(line.getSourceType())) { + return "数据源类型不能为空"; + } + if (oConvertUtils.isEmpty(line.getCategoryRefId())) { + return "对应分类不能为空"; + } + String kindKey = line.getKindKey().trim(); + if (!kindKeys.add(kindKey)) { + return "本次提交存在重复的种类键值:" + kindKey; + } + String refId = line.getCategoryRefId().trim(); + if (!categoryRefIds.add(refId)) { + return "本次提交存在重复的对应分类"; + } + if (line.getPriority() == null) { + line.setPriority(999); + } + return null; + } + + @Override + public void checkDuplicate(MesXslMixerMaterialKindCfg line, String excludeId) { + Integer tenantId = resolveTenantId(line.getTenantId()); + LambdaQueryWrapper kindQw = new LambdaQueryWrapper<>(); + kindQw.eq(MesXslMixerMaterialKindCfg::getKindKey, line.getKindKey().trim()); + kindQw.and(q -> q.eq(MesXslMixerMaterialKindCfg::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesXslMixerMaterialKindCfg::getDelFlag)); + if (tenantId != null && MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) { + kindQw.eq(MesXslMixerMaterialKindCfg::getTenantId, tenantId); + } + if (oConvertUtils.isNotEmpty(excludeId)) { + kindQw.ne(MesXslMixerMaterialKindCfg::getId, excludeId); + } + if (count(kindQw) > 0) { + throw new JeecgBootException("种类键值已存在:" + line.getKindKey()); + } + + LambdaQueryWrapper refQw = new LambdaQueryWrapper<>(); + refQw.eq(MesXslMixerMaterialKindCfg::getCategoryRefId, line.getCategoryRefId().trim()); + refQw.and(q -> q.eq(MesXslMixerMaterialKindCfg::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesXslMixerMaterialKindCfg::getDelFlag)); + if (tenantId != null && MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) { + refQw.eq(MesXslMixerMaterialKindCfg::getTenantId, tenantId); + } + if (oConvertUtils.isNotEmpty(excludeId)) { + refQw.ne(MesXslMixerMaterialKindCfg::getId, excludeId); + } + if (count(refQw) > 0) { + throw new JeecgBootException("对应分类已配置:" + line.getCategoryRefName()); + } + } + + private static Integer resolveTenantId(Integer tenantId) { + if (tenantId != null) { + return tenantId; + } + String ts = TenantContext.getTenant(); + if (oConvertUtils.isEmpty(ts)) { + try { + HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); + if (request != null) { + ts = TokenUtils.getTenantIdByRequest(request); + } + } catch (Exception ignored) { + // ignore + } + } + if (oConvertUtils.isEmpty(ts)) { + return null; + } + try { + return Integer.parseInt(ts); + } catch (NumberFormatException e) { + return null; + } + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A51】密炼物料种类配置展开与批量保存----------- +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java index f927a3d..6f55c62 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java @@ -2,14 +2,24 @@ package org.jeecg.modules.xslmes.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baomidou.mybatisplus.extension.toolkit.Db; import jakarta.annotation.Resource; import java.io.Serializable; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jeecg.common.constant.CommonConstant; +import org.jeecg.modules.mes.material.entity.MesMixerMaterial; +import org.jeecg.modules.mes.material.service.IMesMixerMaterialService; +import org.jeecg.modules.system.entity.SysCategory; +import org.jeecg.modules.system.service.ISysCategoryService; import org.jeecg.modules.xslmes.entity.MesXslMixingSpec; import org.jeecg.modules.xslmes.entity.MesXslMixingSpecDownStep; import org.jeecg.modules.xslmes.entity.MesXslMixingSpecMaterial; @@ -27,11 +37,19 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @Service +@Slf4j public class MesXslMixingSpecServiceImpl extends ServiceImpl implements IMesXslMixingSpecService { private static final String TCU_UP = "up_mixer"; private static final String TCU_DOWN = "down_mixer"; + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A49】删除混炼示方时同步删除B/F段胶密炼物料----------- + private static final String CATEGORY_MASTER_MAJOR = "XSLMES_MATERIAL_MASTER"; + private static final String CATEGORY_MASTER_MINOR_A = "XSLMES_MATERIAL_MASTER_A"; + private static final String CATEGORY_FINAL_MAJOR = "XSLMES_MATERIAL_FINAL"; + private static final String CATEGORY_FINAL_MINOR_Q = "XSLMES_MATERIAL_FINAL_Q"; + private static final Pattern GENERATED_B_RUBBER_SPEC_PATTERN = Pattern.compile("^B\\d", Pattern.CASE_INSENSITIVE); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A49】删除混炼示方时同步删除B/F段胶密炼物料----------- @Resource private MesXslMixingSpecMaterialMapper materialMapper; @@ -42,6 +60,12 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl stepList, List downStepList, List tcuList) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + SavePerfTrace trace = new SavePerfTrace("新增", main); + trace.logPayloadSize(materialList, stepList, downStepList, tcuList); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- //update-begin---author:cursor ---date:20260522 for:【XSLMES-20260522-A17】混炼示方主子表新增保存----------- normalizeMain(main); + trace.step("normalizeMain"); this.save(main); - saveChildren(main.getId(), materialList, stepList, downStepList, tcuList); + trace.step("saveMain", "mainId", main.getId()); + saveChildren(main.getId(), materialList, stepList, downStepList, tcuList, trace); + trace.finish(); //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A17】混炼示方主子表新增保存----------- } @@ -65,19 +96,32 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl stepList, List downStepList, List tcuList) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + SavePerfTrace trace = new SavePerfTrace("编辑", main); + trace.logPayloadSize(materialList, stepList, downStepList, tcuList); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- //update-begin---author:cursor ---date:20260522 for:【XSLMES-20260522-A17】混炼示方主子表编辑保存----------- normalizeMain(main); + trace.step("normalizeMain"); this.updateById(main); - clearChildren(main.getId()); - saveChildren(main.getId(), materialList, stepList, downStepList, tcuList); + trace.step("updateMain"); + clearChildren(main.getId(), trace); + saveChildren(main.getId(), materialList, stepList, downStepList, tcuList, trace); + trace.finish(); //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A17】混炼示方主子表编辑保存----------- } @Override @Transactional(rollbackFor = Exception.class) public void delMain(String id) { + MesXslMixingSpec main = this.getById(id); clearChildren(id); this.removeById(id); + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A49】删除混炼示方时同步删除B/F段胶密炼物料----------- + if (main != null) { + syncDeleteGeneratedRubberMixerMaterial(main.getSpecName()); + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A49】删除混炼示方时同步删除B/F段胶密炼物料----------- } @Override @@ -203,10 +247,26 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl().eq(MesXslMixingSpecMaterial::getMixingSpecId, mainId)); + if (trace != null) { + trace.step("deleteMaterialChildren"); + } stepMapper.delete(new LambdaQueryWrapper().eq(MesXslMixingSpecStep::getMixingSpecId, mainId)); + if (trace != null) { + trace.step("deleteStepChildren"); + } downStepMapper.delete(new LambdaQueryWrapper().eq(MesXslMixingSpecDownStep::getMixingSpecId, mainId)); + if (trace != null) { + trace.step("deleteDownStepChildren"); + } tcuMapper.delete(new LambdaQueryWrapper().eq(MesXslMixingSpecTcu::getMixingSpecId, mainId)); + if (trace != null) { + trace.step("deleteTcuChildren"); + } } private void saveChildren( @@ -215,54 +275,142 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl stepList, List downStepList, List tcuList) { + saveChildren(mainId, materialList, stepList, downStepList, tcuList, null); + } + + private void saveChildren( + String mainId, + List materialList, + List stepList, + List downStepList, + List tcuList, + SavePerfTrace trace) { + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】混炼示方子表批量插入避免保存超时----------- Date now = new Date(); + saveMaterialChildren(mainId, materialList, now, trace); + saveStepChildren(mainId, stepList, now, trace); + saveDownStepChildren(mainId, downStepList, now, trace); + saveTcuChildren(mainId, tcuList, now, trace); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】混炼示方子表批量插入避免保存超时----------- + } + + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】混炼示方子表批量插入避免保存超时----------- + private void saveMaterialChildren(String mainId, List materialList, Date now) { + saveMaterialChildren(mainId, materialList, now, null); + } + + private void saveMaterialChildren(String mainId, List materialList, Date now, SavePerfTrace trace) { + if (CollectionUtils.isEmpty(materialList)) { + return; + } + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计----------- + fillMaterialAccumWeight(materialList); + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计----------- + if (trace != null) { + trace.step("fillMaterialAccumWeight", "rows", countRows(materialList)); + } + List rows = new ArrayList<>(); int sort = 0; - if (!CollectionUtils.isEmpty(materialList)) { - for (MesXslMixingSpecMaterial row : materialList) { - if (row == null) { - continue; - } - row.setId(null); - row.setMixingSpecId(mainId); - row.setSortNo(sort++); - if (row.getCreateTime() == null) { - row.setCreateTime(now); - } - materialMapper.insert(row); + for (MesXslMixingSpecMaterial row : materialList) { + if (row == null) { + continue; + } + row.setId(null); + row.setMixingSpecId(mainId); + row.setSortNo(sort++); + if (row.getCreateTime() == null) { + row.setCreateTime(now); + } + rows.add(row); + } + if (trace != null) { + trace.step("buildMaterialRows", "rows", rows.size()); + } + if (!rows.isEmpty()) { + Db.saveBatch(rows); + if (trace != null) { + trace.step("batchInsertMaterial", "rows", rows.size()); } } - sort = 0; - if (!CollectionUtils.isEmpty(stepList)) { - for (MesXslMixingSpecStep row : stepList) { - if (row == null) { - continue; - } - row.setId(null); - row.setMixingSpecId(mainId); - row.setSortNo(sort++); - if (row.getCreateTime() == null) { - row.setCreateTime(now); - } - stepMapper.insert(row); + } + + private void saveStepChildren(String mainId, List stepList, Date now) { + saveStepChildren(mainId, stepList, now, null); + } + + private void saveStepChildren(String mainId, List stepList, Date now, SavePerfTrace trace) { + if (CollectionUtils.isEmpty(stepList)) { + return; + } + List rows = new ArrayList<>(); + int sort = 0; + for (MesXslMixingSpecStep row : stepList) { + if (row == null) { + continue; + } + row.setId(null); + row.setMixingSpecId(mainId); + row.setSortNo(sort++); + if (row.getCreateTime() == null) { + row.setCreateTime(now); + } + rows.add(row); + } + if (trace != null) { + trace.step("buildStepRows", "rows", rows.size()); + } + if (!rows.isEmpty()) { + Db.saveBatch(rows); + if (trace != null) { + trace.step("batchInsertStep", "rows", rows.size()); } } - sort = 0; - if (!CollectionUtils.isEmpty(downStepList)) { - for (MesXslMixingSpecDownStep row : downStepList) { - if (row == null) { - continue; - } - row.setId(null); - row.setMixingSpecId(mainId); - row.setSortNo(sort++); - if (row.getCreateTime() == null) { - row.setCreateTime(now); - } - downStepMapper.insert(row); + } + + private void saveDownStepChildren(String mainId, List downStepList, Date now) { + saveDownStepChildren(mainId, downStepList, now, null); + } + + private void saveDownStepChildren(String mainId, List downStepList, Date now, SavePerfTrace trace) { + if (CollectionUtils.isEmpty(downStepList)) { + return; + } + List rows = new ArrayList<>(); + int sort = 0; + for (MesXslMixingSpecDownStep row : downStepList) { + if (row == null) { + continue; + } + row.setId(null); + row.setMixingSpecId(mainId); + row.setSortNo(sort++); + if (row.getCreateTime() == null) { + row.setCreateTime(now); + } + rows.add(row); + } + if (trace != null) { + trace.step("buildDownStepRows", "rows", rows.size()); + } + if (!rows.isEmpty()) { + Db.saveBatch(rows); + if (trace != null) { + trace.step("batchInsertDownStep", "rows", rows.size()); } } + } + + private void saveTcuChildren(String mainId, List tcuList, Date now) { + saveTcuChildren(mainId, tcuList, now, null); + } + + private void saveTcuChildren(String mainId, List tcuList, Date now, SavePerfTrace trace) { List tcuRows = fillDefaultTcuRows(tcuList); - sort = 0; + if (trace != null) { + trace.step("fillDefaultTcuRows", "rows", tcuRows.size()); + } + List rows = new ArrayList<>(); + int sort = 0; for (MesXslMixingSpecTcu row : tcuRows) { row.setId(null); row.setMixingSpecId(mainId); @@ -273,9 +421,139 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl rows) { + return rows == null ? 0 : rows.size(); + } + + /** 混炼示方保存性能追踪(临时诊断日志,定位慢步骤) */ + private static final class SavePerfTrace { + private final String action; + private final String mainId; + private final String specName; + private final long startMs = System.currentTimeMillis(); + private long lastMs = startMs; + private final StringBuilder summary = new StringBuilder(); + + private SavePerfTrace(String action, MesXslMixingSpec main) { + this.action = action; + this.mainId = main != null ? main.getId() : null; + this.specName = main != null ? main.getSpecName() : null; + summary.append("action=").append(action); + if (StringUtils.isNotBlank(mainId)) { + summary.append(", mainId=").append(mainId); + } + if (StringUtils.isNotBlank(specName)) { + summary.append(", specName=").append(specName); + } + } + + private void logPayloadSize( + List materialList, + List stepList, + List downStepList, + List tcuList) { + log.info( + "[混炼示方保存性能] 开始{} specName={}, mainId={}, payload=[material={}, step={}, downStep={}, tcu={}]", + action, + specName, + mainId, + countRows(materialList), + countRows(stepList), + countRows(downStepList), + countRows(tcuList)); + } + + private void step(String stepName, Object... kvPairs) { + long now = System.currentTimeMillis(); + long stepMs = now - lastMs; + long totalMs = now - startMs; + summary.append(" | ").append(stepName).append('=').append(stepMs).append("ms"); + if (kvPairs != null && kvPairs.length > 0) { + summary.append('('); + for (int i = 0; i < kvPairs.length; i += 2) { + if (i > 0) { + summary.append(", "); + } + summary.append(kvPairs[i]).append('=').append(kvPairs[i + 1]); + } + summary.append(')'); + } + log.info("[混炼示方保存性能] {} 步骤={} 本步耗时={}ms 累计={}ms", action, stepName, stepMs, totalMs); + lastMs = now; + } + + private void finish() { + log.info("[混炼示方保存性能] 完成{} 总耗时={}ms | {}", action, System.currentTimeMillis() - startMs, summary); + } + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A47】混炼示方保存分步骤性能日志----------- + + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计----------- + /** 按种类连续分组,累计写入每组最后一行 */ + private void fillMaterialAccumWeight(List materials) { + if (CollectionUtils.isEmpty(materials)) { + return; + } + int index = 0; + while (index < materials.size()) { + MesXslMixingSpecMaterial current = materials.get(index); + if (!isMaterialDataRow(current)) { + current.setAccumWeight(null); + index++; + continue; + } + String kind = normalizeMaterialKind(current); + int groupEnd = index; + BigDecimal sum = BigDecimal.ZERO; + while (groupEnd < materials.size()) { + MesXslMixingSpecMaterial row = materials.get(groupEnd); + if (!isMaterialDataRow(row) || !kind.equals(normalizeMaterialKind(row))) { + break; + } + if (row.getUnitWeight() != null) { + sum = sum.add(row.getUnitWeight()); + } + groupEnd++; + } + for (int rowIndex = index; rowIndex < groupEnd; rowIndex++) { + if (rowIndex == groupEnd - 1) { + materials.get(rowIndex).setAccumWeight(sum.compareTo(BigDecimal.ZERO) != 0 ? sum : null); + } else { + materials.get(rowIndex).setAccumWeight(null); + } + } + index = groupEnd; + } + } + + private boolean isMaterialDataRow(MesXslMixingSpecMaterial row) { + if (row == null) { + return false; + } + return StringUtils.isNotBlank(row.getMixerMaterialName()) + || row.getUnitWeight() != null + || StringUtils.isNotBlank(row.getMaterialKind()); + } + + private String normalizeMaterialKind(MesXslMixingSpecMaterial row) { + return StringUtils.isNotBlank(row.getMaterialKind()) ? row.getMaterialKind().trim() : ""; + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计----------- private List queryMaterialByMainId(String mainId) { return materialMapper.selectList( @@ -336,4 +614,112 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl().eq(MesXslMixingSpec::getSpecName, specCode)); + if (remainingSpecCount > 0) { + log.debug("[混炼示方删除] 示方编号 {} 仍有 {} 条混炼示方,跳过密炼物料同步删除", specCode, remainingSpecCount); + return; + } + long referencedCount = materialMapper.selectCount( + new LambdaQueryWrapper().eq(MesXslMixingSpecMaterial::getMixerMaterialName, specCode)); + if (referencedCount > 0) { + log.info( + "[混炼示方删除] 示方编号 {} 仍被 {} 条混炼示方明细引用,跳过密炼物料同步删除", + specCode, + referencedCount); + return; + } + MesMixerMaterial material = findMixerMaterialByCodeOrName(specCode); + if (material == null) { + log.debug("[混炼示方删除] 未找到示方编号 {} 对应密炼物料,跳过同步删除", specCode); + return; + } + Map categoryIdCache = new HashMap<>(); + if (!isGeneratedRubberMixerMaterial(material, isFinalStage, categoryIdCache)) { + log.info( + "[混炼示方删除] 密炼物料 id={}, code={} 分类非生成示方自动同步类型,跳过删除", + material.getId(), + specCode); + return; + } + mesMixerMaterialService.removeById(material.getId()); + log.info("[混炼示方删除] 同步删除密炼物料 id={}, code={}", material.getId(), specCode); + } + + /** 判断是否为生成混炼示方时自动同步的 B/F 段胶示方编号;true=F段,false=B段,null=非自动生成胶料编号 */ + private Boolean resolveGeneratedRubberSegment(String specCode) { + if (StringUtils.isBlank(specCode)) { + return null; + } + String normalized = specCode.trim(); + if (normalized.toUpperCase().startsWith("F")) { + return true; + } + if (GENERATED_B_RUBBER_SPEC_PATTERN.matcher(normalized).find()) { + return false; + } + return null; + } + + private MesMixerMaterial findMixerMaterialByCodeOrName(String specCode) { + MesMixerMaterial byCode = mesMixerMaterialService.getOne( + new LambdaQueryWrapper() + .eq(MesMixerMaterial::getMaterialCode, specCode) + .and(w -> w.eq(MesMixerMaterial::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesMixerMaterial::getDelFlag)) + .last("limit 1")); + if (byCode != null) { + return byCode; + } + return mesMixerMaterialService.getOne( + new LambdaQueryWrapper() + .eq(MesMixerMaterial::getMaterialName, specCode) + .and(w -> w.eq(MesMixerMaterial::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesMixerMaterial::getDelFlag)) + .last("limit 1")); + } + + private boolean isGeneratedRubberMixerMaterial( + MesMixerMaterial material, boolean isFinalStage, Map categoryIdCache) { + if (material == null) { + return false; + } + String majorCode = isFinalStage ? CATEGORY_FINAL_MAJOR : CATEGORY_MASTER_MAJOR; + String minorCode = isFinalStage ? CATEGORY_FINAL_MINOR_Q : CATEGORY_MASTER_MINOR_A; + String expectedMajorId = resolveCategoryIdByCode(majorCode, categoryIdCache); + String expectedMinorId = resolveCategoryIdByCode(minorCode, categoryIdCache); + if (StringUtils.isBlank(expectedMajorId) || StringUtils.isBlank(expectedMinorId)) { + return false; + } + return expectedMajorId.equals(material.getMajorCategoryId()) && expectedMinorId.equals(material.getMinorCategoryId()); + } + + private String resolveCategoryIdByCode(String categoryCode, Map cache) { + if (StringUtils.isBlank(categoryCode)) { + return null; + } + if (cache != null && cache.containsKey(categoryCode)) { + return cache.get(categoryCode); + } + SysCategory category = sysCategoryService.getOne( + new LambdaQueryWrapper().eq(SysCategory::getCode, categoryCode.trim()).last("limit 1")); + String categoryId = category != null ? category.getId() : null; + if (cache != null) { + cache.put(categoryCode, categoryId); + } + return categoryId; + } + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A49】删除混炼示方时同步删除B/F段胶密炼物料----------- } diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysCategoryServiceImpl.java b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysCategoryServiceImpl.java index 869b23c..9e48a55 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysCategoryServiceImpl.java +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysCategoryServiceImpl.java @@ -29,8 +29,8 @@ import java.util.stream.Collectors; @Service public class SysCategoryServiceImpl extends ServiceImpl implements ISysCategoryService { - /** 原辅材料分类编码(其子类可标记为胶料) */ - private static final String MATERIAL_RAW_AUX_CODE = "XSLMES_MATERIAL_RAW_AUX"; + /** MES 物料大类编码前缀(其直接子类为物料小类,可标记胶料) */ + private static final String MATERIAL_MAJOR_CODE_PREFIX = "XSLMES_MATERIAL_"; @Override public void addSysCategory(SysCategory sysCategory) { @@ -103,9 +103,9 @@ public class SysCategoryServiceImpl extends ServiceImpl queryListByCode(String pcode) throws JeecgBootException{ diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_102__mes_xsl_mixer_material_kind_cfg.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_102__mes_xsl_mixer_material_kind_cfg.sql new file mode 100644 index 0000000..095ec4e --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_102__mes_xsl_mixer_material_kind_cfg.sql @@ -0,0 +1,91 @@ +-- 密炼物料种类配置:建表 + 菜单(挂 MES技术管理)+ admin 授权 +SET NAMES utf8mb4; + +CREATE TABLE IF NOT EXISTS `mes_xsl_mixer_material_kind_cfg` ( + `id` varchar(32) NOT NULL COMMENT '主键', + `kind_key` varchar(100) NOT NULL COMMENT '种类键值', + `kind_name` varchar(200) NOT NULL COMMENT '种类名称', + `source_type` varchar(20) NOT NULL COMMENT '数据源类型:dict数据字典/category分类字典', + `source_root_code` varchar(100) DEFAULT NULL COMMENT '根字典编码或分类pcode', + `source_root_name` varchar(200) DEFAULT NULL COMMENT '根名称冗余', + `category_ref_id` varchar(32) DEFAULT NULL COMMENT '对应分类/字典项ID', + `category_ref_code` varchar(100) DEFAULT NULL COMMENT '对应分类编码/字典项值', + `category_ref_name` varchar(200) DEFAULT NULL COMMENT '对应分类名称冗余', + `tenant_id` int DEFAULT NULL COMMENT '租户ID', + `priority` int DEFAULT '0' COMMENT '优先级(数字越小越优先)', + `sys_org_code` varchar(64) DEFAULT NULL COMMENT '所属部门', + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(50) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + `del_flag` int NOT NULL DEFAULT '0' COMMENT '逻辑删除(0正常 1已删除)', + PRIMARY KEY (`id`), + KEY `idx_mxmmkc_tenant_priority` (`tenant_id`, `priority`), + KEY `idx_mxmmkc_source_root` (`source_type`, `source_root_code`), + KEY `idx_mxmmkc_category_ref` (`category_ref_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES密炼物料种类配置'; + +UPDATE `sys_permission` +SET `is_leaf` = 0, `update_time` = NOW() +WHERE `id` = '1900000000000000810' AND `is_leaf` = 1; + +INSERT INTO `sys_permission` ( + `id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, + `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, + `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, + `del_flag`, `rule_flag`, `status`, `internal_or_external` +) +SELECT + '177925970995550', '1900000000000000810', '密炼物料种类配置', '/xslmes/mesXslMixerMaterialKindCfg', + 'xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList', 1, 'MesXslMixerMaterialKindCfgList', NULL, + 1, NULL, '0', 3.00, 0, 'ant-design:tags-outlined', 0, 1, + 0, 0, 'MES密炼物料种类配置', 'admin', NOW(), 'admin', NOW(), + 0, 0, '1', 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_permission` + WHERE `id` = '177925970995550' + OR (`del_flag` = 0 AND `menu_type` = 1 AND `name` = '密炼物料种类配置' AND `parent_id` = '1900000000000000810') +); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995551', '177925970995550', '新增', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:add', '1', 1.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995551'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995552', '177925970995550', '编辑', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:edit', '1', 2.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995552'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995553', '177925970995550', '删除', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:delete', '1', 3.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995553'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995554', '177925970995550', '批量删除', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:deleteBatch', '1', 4.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995554'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995555', '177925970995550', '导出', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:exportXls', '1', 5.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995555'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995556', '177925970995550', '导入', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:importExcel', '1', 6.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995556'); + +INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`) +SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1' +FROM `sys_role` r +CROSS JOIN `sys_permission` p +WHERE r.`role_code` = 'admin' + AND p.`id` IN ( + '177925970995550', + '177925970995551', + '177925970995552', + '177925970995553', + '177925970995554', + '177925970995555', + '177925970995556' + ) + AND NOT EXISTS ( + SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id` + ); diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_103__mes_xsl_mixer_material_kind_cfg_menu_fix.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_103__mes_xsl_mixer_material_kind_cfg_menu_fix.sql new file mode 100644 index 0000000..a730d7e --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_103__mes_xsl_mixer_material_kind_cfg_menu_fix.sql @@ -0,0 +1,67 @@ +-- 密炼物料种类配置:菜单 ID 与配合示方(177925970995530)冲突,补插正确菜单 +SET NAMES utf8mb4; + +UPDATE `sys_permission` +SET `is_leaf` = 0, `update_time` = NOW() +WHERE `id` = '1900000000000000810'; + +INSERT INTO `sys_permission` ( + `id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`, + `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`, + `hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, + `del_flag`, `rule_flag`, `status`, `internal_or_external` +) +SELECT + '177925970995550', '1900000000000000810', '密炼物料种类配置', '/xslmes/mesXslMixerMaterialKindCfg', + 'xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList', 1, 'MesXslMixerMaterialKindCfgList', NULL, + 1, NULL, '0', 3.00, 0, 'ant-design:tags-outlined', 0, 1, + 0, 0, 'MES密炼物料种类配置', 'admin', NOW(), 'admin', NOW(), + 0, 0, '1', 0 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_permission` + WHERE `id` = '177925970995550' + OR (`del_flag` = 0 AND `menu_type` = 1 AND `name` = '密炼物料种类配置' AND `parent_id` = '1900000000000000810') +); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995551', '177925970995550', '新增', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:add', '1', 1.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995551'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995552', '177925970995550', '编辑', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:edit', '1', 2.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995552'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995553', '177925970995550', '删除', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:delete', '1', 3.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995553'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995554', '177925970995550', '批量删除', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:deleteBatch', '1', 4.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995554'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995555', '177925970995550', '导出', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:exportXls', '1', 5.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995555'); + +INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`) +SELECT '177925970995556', '177925970995550', '导入', 2, 'xslmes:mes_xsl_mixer_material_kind_cfg:importExcel', '1', 6.00, 0, 1, 0, '1', 0, 'admin', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '177925970995556'); + +INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`) +SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1' +FROM `sys_role` r +CROSS JOIN `sys_permission` p +WHERE r.`role_code` = 'admin' + AND p.`id` IN ( + '177925970995550', + '177925970995551', + '177925970995552', + '177925970995553', + '177925970995554', + '177925970995555', + '177925970995556' + ) + AND NOT EXISTS ( + SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id` + ); diff --git a/jeecgboot-vue3/src/views/mes/material/modules/MesMixerMaterialSysCategoryModal.vue b/jeecgboot-vue3/src/views/mes/material/modules/MesMixerMaterialSysCategoryModal.vue index 9750f98..8d167a0 100644 --- a/jeecgboot-vue3/src/views/mes/material/modules/MesMixerMaterialSysCategoryModal.vue +++ b/jeecgboot-vue3/src/views/mes/material/modules/MesMixerMaterialSysCategoryModal.vue @@ -13,7 +13,8 @@ import { loadTreeData } from '/@/views/system/category/category.api'; import { MATERIAL_RAW_AUX_CODE, materialRawAuxCategoryId, - isMaterialRawAuxSubCategory, + ensureMaterialCategoryContext, + isMaterialMinorCategory, toIsRubberFlag, fromIsRubberFlag, } from '/@/views/system/category/category.constants'; @@ -48,7 +49,7 @@ const schemas: FormSchema[] = [ defaultValue: false, renderComponentContent: '胶料', colProps: { span: 24 }, - ifShow: ({ values }) => isMaterialRawAuxSubCategory(values.pid), + ifShow: ({ values }) => isMaterialMinorCategory(values.pid), }, ]; @@ -95,7 +96,7 @@ async function ensureMaterialRawAuxCategoryId() { function normalizeSubmitValues(values: Recordable) { const payload = { ...values }; - payload.isRubber = isMaterialRawAuxSubCategory(payload.pid) ? toIsRubberFlag(payload.isRubber) : '0'; + payload.isRubber = isMaterialMinorCategory(payload.pid) ? toIsRubberFlag(payload.isRubber) : '0'; return payload; } @@ -104,6 +105,7 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data setModalProps({ confirmLoading: false }); isUpdate.value = !!data?.isUpdate; await ensureMaterialRawAuxCategoryId(); + await ensureMaterialCategoryContext(); const tree = await buildPidTree(); if (!tree.length) { createMessage.warning('未加载到物料分类树,请确认分类字典根编码 XSLMES_MATERIAL 已存在'); diff --git a/jeecgboot-vue3/src/views/system/category/category.constants.ts b/jeecgboot-vue3/src/views/system/category/category.constants.ts index 011798f..40aca17 100644 --- a/jeecgboot-vue3/src/views/system/category/category.constants.ts +++ b/jeecgboot-vue3/src/views/system/category/category.constants.ts @@ -1,16 +1,78 @@ import { ref } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { getChildListBatch, loadTreeData } from './category.api'; -/** MES 物料分类 - 原辅材料编码 */ +/** MES 物料分类根编码 */ +export const MATERIAL_ROOT_CODE = 'XSLMES_MATERIAL'; + +/** MES 物料分类 - 原辅材料编码(兼容旧逻辑) */ export const MATERIAL_RAW_AUX_CODE = 'XSLMES_MATERIAL_RAW_AUX'; -/** 原辅材料分类节点 ID(运行时加载) */ +/** 原辅材料分类节点 ID(运行时加载,兼容旧逻辑) */ export const materialRawAuxCategoryId = ref(''); -/** 是否为原辅材料的直接子类 */ +/** MES 物料分类根节点 ID(运行时加载) */ +export const materialRootCategoryId = ref(''); + +/** MES 物料大类节点 ID 集合(运行时加载) */ +export const materialMajorCategoryIds = ref>(new Set()); + +let materialCategoryContextLoading: Promise | null = null; + +/** 加载 MES 物料分类上下文(根节点 + 大类 ID) */ +export async function ensureMaterialCategoryContext(force = false) { + if (!force && materialRootCategoryId.value && materialMajorCategoryIds.value.size > 0) { + return; + } + if (materialCategoryContextLoading) { + await materialCategoryContextLoading; + return; + } + materialCategoryContextLoading = (async () => { + const rootRes = await defHttp.get( + { url: '/sys/category/loadOne', params: { field: 'code', val: MATERIAL_ROOT_CODE } }, + { isTransformResponse: false }, + ); + if (rootRes?.success && rootRes?.result?.id) { + materialRootCategoryId.value = String(rootRes.result.id); + } + const auxRes = await defHttp.get( + { url: '/sys/category/loadOne', params: { field: 'code', val: MATERIAL_RAW_AUX_CODE } }, + { isTransformResponse: false }, + ); + if (auxRes?.success && auxRes?.result?.id) { + materialRawAuxCategoryId.value = String(auxRes.result.id); + } + const majors = await loadTreeData({ async: false, pcode: MATERIAL_ROOT_CODE }); + const majorIds = new Set(); + (Array.isArray(majors) ? majors : []).forEach((node) => { + const nodeKey = node?.key ?? node?.value ?? node?.id; + if (nodeKey != null) { + majorIds.add(String(nodeKey)); + } + }); + materialMajorCategoryIds.value = majorIds; + })(); + try { + await materialCategoryContextLoading; + } finally { + materialCategoryContextLoading = null; + } +} + +/** 是否为原辅材料的直接子类(兼容旧逻辑) */ export function isMaterialRawAuxSubCategory(pid?: string) { return !!materialRawAuxCategoryId.value && pid === materialRawAuxCategoryId.value; } +/** 是否为 MES 物料小类(父节点为物料大类) */ +export function isMaterialMinorCategory(pid?: string) { + if (!pid) { + return false; + } + return materialMajorCategoryIds.value.has(String(pid)); +} + /** 表单 Checkbox 布尔值 -> 数据库存储值 */ export function toIsRubberFlag(value: unknown) { return value === true || value === '1' ? '1' : '0'; @@ -20,3 +82,206 @@ export function toIsRubberFlag(value: unknown) { export function fromIsRubberFlag(value: unknown) { return value === '1' || value === 1 || value === true; } + +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】统一加载MES物料分类树(大类+小类)----------- +export interface MesMaterialCategoryMinorItem { + id: string; + name: string; + majorId: string; + majorName: string; + label: string; +} + +export interface MesMaterialCategoryMajorItem { + id: string; + name: string; + minors: MesMaterialCategoryMinorItem[]; +} + +export interface MesMaterialCategoryTreeLoadResult { + majors: MesMaterialCategoryMajorItem[]; + minors: MesMaterialCategoryMinorItem[]; + treeNodes: Recordable[]; +} + +function normalizeCategoryNodeKey(node: Recordable): string { + const key = node?.key ?? node?.value ?? node?.id; + return key != null && String(key) !== '' ? String(key) : ''; +} + +function normalizeCategoryNodeTitle(node: Recordable): string { + return String(node?.title ?? node?.name ?? ''); +} + +function normalizeTreeSelectNodes(nodes: unknown): Recordable[] { + if (Array.isArray(nodes)) { + return nodes as Recordable[]; + } + if (nodes && typeof nodes === 'object') { + const payload = nodes as Recordable; + if (Array.isArray(payload.result)) { + return payload.result; + } + if (Array.isArray(payload.records)) { + return payload.records; + } + } + return []; +} + +/** 加载 MES 物料分类树:根下物料大类 + 各物料小类(供密炼物料/混炼示方选料复用) */ +export async function loadMesMaterialCategoryTreeData(): Promise { + // 优先 loadTreeRoot(与密炼物料列表页一致,已验证可用) + try { + const treeRes = await loadTreeData({ async: false, pcode: MATERIAL_ROOT_CODE }); + const treeResult = buildMesMaterialCategoryTreeFromTreeSelect(normalizeTreeSelectNodes(treeRes)); + if (treeResult.minors.length) { + if (!materialRootCategoryId.value) { + await ensureMaterialCategoryContext(); + } + return treeResult; + } + } catch { + // 继续走 batch 兜底 + } + + let rootId = materialRootCategoryId.value; + if (!rootId) { + try { + const root = await defHttp.get({ + url: '/sys/category/loadOne', + params: { field: 'code', val: MATERIAL_ROOT_CODE }, + }); + if (root?.id) { + rootId = String(root.id); + materialRootCategoryId.value = rootId; + } + } catch { + rootId = ''; + } + } + if (!rootId) { + return { majors: [], minors: [], treeNodes: [] }; + } + + let majorRecords: Recordable[] = []; + let minorRecords: Recordable[] = []; + try { + const majorBatch = await getChildListBatch({ parentIds: rootId }); + if (majorBatch?.success === false) { + return { majors: [], minors: [], treeNodes: [] }; + } + majorRecords = (majorBatch?.result?.records || majorBatch?.records || []) as Recordable[]; + if (majorRecords.length) { + const majorIds = majorRecords.map((item) => String(item.id)).filter(Boolean).join(','); + if (majorIds) { + const minorBatch = await getChildListBatch({ parentIds: majorIds }); + if (minorBatch?.success !== false) { + minorRecords = (minorBatch?.result?.records || minorBatch?.records || []) as Recordable[]; + } + } + } + } catch { + return { majors: [], minors: [], treeNodes: [] }; + } + + const result = buildMesMaterialCategoryTreeFromRecords(majorRecords, minorRecords); + materialMajorCategoryIds.value = new Set(result.majors.map((item) => item.id)); + return result; +} + +function buildMesMaterialCategoryTreeFromRecords(majorRecords: Recordable[], minorRecords: Recordable[]) { + const majorMap = new Map(); + majorRecords.forEach((record) => { + const majorId = normalizeCategoryNodeKey(record); + if (!majorId) { + return; + } + majorMap.set(majorId, { + id: majorId, + name: normalizeCategoryNodeTitle(record), + minors: [], + }); + }); + + minorRecords.forEach((record) => { + const majorId = record?.pid != null ? String(record.pid) : ''; + const minorId = normalizeCategoryNodeKey(record); + const major = majorMap.get(majorId); + if (!major || !minorId) { + return; + } + major.minors.push({ + id: minorId, + name: normalizeCategoryNodeTitle(record), + majorId: major.id, + majorName: major.name, + label: major.name && normalizeCategoryNodeTitle(record) + ? `${major.name} / ${normalizeCategoryNodeTitle(record)}` + : normalizeCategoryNodeTitle(record) || major.name, + }); + }); + + const majors: MesMaterialCategoryMajorItem[] = []; + const minors: MesMaterialCategoryMinorItem[] = []; + const treeNodes: Recordable[] = []; + + majorMap.forEach((major) => { + if (!major.minors.length) { + return; + } + majors.push({ id: major.id, name: major.name, minors: major.minors }); + minors.push(...major.minors); + treeNodes.push({ + key: major.id, + title: major.name, + children: major.minors.map((minor) => ({ key: minor.id, title: minor.name })), + }); + }); + + return { majors, minors, treeNodes }; +} + +function buildMesMaterialCategoryTreeFromTreeSelect(majorRaw: Recordable[]) { + const majors: MesMaterialCategoryMajorItem[] = []; + const minors: MesMaterialCategoryMinorItem[] = []; + const treeNodes: Recordable[] = []; + + majorRaw.forEach((major) => { + const majorId = normalizeCategoryNodeKey(major); + const majorName = normalizeCategoryNodeTitle(major); + if (!majorId) { + return; + } + const majorMinors: MesMaterialCategoryMinorItem[] = []; + (Array.isArray(major.children) ? major.children : []).forEach((child) => { + const minorId = normalizeCategoryNodeKey(child); + const minorName = normalizeCategoryNodeTitle(child); + if (!minorId) { + return; + } + const item: MesMaterialCategoryMinorItem = { + id: minorId, + name: minorName, + majorId, + majorName, + label: majorName && minorName ? `${majorName} / ${minorName}` : minorName || majorName, + }; + majorMinors.push(item); + minors.push(item); + }); + if (!majorMinors.length) { + return; + } + majors.push({ id: majorId, name: majorName, minors: majorMinors }); + treeNodes.push({ + key: majorId, + title: majorName, + children: majorMinors.map((minor) => ({ key: minor.id, title: minor.name })), + }); + }); + + materialMajorCategoryIds.value = new Set(majors.map((item) => item.id)); + return { majors, minors, treeNodes }; +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】统一加载MES物料分类树(大类+小类)----------- diff --git a/jeecgboot-vue3/src/views/system/category/category.data.ts b/jeecgboot-vue3/src/views/system/category/category.data.ts index 8adedf2..a57e10c 100644 --- a/jeecgboot-vue3/src/views/system/category/category.data.ts +++ b/jeecgboot-vue3/src/views/system/category/category.data.ts @@ -1,6 +1,6 @@ import { BasicColumn } from '/@/components/Table'; import { FormSchema } from '/@/components/Table'; -import { isMaterialRawAuxSubCategory } from './category.constants'; +import { isMaterialMinorCategory } from './category.constants'; export const columns: BasicColumn[] = [ { @@ -81,6 +81,6 @@ export const formSchema: FormSchema[] = [ defaultValue: false, renderComponentContent: '胶料', colProps: { span: 24 }, - show: ({ values }) => isMaterialRawAuxSubCategory(values.pid), + show: ({ values }) => isMaterialMinorCategory(values.pid), }, ]; diff --git a/jeecgboot-vue3/src/views/system/category/components/CategoryModal.vue b/jeecgboot-vue3/src/views/system/category/components/CategoryModal.vue index a59b3e2..09cec24 100644 --- a/jeecgboot-vue3/src/views/system/category/components/CategoryModal.vue +++ b/jeecgboot-vue3/src/views/system/category/components/CategoryModal.vue @@ -13,7 +13,8 @@ import { MATERIAL_RAW_AUX_CODE, materialRawAuxCategoryId, - isMaterialRawAuxSubCategory, + ensureMaterialCategoryContext, + isMaterialMinorCategory, toIsRubberFlag, fromIsRubberFlag, } from '../category.constants'; @@ -40,7 +41,7 @@ function normalizeSubmitValues(values: Recordable) { const payload = { ...values }; - payload.isRubber = isMaterialRawAuxSubCategory(payload.pid) ? toIsRubberFlag(payload.isRubber) : '0'; + payload.isRubber = isMaterialMinorCategory(payload.pid) ? toIsRubberFlag(payload.isRubber) : '0'; return payload; } //表单配置 @@ -64,6 +65,7 @@ setModalProps({ confirmLoading: false, minHeight: 80 }); isUpdate.value = !!data?.isUpdate; await ensureMaterialRawAuxCategoryId(); + await ensureMaterialCategoryContext(); // 代码逻辑说明: 分类字典data.record为空报错------------ isSubAdd.value = !data?.isUpdate && data.record && data.record.id; if (data?.record) { diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerMultiSelectModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerMultiSelectModal.vue index fe34ebc..42dfb34 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerMultiSelectModal.vue +++ b/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerMultiSelectModal.vue @@ -23,6 +23,7 @@ columns: [ { title: '设备名称', dataIndex: 'equipmentName', width: 160 }, { title: '设备编号', dataIndex: 'equipmentCode', width: 140 }, + { title: '有效体积', dataIndex: 'effectiveVolume', width: 100 }, { title: '设备类别', dataIndex: 'equipmentCategoryName', width: 120 }, { title: '设备类型', dataIndex: 'equipmentTypeName', width: 120 }, ], diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue index 023e99b..59a23de 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue +++ b/jeecgboot-vue3/src/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue @@ -79,7 +79,7 @@ } } if (!row?.id) { - emit('select', { equipmentLedgerId: '', equipmentName: '', equipmentCode: '' }); + emit('select', { equipmentLedgerId: '', equipmentName: '', equipmentCode: '', effectiveVolume: '' }); closeModal(); return; } @@ -87,6 +87,7 @@ equipmentLedgerId: row.id, equipmentName: row.equipmentName || '', equipmentCode: row.equipmentCode || '', + effectiveVolume: row.effectiveVolume || '', }); closeModal(); } diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.api.ts b/jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.api.ts index 0c034b7..bfa34cf 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.api.ts +++ b/jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.api.ts @@ -47,5 +47,8 @@ export const saveOrUpdate = (params, isUpdate) => defHttp.post({ url: isUpdate ? //update-begin---author:cursor ---date:20260522 for:【XSLMES-20260522-A38】配合示方生成混炼示方----------- export const buildMixingGeneratePreview = (params) => defHttp.get({ url: Api.buildMixingGeneratePreview, params }, { successMessageMode: 'none' }); -export const generateMixingSpec = (params) => defHttp.post({ url: Api.generateMixingSpec, params }); +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】批量生成混炼示方延长超时避免误报失败----------- +export const generateMixingSpec = (params) => + defHttp.post({ url: Api.generateMixingSpec, params, timeout: 120 * 1000 }); +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】批量生成混炼示方延长超时避免误报失败----------- //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A38】配合示方生成混炼示方----------- diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.api.ts b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.api.ts new file mode 100644 index 0000000..26e37af --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.api.ts @@ -0,0 +1,36 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/xslmes/mesXslMixerMaterialKindCfg/list', + save = '/xslmes/mesXslMixerMaterialKindCfg/add', + edit = '/xslmes/mesXslMixerMaterialKindCfg/edit', + addBatch = '/xslmes/mesXslMixerMaterialKindCfg/addBatch', + expandLines = '/xslmes/mesXslMixerMaterialKindCfg/expandLines', + deleteOne = '/xslmes/mesXslMixerMaterialKindCfg/delete', + deleteBatch = '/xslmes/mesXslMixerMaterialKindCfg/deleteBatch', + importExcel = '/xslmes/mesXslMixerMaterialKindCfg/importExcel', + exportXls = '/xslmes/mesXslMixerMaterialKindCfg/exportXls', + queryById = '/xslmes/mesXslMixerMaterialKindCfg/queryById', +} + +export const list = (params) => defHttp.get({ url: Api.list, params }); + +export const queryById = (params) => defHttp.get({ url: Api.queryById, params }); + +export const expandLines = (params) => defHttp.get({ url: Api.expandLines, params }); + +export const addBatch = (params) => defHttp.post({ url: Api.addBatch, params }, { successMessageMode: 'none' }); + +export const deleteOne = (params, handleSuccess) => + defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => handleSuccess()); + +export const batchDelete = (params, handleSuccess) => + defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }).then(() => handleSuccess()); + +export const saveOrUpdate = (params, isUpdate) => { + const url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url, params }, { successMessageMode: 'none' }); +}; + +export const getExportUrl = Api.exportXls; +export const getImportUrl = Api.importExcel; diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.data.ts b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.data.ts new file mode 100644 index 0000000..d2adc00 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfg.data.ts @@ -0,0 +1,156 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types'; +import { list as dictList } from '/@/views/system/dict/dict.api'; +import { loadTreeData } from '/@/api/common/api'; + +export const SOURCE_TYPE_OPTIONS = [ + { label: '数据字典', value: 'dict' }, + { label: '分类字典', value: 'category' }, +]; + +export const sourceTypeTextMap: Record = { + dict: '数据字典', + category: '分类字典', +}; + +export const columns: BasicColumn[] = [ + { title: '种类键值', align: 'center', dataIndex: 'kindKey', width: 120 }, + { title: '种类名称', align: 'center', dataIndex: 'kindName', width: 140 }, + { + title: '数据源', + align: 'center', + dataIndex: 'sourceType', + width: 100, + customRender: ({ text }) => sourceTypeTextMap[String(text || '')] || text || '', + }, + { title: '根名称', align: 'center', dataIndex: 'sourceRootName', width: 140 }, + { title: '对应分类', align: 'center', dataIndex: 'categoryRefName', width: 140 }, + { title: '优先级', align: 'center', dataIndex: 'priority', width: 80 }, + { title: '租户ID', align: 'center', dataIndex: 'tenantId', width: 80, defaultHidden: true }, + { title: '创建时间', align: 'center', dataIndex: 'createTime', width: 165 }, +]; + +export const searchFormSchema: FormSchema[] = [ + { label: '种类键值', field: 'kindKey', component: 'Input', colProps: { span: 6 } }, + { label: '种类名称', field: 'kindName', component: 'Input', colProps: { span: 6 } }, + { + label: '数据源', + field: 'sourceType', + component: 'Select', + componentProps: { options: SOURCE_TYPE_OPTIONS, allowClear: true }, + colProps: { span: 6 }, + }, + { label: '根编码', field: 'sourceRootCode', component: 'Input', colProps: { span: 6 } }, +]; + +export const batchFormSchema: FormSchema[] = [ + { + label: '数据源', + field: 'sourceType', + component: 'Select', + required: true, + defaultValue: 'category', + componentProps: { options: SOURCE_TYPE_OPTIONS }, + }, + { + label: '数据字典', + field: 'dictRootCode', + component: 'ApiSelect', + required: true, + ifShow: ({ values }) => values.sourceType === 'dict', + componentProps: { + api: () => dictList({ pageNo: 1, pageSize: 500 }), + resultField: 'records', + labelField: 'dictName', + valueField: 'dictCode', + showSearch: true, + placeholder: '请选择数据字典根', + }, + }, + { + label: '分类字典', + field: 'categoryRootCode', + component: 'ApiSelect', + required: true, + ifShow: ({ values }) => values.sourceType === 'category', + componentProps: { + api: loadTreeData, + params: { async: false, pcode: '0' }, + resultField: '', + labelField: 'title', + valueField: 'code', + showSearch: true, + placeholder: '请选择分类字典根', + }, + }, +]; + +export const editFormSchema: FormSchema[] = [ + { label: '', field: 'id', component: 'Input', show: false }, + { label: '', field: 'sourceType', component: 'Input', show: false }, + { label: '', field: 'sourceRootCode', component: 'Input', show: false }, + { label: '', field: 'sourceRootName', component: 'Input', show: false }, + { label: '', field: 'categoryRefId', component: 'Input', show: false }, + { label: '', field: 'categoryRefCode', component: 'Input', show: false }, + { + label: '种类键值', + field: 'kindKey', + component: 'Input', + componentProps: { disabled: true }, + }, + { + label: '种类名称', + field: 'kindName', + component: 'Input', + required: true, + }, + { + label: '对应分类', + field: 'categoryRefName', + component: 'Input', + componentProps: { disabled: true }, + }, + { + label: '优先级', + field: 'priority', + component: 'InputNumber', + required: true, + componentProps: { min: 0, precision: 0, style: { width: '100%' } }, + }, + { + label: '租户ID', + field: 'tenantId', + component: 'InputNumber', + componentProps: { disabled: true, style: { width: '100%' } }, + }, +]; + +export const batchJVxeColumns: JVxeColumn[] = [ + { title: '', key: 'categoryRefId', type: JVxeTypes.hidden }, + { title: '', key: 'categoryRefCode', type: JVxeTypes.hidden }, + { title: '', key: 'sourceType', type: JVxeTypes.hidden }, + { title: '', key: 'sourceRootCode', type: JVxeTypes.hidden }, + { title: '', key: 'sourceRootName', type: JVxeTypes.hidden }, + { title: '', key: 'tenantId', type: JVxeTypes.hidden }, + { title: '种类键值', key: 'kindKey', type: JVxeTypes.normal, width: 200, minWidth: 160, disabled: true }, + { title: '种类名称', key: 'kindName', type: JVxeTypes.input, width: 180, minWidth: 140 }, + { title: '对应分类', key: 'categoryRefName', type: JVxeTypes.normal, width: 180, minWidth: 140, disabled: true }, + { + title: '优先级', + key: 'priority', + type: JVxeTypes.inputNumber, + width: 110, + minWidth: 90, + align: 'center', + validateRules: [{ required: true, message: '请输入优先级' }], + }, +]; + +export const superQuerySchema = { + kindKey: { title: '种类键值', order: 0, view: 'text' }, + kindName: { title: '种类名称', order: 1, view: 'text' }, + sourceType: { title: '数据源', order: 2, view: 'list', enum: SOURCE_TYPE_OPTIONS }, + sourceRootCode: { title: '根编码', order: 3, view: 'text' }, + categoryRefName: { title: '对应分类', order: 4, view: 'text' }, + priority: { title: '优先级', order: 5, view: 'number' }, +}; diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList.vue b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList.vue new file mode 100644 index 0000000..bad582c --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/MesXslMixerMaterialKindCfgList.vue @@ -0,0 +1,162 @@ + + + + + + 新增 + + + 导出 + + + 导入 + + + + + + + 删除 + + + + + 批量操作 + + + + + + + + + + + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgBatchModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgBatchModal.vue new file mode 100644 index 0000000..b17fc53 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgBatchModal.vue @@ -0,0 +1,189 @@ + + + + + + 带出明细 + + + 种类配置明细 + + + + + + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgEditModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgEditModal.vue new file mode 100644 index 0000000..1de8418 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixerMaterialKindCfg/components/MesXslMixerMaterialKindCfgEditModal.vue @@ -0,0 +1,52 @@ + + + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.api.ts b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.api.ts index 328081e..7a49e28 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.api.ts +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.api.ts @@ -18,7 +18,10 @@ export const getExportUrl = Api.exportXls; export const getImportUrl = Api.importExcel; export const list = (params) => defHttp.get({ url: Api.list, params }); export const queryById = (params) => defHttp.get({ url: Api.queryById, params }); -export const saveOrUpdate = (params, isUpdate) => defHttp.post({ url: isUpdate ? Api.edit : Api.save, params }); +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】混炼示方主子表保存延长超时避免误报失败----------- +export const saveOrUpdate = (params, isUpdate) => + defHttp.post({ url: isUpdate ? Api.edit : Api.save, params, timeout: 60 * 1000 }); +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A45】混炼示方主子表保存延长超时避免误报失败----------- export const queryIssueNumberOptions = (params) => defHttp.get({ url: Api.queryIssueNumberOptions, params }); export const queryPurposeOptions = (params) => defHttp.get({ url: Api.queryPurposeOptions, params }); diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts index 415d138..2187215 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/MesXslMixingSpec.data.ts @@ -91,10 +91,27 @@ export const materialColumns: JVxeColumn[] = [ { title: '物料大类', key: 'materialMajor', type: JVxeTypes.input, width: 100, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH }, { title: '物料小类', key: 'materialMinor', type: JVxeTypes.input, width: 120, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH }, { title: '种类', key: 'materialKind', type: JVxeTypes.input, width: 80, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH }, - { title: '密炼物料名称', key: 'mixerMaterialName', type: JVxeTypes.input, width: 160, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH }, + { + title: '密炼物料名称', + key: 'mixerMaterialName', + type: JVxeTypes.slot, + slotName: 'mixerMaterialNameSlot', + width: 160, + minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, + }, { title: '密炼物料描述', key: 'mixerMaterialDesc', type: JVxeTypes.input, width: 220, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH }, { title: '单重', key: 'unitWeight', type: JVxeTypes.inputNumber, width: 72, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' }, - { title: '累计', key: 'accumWeight', type: JVxeTypes.inputNumber, width: 72, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' }, + //update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】累计列按种类分组合计只读展示----------- + { + title: '累计', + key: 'accumWeight', + type: JVxeTypes.inputNumber, + width: 72, + minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, + align: 'center', + disabled: true, + }, + //update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】累计列按种类分组合计只读展示----------- { title: '顺序', key: 'seqNo', type: JVxeTypes.inputNumber, width: 64, minWidth: MIXING_MATERIAL_MIN_COLUMN_WIDTH, align: 'center' }, ]; @@ -196,6 +213,280 @@ export function calcMixingMaterialTableWidth(columns: JVxeColumn[], widthMap: Re } //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A17】橡胶及配合剂明细列展示设置----------- +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计----------- +/** 是否为有效明细行(参与种类分组) */ +function isMixingMaterialDataRow(row: Recordable): boolean { + if (!row) { + return false; + } + return !!(row.mixerMaterialName || row.materialKind || row.unitWeight != null && row.unitWeight !== ''); +} + +/** 规范化种类字段,用于连续行分组 */ +function normalizeMixingMaterialKind(row: Recordable): string { + const kind = row?.materialKind; + return kind != null && String(kind).trim() !== '' ? String(kind).trim() : ''; +} + +/** 按种类连续分组,累计写入每组最后一行 */ +export function fillMixingMaterialAccumWeight(rows: Recordable[] = []): Recordable[] { + if (!rows?.length) { + return rows; + } + let index = 0; + while (index < rows.length) { + const current = rows[index]; + if (!isMixingMaterialDataRow(current)) { + current.accumWeight = null; + index++; + continue; + } + const kind = normalizeMixingMaterialKind(current); + let groupEnd = index; + let sum = 0; + while (groupEnd < rows.length) { + const row = rows[groupEnd]; + if (!isMixingMaterialDataRow(row) || normalizeMixingMaterialKind(row) !== kind) { + break; + } + const weight = toMixingMaterialNumber(row.unitWeight); + if (weight != null) { + sum += weight; + } + groupEnd++; + } + for (let rowIndex = index; rowIndex < groupEnd; rowIndex++) { + rows[rowIndex].accumWeight = + rowIndex === groupEnd - 1 && sum !== 0 ? roundMixingMaterialNumber(sum) : null; + } + index = groupEnd; + } + return rows; +} + +/** 安全解析明细数值,避免字符串拼接 */ +function toMixingMaterialNumber(value: unknown): number | null { + if (value == null || value === '') { + return null; + } + const num = Number(value); + return Number.isFinite(num) ? num : null; +} + +/** 混炼示方重量小数位(与后端 BigDecimal 精度一致) */ +const MIXING_MATERIAL_WEIGHT_SCALE = 6; + +/** 重量四舍五入,消除浮点累加误差 */ +function roundMixingMaterialNumber(value: number): number { + return Number(value.toFixed(MIXING_MATERIAL_WEIGHT_SCALE)); +} + +/** 格式化重量展示文本 */ +function formatMixingMaterialWeight(value: unknown): string { + const num = toMixingMaterialNumber(value); + if (num == null) { + return ''; + } + return String(roundMixingMaterialNumber(num)); +} + +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A43】换算系数联动明细单重实时计算----------- +/** 规范化换算系数,空值或非正数按 1 处理 */ +export function normalizeMixingConvertFactor(factor: unknown): number { + const num = toMixingMaterialNumber(factor); + if (num == null || num <= 0) { + return 1; + } + return num; +} + +/** 基准单重 × 换算系数 */ +export function calcMixingMaterialConvertedWeight(base: unknown, factor: unknown): number | null { + const baseNum = toMixingMaterialNumber(base); + if (baseNum == null) { + return null; + } + return Number((baseNum * normalizeMixingConvertFactor(factor)).toFixed(MIXING_MATERIAL_WEIGHT_SCALE)); +} + +/** 从当前显示单重反推基准单重 */ +export function syncMaterialBaseUnitWeightFromDisplay(row: Recordable, factor: unknown) { + if (!row) { + return; + } + const unit = toMixingMaterialNumber(row.unitWeight); + if (unit == null) { + row.baseUnitWeight = null; + return; + } + row.baseUnitWeight = Number((unit / normalizeMixingConvertFactor(factor)).toFixed(MIXING_MATERIAL_WEIGHT_SCALE)); +} + +/** 初始化明细行基准单重(编辑加载时由已保存单重反推) */ +export function initMaterialBaseUnitWeight(row: Recordable, factor: unknown, force = false) { + if (!isMixingMaterialDataRow(row)) { + row.baseUnitWeight = null; + return; + } + if (!force && toMixingMaterialNumber(row.baseUnitWeight) != null) { + return; + } + syncMaterialBaseUnitWeightFromDisplay(row, factor); +} + +/** 批量初始化基准单重 */ +export function initMaterialBaseUnitWeights(rows: Recordable[] = [], factor: unknown, force = false) { + for (const row of rows) { + initMaterialBaseUnitWeight(row, factor, force); + } + return rows; +} + +/** 按换算系数重算所有明细单重 */ +export function applyConvertFactorToMaterialRows( + rows: Recordable[] = [], + factor: unknown, + prevFactor?: unknown, +): Recordable[] { + const nextFactor = normalizeMixingConvertFactor(factor); + const oldFactor = prevFactor != null ? normalizeMixingConvertFactor(prevFactor) : nextFactor; + for (const row of rows) { + if (!isMixingMaterialDataRow(row)) { + continue; + } + let base = toMixingMaterialNumber(row.baseUnitWeight); + if (base == null) { + const unit = toMixingMaterialNumber(row.unitWeight); + if (unit == null) { + continue; + } + base = roundMixingMaterialNumber(unit / oldFactor); + row.baseUnitWeight = base; + } + row.unitWeight = calcMixingMaterialConvertedWeight(base, nextFactor); + } + return rows; +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A43】换算系数联动明细单重实时计算----------- + +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A46】填充体积按单重/比重/机台有效体积自动计算----------- +/** 解析设备有效体积(支持纯数字或带单位字符串) */ +export function parseMixingEffectiveVolume(raw: unknown): number | null { + if (raw == null || raw === '') { + return null; + } + const text = String(raw).trim(); + if (!text) { + return null; + } + const direct = toMixingMaterialNumber(text); + if (direct != null && direct > 0) { + return direct; + } + const matched = text.match(/([0-9]+(?:\.[0-9]+)?)/); + if (!matched) { + return null; + } + const parsed = Number(matched[1]); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; +} + +/** 按段数与比重字段选择本段计算用比重 */ +export function resolveMixingSpecificGravity(form: Recordable = {}): number | null { + const motherSg = toMixingMaterialNumber(form.motherRubberSg); + const finalSg = toMixingMaterialNumber(form.finalRubberSg); + const stageCount = String(form.stageCount || '').trim(); + const stageMatch = stageCount.match(/^(\d+)\/(\d+)$/); + const isFinalStage = stageMatch ? stageMatch[1] === stageMatch[2] : false; + if (isFinalStage && finalSg != null && finalSg > 0) { + return finalSg; + } + if (motherSg != null && motherSg > 0) { + return motherSg; + } + if (finalSg != null && finalSg > 0) { + return finalSg; + } + return null; +} + +/** + * 填充体积(%) = 单重合计 ÷ 比重 ÷ 机台有效体积(L) × 100 + * 单重合计已含换算系数,此处不再重复乘换算系数 + */ +export function calcMixingFillVolume(totalWeight: unknown, specificGravity: unknown, effectiveVolume: unknown): number | null { + const weight = toMixingMaterialNumber(totalWeight); + const sg = toMixingMaterialNumber(specificGravity); + const volume = parseMixingEffectiveVolume(effectiveVolume); + if (weight == null || weight <= 0 || sg == null || sg <= 0 || volume == null || volume <= 0) { + return null; + } + const materialVolume = weight / sg; + return Number(((materialVolume / volume) * 100).toFixed(6)); +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A46】填充体积按单重/比重/机台有效体积自动计算----------- + +/** 汇总有效明细行的单重合计 */ +export function calcMixingMaterialUnitWeightTotal(rows: Recordable[] = []): number | null { + let sum = 0; + let hasAny = false; + for (const row of rows) { + if (!isMixingMaterialDataRow(row)) { + continue; + } + const weight = toMixingMaterialNumber(row.unitWeight); + if (weight != null) { + sum += weight; + hasAny = true; + } + } + return hasAny ? roundMixingMaterialNumber(sum) : null; +} + +/** 汇总有效明细行的累计合计(与单重合计一致) */ +export function calcMixingMaterialAccumWeightTotal(rows: Recordable[] = []): number | null { + return calcMixingMaterialUnitWeightTotal(rows); +} + +export interface MixingMaterialFooterCell { + key: string; + width: number; + text: string; + align?: 'left' | 'center' | 'right'; + isLabel?: boolean; + isTotal?: boolean; +} + +/** 构建橡胶及配合剂明细底部合计行单元格(列宽与明细表同步) */ +export function buildMixingMaterialFooterCells( + columns: JVxeColumn[], + widthMap: Record, + totals: { unitWeight?: number | null; accumWeight?: number | null }, +): MixingMaterialFooterCell[] { + const unitWeightIndex = columns.findIndex((col) => String(col.key) === 'unitWeight'); + const formatTotal = (value: number | null | undefined) => formatMixingMaterialWeight(value); + + return columns.map((col, index) => { + const key = String(col.key); + const width = widthMap[key] ?? Number(col.width) ?? 80; + if (key === 'unitWeight') { + return { key, width, text: formatTotal(totals.unitWeight), align: 'center', isTotal: true }; + } + if (key === 'accumWeight') { + return { key, width, text: formatTotal(totals.accumWeight), align: 'center', isTotal: true }; + } + const isLabelCol = unitWeightIndex > 0 && index === unitWeightIndex - 1; + return { + key, + width, + text: isLabelCol ? '合计' : '', + align: 'center', + isLabel: isLabelCol, + }; + }); +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A41】橡胶及配合剂明细累计按种类分组合计----------- + //update-begin---author:cursor ---date:20260522 for:【XSLMES-20260522-A20】明细表默认列宽对齐参考图----------- /** 混合步骤/下密炼机明细列可缩小到的最小宽度 */ export const MIXING_STEP_MIN_COLUMN_WIDTH = 48; @@ -412,6 +703,9 @@ export const MIXING_VXE_MINI_HEADER_HEIGHT = 36; /** vxe mini 行高 */ export const MIXING_VXE_MINI_ROW_HEIGHT = 32; +/** 橡胶及配合剂明细合计行高度(含边框) */ +export const MIXING_MATERIAL_FOOTER_ROW_HEIGHT = MIXING_VXE_MINI_ROW_HEIGHT + 1; + //update-begin---author:cursor ---date:20260522 for:【XSLMES-20260522-A29】胶料/混合步骤表格高度按行数完整展示----------- /** 计算橡胶及配合剂明细表格展示高度 */ export function calcMixingMaterialTableHeight(rowCount = MIXING_MATERIAL_VISIBLE_ROW_COUNT) { @@ -637,3 +931,99 @@ export function ensureMixingDetailRows(rows: Recordable[] = [], defaultCount: nu } //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A39】编辑页明细补齐默认空行与新增一致----------- //update-end---author:cursor ---date:20260522 for:【XSLMES-20260522-A22】明细表默认空行数----------- + +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】混炼示方密炼物料选料弹窗与种类解析----------- +/** 混炼示方选料弹窗:隐藏的小类 ID 偏好 localStorage 键 */ +export const MIXING_MATERIAL_PICKER_HIDDEN_CATEGORY_CACHE_KEY = 'mes_xsl_mixing_spec_material_picker_hidden_categories'; + +export interface MixingMaterialPickerCategoryItem { + id: string; + name: string; + majorId: string; + majorName: string; + label: string; +} + +const mixingMaterialPickerStorage = createLocalStorage(); + +export function loadMixingMaterialPickerHiddenCategoryIds(): string[] { + const raw = mixingMaterialPickerStorage.get(MIXING_MATERIAL_PICKER_HIDDEN_CATEGORY_CACHE_KEY); + return Array.isArray(raw) ? raw.map(String) : []; +} + +export function saveMixingMaterialPickerHiddenCategoryIds(ids: string[]) { + mixingMaterialPickerStorage.set(MIXING_MATERIAL_PICKER_HIDDEN_CATEGORY_CACHE_KEY, ids || []); +} + +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】选料弹窗小类树为空时重置隐藏配置----------- +/** 过滤无效隐藏项;若全部小类被隐藏则自动重置,避免左侧树只剩「全部小类」 */ +export function sanitizeMixingMaterialPickerHiddenCategoryIds(allMinorIds: string[], hidden: string[]) { + const allSet = new Set((allMinorIds || []).map(String)); + const filtered = (hidden || []).map(String).filter((id) => allSet.has(id)); + if (allSet.size > 0 && filtered.length >= allSet.size) { + return []; + } + return filtered; +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】选料弹窗小类树为空时重置隐藏配置----------- + +/** 解析混炼示方明细种类:小类勾选胶料则显示「胶料」,否则显示小类名 */ +export function resolveMixingMaterialKindFromCategory(isRubber?: unknown, minorName?: string) { + if (isRubber === '1' || isRubber === 1 || isRubber === true) { + return '胶料'; + } + return minorName != null && String(minorName).trim() !== '' ? String(minorName).trim() : ''; +} + +//update-begin---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】选料弹窗自动/人工称量列与种类映射----------- +/** 与配合示方「自动/人工」列相同字典 */ +export const MIXING_MATERIAL_PICKER_WEIGH_MODE_DICT = 'xslmes_formula_spec_weigh_mode'; + +/** 选料弹窗表格列(隐藏 ERP 编号,新增仅本次有效的自动/人工称量) */ +export const mixingMaterialPickerTableColumns: BasicColumn[] = [ + { title: '物料编码', align: 'center', width: 120, dataIndex: 'materialCode' }, + { title: '物料名称', align: 'center', width: 160, dataIndex: 'materialName' }, + { title: '自动/人工称量', align: 'center', width: 132, dataIndex: 'pickerWeighMode' }, + { title: '物料大类', align: 'center', width: 120, dataIndex: 'majorCategoryId_dictText' }, + { title: '物料小类', align: 'center', width: 120, dataIndex: 'minorCategoryId_dictText' }, + { title: '物料描述', align: 'center', width: 180, ellipsis: true, dataIndex: 'materialDesc' }, +]; + +/** 配合示方称量方式 -> 混炼示方种类(与后端 resolveWeighModeMaterialKind 一致) */ +export function resolveMixingMaterialKindFromWeighMode(weighMode?: string) { + if (weighMode == null || String(weighMode).trim() === '') { + return ''; + } + const normalized = String(weighMode).trim(); + const lower = normalized.toLowerCase(); + if (lower.startsWith('auto') || normalized.includes('自动')) { + return '自动'; + } + if (lower === 'manual' || normalized.includes('人工')) { + return '人工'; + } + return ''; +} + +/** 选料确认时种类:称量方式优先,否则按小类胶料/小类名 */ +export function resolveMixingMaterialKindForPicker(weighMode: string | undefined, isRubber?: unknown, minorName?: string) { + const fromWeighMode = resolveMixingMaterialKindFromWeighMode(weighMode); + if (fromWeighMode) { + return fromWeighMode; + } + return resolveMixingMaterialKindFromCategory(isRubber, minorName); +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】选料弹窗自动/人工称量列与种类映射----------- + +/** 选择密炼物料后回填混炼示方橡胶及配合剂明细行 */ +export function applyMixingMaterialFromSelection(row: Recordable, material: Recordable, materialKind: string) { + if (!row || !material) { + return; + } + row.mixerMaterialName = material.materialName || material.materialCode || ''; + row.mixerMaterialDesc = material.materialDesc || material.materialName || material.materialCode || ''; + row.materialMajor = material.majorCategoryId_dictText || ''; + row.materialMinor = material.minorCategoryId_dictText || ''; + row.materialKind = materialKind || row.materialMinor || ''; +} +//update-end---author:cursor ---date:20260525 for:【XSLMES-20260525-A50】混炼示方密炼物料选料弹窗与种类解析----------- diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue new file mode 100644 index 0000000..e4d6850 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialCategorySetting.vue @@ -0,0 +1,221 @@ + + + + + + 小类展示 + + + + + + + 暂无物料小类,请确认分类字典 XSLMES_MATERIAL 已配置 + + + + {{ group.majorName }} + + onMinorVisibleChange(opt.value, e.target.checked)" + > + {{ opt.label }} + + + + + + + 重置 + 保存 + + + + + + + + + + + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue new file mode 100644 index 0000000..9c28399 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingMaterialSelectModal.vue @@ -0,0 +1,406 @@ + + + + + + 搜索 + + + + + + + + + + setPickerWeighMode(record.id, val)" + /> + + + + + + + + + + + + + + + diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue index d1c244c..9db7a0d 100644 --- a/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue +++ b/jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue @@ -55,7 +55,15 @@ 换算系数 - + 填充体积 @@ -80,7 +88,7 @@ 母胶比重 - + 段数 @@ -104,7 +112,7 @@ 终炼胶比重 - + 适用工厂 @@ -181,25 +189,60 @@ - + + + + + + + {{ row.mixerMaterialName }} + + + + + + + @@ -368,11 +411,11 @@ - - - + + +