From 5d7335d1a73305f4c739a7817fd7d86261ec5e06 Mon Sep 17 00:00:00 2001 From: geht <2947093423@qq.com> Date: Fri, 15 May 2026 11:19:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=93=E5=BA=93=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E7=AE=A1=E7=90=86=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E5=8E=86=E5=8F=B2=E5=88=86=E7=B1=BB=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=8C=E9=87=8D=E6=9E=84=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=9C=8D=E5=8A=A1=E5=92=8C=E6=8E=A7=E5=88=B6=E5=99=A8?= =?UTF-8?q?=E4=BB=A5=E6=8F=90=E5=8D=87=E7=B3=BB=E7=BB=9F=E7=9A=84=E5=8F=AF?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=80=A7=E5=92=8C=E6=89=A9=E5=B1=95=E6=80=A7?= =?UTF-8?q?=E3=80=82=E5=90=8C=E6=97=B6=EF=BC=8C=E5=A2=9E=E5=BC=BA=E5=8E=9F?= =?UTF-8?q?=E6=9D=90=E6=96=99=E5=8D=A1=E7=89=87=E5=92=8C=E5=BA=93=E5=8C=BA?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9A=84=E6=9F=A5=E8=AF=A2=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=95=B0=E6=8D=AE=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E7=9A=84=E5=87=86=E7=A1=AE=E6=80=A7=E5=92=8C=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...rehouseAreaActualCapacityContribution.java | 16 + ...seAreaDisplayedActualCapacityMediator.java | 31 + ...rialCardRemainQtyCapacityContribution.java | 134 ++++ ...XslMesWarehouseAreaCapacityProperties.java | 43 ++ .../constant/MesXslWarehouseCategory.java | 49 +- .../MesXslDesktopAnonController.java | 8 +- .../MesXslRawMaterialCardController.java | 30 + ...slRawMaterialWarehouseBoardController.java | 38 + .../MesXslWarehouseAreaController.java | 34 +- .../xslmes/dto/MesXslAreaRemainQtySumDTO.java | 20 + .../mapper/MesXslRawMaterialCardMapper.java | 12 +- .../xml/MesXslRawMaterialCardMapper.xml | 14 + ...esXslRawMaterialWarehouseBoardService.java | 18 + .../service/IMesXslWarehouseAreaService.java | 7 + ...lRawMaterialWarehouseBoardServiceImpl.java | 248 ++++++ .../impl/MesXslWarehouseAreaServiceImpl.java | 12 + .../impl/MesXslWarehouseServiceImpl.java | 8 +- .../vo/MesXslRawMaterialWarehouseBoardVO.java | 91 +++ .../src/main/resources/application.yml | 4 +- .../application-xslmes-warehouse-area.yml | 11 + ...__mes_xsl_raw_material_warehouse_board.sql | 19 + .../MesXslRawMaterialWarehouseBoard.api.ts | 8 + .../MesXslRawMaterialWarehouseBoard.vue | 725 ++++++++++++++++++ 23 files changed, 1568 insertions(+), 12 deletions(-) create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaActualCapacityContribution.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaDisplayedActualCapacityMediator.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/impl/RawMaterialCardRemainQtyCapacityContribution.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/config/XslMesWarehouseAreaCapacityProperties.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialWarehouseBoardController.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dto/MesXslAreaRemainQtySumDTO.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialWarehouseBoardService.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialWarehouseBoardServiceImpl.java create mode 100644 jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/vo/MesXslRawMaterialWarehouseBoardVO.java create mode 100644 jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/config/application-xslmes-warehouse-area.yml create mode 100644 jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_56__mes_xsl_raw_material_warehouse_board.sql create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.api.ts create mode 100644 jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.vue diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaActualCapacityContribution.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaActualCapacityContribution.java new file mode 100644 index 00000000..87fc11a8 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaActualCapacityContribution.java @@ -0,0 +1,16 @@ +package org.jeecg.modules.xslmes.capacity; + +import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; + +import java.util.List; + +/** + * 库区展示用「实际存放量」数据源贡献者(可多实现并按 Spring Order 编排) + */ +public interface WarehouseAreaActualCapacityContribution { + + /** + * @param areas 通常为列表/分页的一段记录(内存回填,不写库) + */ + void contribute(List areas); +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaDisplayedActualCapacityMediator.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaDisplayedActualCapacityMediator.java new file mode 100644 index 00000000..4519d26f --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/WarehouseAreaDisplayedActualCapacityMediator.java @@ -0,0 +1,31 @@ +package org.jeecg.modules.xslmes.capacity; + +import lombok.RequiredArgsConstructor; +import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * 编排库区「实际存放量」展示回填(可多 {@link WarehouseAreaActualCapacityContribution} 扩展)。 + */ +@Component +@RequiredArgsConstructor +public class WarehouseAreaDisplayedActualCapacityMediator { + + private final List contributions; + + public void apply(Collection areas) { + if (areas == null || areas.isEmpty()) { + return; + } + List list = new ArrayList<>(areas); + List ordered = contributions.stream().sorted(AnnotationAwareOrderComparator.INSTANCE).toList(); + for (WarehouseAreaActualCapacityContribution c : ordered) { + c.contribute(list); + } + } +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/impl/RawMaterialCardRemainQtyCapacityContribution.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/impl/RawMaterialCardRemainQtyCapacityContribution.java new file mode 100644 index 00000000..7e113f3e --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/capacity/impl/RawMaterialCardRemainQtyCapacityContribution.java @@ -0,0 +1,134 @@ +package org.jeecg.modules.xslmes.capacity.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jeecg.modules.xslmes.capacity.WarehouseAreaActualCapacityContribution; +import org.jeecg.modules.xslmes.config.XslMesWarehouseAreaCapacityProperties; +import org.jeecg.modules.xslmes.dto.MesXslAreaRemainQtySumDTO; +import org.jeecg.modules.system.service.ISysCategoryService; +import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; +import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 用「原材料卡片」按库区(trim)汇总 remaining_quantity,回填展示字段 actual_capacity(不写库)。 + */ +@Slf4j +@Component +@Order(100) +@RequiredArgsConstructor +public class RawMaterialCardRemainQtyCapacityContribution implements WarehouseAreaActualCapacityContribution { + + private final XslMesWarehouseAreaCapacityProperties properties; + private final MesXslRawMaterialCardMapper mesXslRawMaterialCardMapper; + private final ISysCategoryService sysCategoryService; + + @Override + public void contribute(List areas) { + if (areas == null || areas.isEmpty()) { + return; + } + if (!properties.isEnabled()) { + return; + } + Set allowedCategories = resolveAllowedWarehouseCategoryIds(); + if (allowedCategories.isEmpty()) { + log.debug("[WarehouseAreaActualCapacity] 原材料库分类编码/ID 均未解析到有效 warehouse_category,跳过 RawMaterialCard 策略"); + return; + } + + List targets = + areas.stream().filter(a -> a != null && allowedCategories.contains(normId(a.getWarehouseCategory())) && StringUtils.isNotBlank(a.getAreaCode())) + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + + Collection codes = + targets.stream().map(MesXslWarehouseArea::getAreaCode).map(String::trim).filter(StringUtils::isNotBlank).distinct().collect(Collectors.toList()); + + Map sumMap = loadSumMap(codes); + for (MesXslWarehouseArea a : targets) { + String ck = trimKey(a.getAreaCode()); + long sum = sumMap.getOrDefault(ck, 0L); + int display; + try { + display = Math.toIntExact(Math.min(sum, Integer.MAX_VALUE)); + } catch (ArithmeticException ex) { + display = Integer.MAX_VALUE; + } + a.setActualCapacity(display); + } + } + + private Map loadSumMap(Collection codes) { + if (codes.isEmpty()) { + return Collections.emptyMap(); + } + List rows = mesXslRawMaterialCardMapper.sumRemainingQtyByTrimmedWarehouseArea(codes); + if (rows == null || rows.isEmpty()) { + return Collections.emptyMap(); + } + Map map = new HashMap<>(Math.max(16, rows.size() * 2)); + for (MesXslAreaRemainQtySumDTO row : rows) { + if (row == null || row.getAreaCode() == null) { + continue; + } + String k = row.getAreaCode().trim(); + if (StringUtils.isBlank(k)) { + continue; + } + long v = row.getQtySum() == null ? 0L : row.getQtySum(); + map.put(k, v); + } + return map; + } + + private static String trimKey(String code) { + return code == null ? "" : code.trim(); + } + + private static String normId(String id) { + return StringUtils.trimToEmpty(id); + } + + private static Set normalizeIdSet(Collection raw) { + if (raw == null) { + return Collections.emptySet(); + } + return raw.stream().filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toSet()); + } + + /** 配置的 id + 配置的 code 解析出的 id 合并 */ + private Set resolveAllowedWarehouseCategoryIds() { + Set set = new HashSet<>(normalizeIdSet(properties.getRawMaterialWarehouseCategoryIds())); + Collection codes = properties.getRawMaterialWarehouseCategoryCodes(); + if (codes != null) { + for (String c : codes) { + if (StringUtils.isBlank(c)) { + continue; + } + try { + String id = sysCategoryService.queryIdByCode(c.trim()); + if (StringUtils.isNotBlank(id)) { + set.add(id.trim()); + } + } catch (Exception ex) { + log.warn("[WarehouseAreaActualCapacity] 解析原材料库分类编码失败 code={}: {}", c, ex.getMessage()); + } + } + } + return set; + } +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/config/XslMesWarehouseAreaCapacityProperties.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/config/XslMesWarehouseAreaCapacityProperties.java new file mode 100644 index 00000000..d5c37152 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/config/XslMesWarehouseAreaCapacityProperties.java @@ -0,0 +1,43 @@ +package org.jeecg.modules.xslmes.config; + +import lombok.Data; +import org.jeecg.modules.xslmes.constant.MesXslWarehouseCategory; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 库区列表「实际存放量」展示口径配置(不写库) + *

+ * 推荐使用 {@link #rawMaterialWarehouseCategoryCodes},启动时解析为 sys_category.id, + * 避免在配置里维护易变的雪花 id;也可用 {@link #rawMaterialWarehouseCategoryIds} 直接指定 id。 + */ +@Data +@Component +@ConfigurationProperties(prefix = "xslmes.warehouse-area.display-actual-capacity") +public class XslMesWarehouseAreaCapacityProperties { + + /** + * 是否启用展示层回填「实际存放量」 + */ + private boolean enabled = true; + + /** + * 原材料库分类编码列表(resolve 成 id 后与 warehouse_category 匹配)。 + * 默认兼容 {@link MesXslWarehouseCategory#RAW_MATERIAL_CATEGORY_CODES}。 + */ + private List rawMaterialWarehouseCategoryCodes = defaultRawCodes(); + + /** + * 直接指定 warehouse_category=sys_category.id(与 codes 解析结果合并)。 + * 可为空。 + */ + private List rawMaterialWarehouseCategoryIds = new ArrayList<>(); + + private static List defaultRawCodes() { + return new ArrayList<>(MesXslWarehouseCategory.RAW_MATERIAL_CATEGORY_CODES); + } +} + \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/constant/MesXslWarehouseCategory.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/constant/MesXslWarehouseCategory.java index 63bd5087..db79d459 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/constant/MesXslWarehouseCategory.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/constant/MesXslWarehouseCategory.java @@ -1,7 +1,19 @@ package org.jeecg.modules.xslmes.constant; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** * MES 仓库分类(分类字典 sys_category,根编码 {@link #ROOT_CODE}) + * + *

说明:历史版本曾在编码上使用 F1/F2 后缀;当前业务树已不再按楼层区分, + * 「F1」「F2」仅可能作为编码后缀保留,请以 {@link #RAW_MATERIAL_CATEGORY_CODES}、 + * 客户/供应商库编码集合为准做兼容。

*/ public final class MesXslWarehouseCategory { @@ -10,8 +22,37 @@ public final class MesXslWarehouseCategory { /** 根节点编码,与 JCategorySelect 的 pcode 一致 */ public static final String ROOT_CODE = "XSLMES_WH"; - /** 客户库(二楼):须关联客户 */ - public static final String CUSTOMER_CATEGORY_CODE = "XSLMES_WH_F2_KH"; - /** 供应商库(二楼):须关联供应商 */ - public static final String SUPPLIER_CATEGORY_CODE = "XSLMES_WH_F2_GYS"; + /** 兼容历史:原材料库在库中可能出现的分类编码(新环境多为 F1,老数据可能仍为 F2) */ + public static final List RAW_MATERIAL_CATEGORY_CODES = + Collections.unmodifiableList(Arrays.asList("XSLMES_WH_F1_YCL", "XSLMES_WH_F2_YCL")); + + /** 兼容历史编号的「客户库」分类编码集合 */ + private static final Set CUSTOMER_CATEGORY_CODES = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList("XSLMES_WH_F1_KH", "XSLMES_WH_F2_KH"))); + + /** 兼容历史编号的「供应商库」分类编码集合 */ + private static final Set SUPPLIER_CATEGORY_CODES = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList("XSLMES_WH_F1_GYS", "XSLMES_WH_F2_GYS"))); + + /** + * @deprecated 请改用 {@link #isCustomerWarehouseCategoryCode(String)} + */ + @Deprecated + public static final String CUSTOMER_CATEGORY_CODE = "XSLMES_WH_F1_KH"; + + /** + * @deprecated 请改用 {@link #isSupplierWarehouseCategoryCode(String)} + */ + @Deprecated + public static final String SUPPLIER_CATEGORY_CODE = "XSLMES_WH_F1_GYS"; + + /** 是否为「客户库」分类编码(兼容 XSLMES_WH_F1_KH / XSLMES_WH_F2_KH) */ + public static boolean isCustomerWarehouseCategoryCode(String code) { + return CUSTOMER_CATEGORY_CODES.contains(StringUtils.trimToEmpty(code)); + } + + /** 是否为「供应商库」分类编码(兼容 XSLMES_WH_F1_GYS / XSLMES_WH_F2_GYS) */ + public static boolean isSupplierWarehouseCategoryCode(String code) { + return SUPPLIER_CATEGORY_CODES.contains(StringUtils.trimToEmpty(code)); + } } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java index c69aeafb..76f5f846 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java @@ -43,6 +43,7 @@ import org.jeecg.modules.xslmes.service.MesXslStompNotifyService; import org.springframework.web.bind.annotation.*; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -702,6 +703,7 @@ public class MesXslDesktopAnonController { HttpServletRequest req) { QueryWrapper qw = QueryGenerator.initQueryWrapper(mesXslWarehouseArea, req.getParameterMap()); IPage page = warehouseAreaService.page(new Page<>(pageNo, pageSize), qw); + warehouseAreaService.enrichDisplayedActualCapacity(page.getRecords()); return Result.OK(page); } @@ -709,7 +711,11 @@ public class MesXslDesktopAnonController { @GetMapping("/xslmes/mesXslWarehouseArea/anon/queryById") public Result warehouseAreaAnonQueryById(@RequestParam(name = "id") String id) { MesXslWarehouseArea entity = warehouseAreaService.getById(id); - return entity != null ? Result.OK(entity) : Result.error("未找到对应数据"); + if (entity == null) { + return Result.error("未找到对应数据"); + } + warehouseAreaService.enrichDisplayedActualCapacity(Collections.singletonList(entity)); + return Result.OK(entity); } @Operation(summary = "库区-免密添加") diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialCardController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialCardController.java index f784d150..902e33a1 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialCardController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialCardController.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jeecg.common.util.oConvertUtils; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.jeecg.common.api.vo.Result; @@ -81,7 +82,36 @@ public class MesXslRawMaterialCardController extends JeecgController queryWrapper = QueryGenerator.initQueryWrapper(mesXslRawMaterialCard, req.getParameterMap()); + if (filterByWarehouseArea) { + queryWrapper.apply("TRIM(COALESCE(warehouse_area,'')) = {0}", areaEq); + } + // 看板等扩展查询:remaining_quantity「有剩余 / 无剩余」(非实体字段,单独拼条件) + String remainQtyFilter = req.getParameter("remainQtyFilter"); + if ("has".equals(remainQtyFilter)) { + queryWrapper.gt("remaining_quantity", 0); + } else if ("none".equals(remainQtyFilter)) { + queryWrapper.and(w -> w.isNull("remaining_quantity").or().le("remaining_quantity", 0)); + } + String mixKw = req.getParameter("mixKeyword"); + if (StringUtils.isNotBlank(mixKw)) { + final String kw = mixKw.trim(); + queryWrapper.and( + w -> w.like("barcode", kw) + .or() + .like("batch_no", kw) + .or() + .like("material_name", kw) + .or() + .like("material_id", kw)); + } Page page = new Page<>(pageNo, pageSize); IPage pageList = mesXslRawMaterialCardService.page(page, queryWrapper); return Result.OK(pageList); diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialWarehouseBoardController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialWarehouseBoardController.java new file mode 100644 index 00000000..3b9c975a --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialWarehouseBoardController.java @@ -0,0 +1,38 @@ +package org.jeecg.modules.xslmes.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.jeecg.common.api.vo.Result; +import org.jeecg.modules.xslmes.service.IMesXslRawMaterialWarehouseBoardService; +import org.jeecg.modules.xslmes.vo.MesXslRawMaterialWarehouseBoardVO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 原材料库区看板:库区主数据 + 原材料卡片聚合 + */ +@Tag(name = "原材料库区看板") +@RestController +@RequestMapping("/xslmes/mesXslRawMaterialWarehouseBoard") +@Slf4j +public class MesXslRawMaterialWarehouseBoardController { + + @Autowired + private IMesXslRawMaterialWarehouseBoardService mesXslRawMaterialWarehouseBoardService; + + @Operation(summary = "原材料库区看板-汇总数据") + @RequiresPermissions("xslmes:mes_xsl_raw_material_warehouse_board:list") + @GetMapping(value = "/board") + public Result board( + @RequestParam(name = "warehouseId", required = false) String warehouseId, + @RequestParam(name = "keyword", required = false) String keyword, + @RequestParam(name = "measureType", required = false) String measureType) { + MesXslRawMaterialWarehouseBoardVO data = mesXslRawMaterialWarehouseBoardService.queryBoard(warehouseId, keyword, measureType); + return Result.OK(data); + } +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWarehouseAreaController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWarehouseAreaController.java index 8f754483..933337fc 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWarehouseAreaController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslWarehouseAreaController.java @@ -3,6 +3,7 @@ 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 org.apache.shiro.SecurityUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -13,15 +14,22 @@ import org.jeecg.common.api.vo.Result; import org.jeecg.common.aspect.annotation.AutoLog; import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.query.QueryGenerator; +import org.jeecg.common.system.vo.LoginUser; import org.jeecg.common.util.oConvertUtils; +import org.jeecg.config.JeecgBaseConfig; import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService; import org.jeecg.modules.xslmes.service.MesXslStompNotifyService; +import org.jeecgframework.poi.excel.def.NormalExcelConstants; +import org.jeecgframework.poi.excel.entity.ExportParams; +import org.jeecgframework.poi.excel.entity.enmus.ExcelType; +import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -36,6 +44,9 @@ public class MesXslWarehouseAreaController extends JeecgController queryWrapper = QueryGenerator.initQueryWrapper(mesXslWarehouseArea, req.getParameterMap()); Page page = new Page<>(pageNo, pageSize); IPage pageList = mesXslWarehouseAreaService.page(page, queryWrapper); + mesXslWarehouseAreaService.enrichDisplayedActualCapacity(pageList.getRecords()); return Result.OK(pageList); } @@ -166,13 +178,33 @@ public class MesXslWarehouseAreaController extends JeecgController queryWrapper = QueryGenerator.initQueryWrapper(mesXslWarehouseArea, request.getParameterMap()); + LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); + String selections = request.getParameter("selections"); + if (oConvertUtils.isNotEmpty(selections)) { + queryWrapper.in("id", Arrays.asList(selections.split(","))); + } + List exportList = mesXslWarehouseAreaService.list(queryWrapper); + mesXslWarehouseAreaService.enrichDisplayedActualCapacity(exportList); + ModelAndView mv = new ModelAndView(new JeecgEntityExcelView()); + mv.addObject(NormalExcelConstants.FILE_NAME, "MES库区管理"); + mv.addObject(NormalExcelConstants.CLASS, MesXslWarehouseArea.class); + ExportParams exportParams = new ExportParams("MES库区管理报表", "导出人:" + sysUser.getRealname(), "MES库区管理", ExcelType.XSSF); + exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload()); + mv.addObject(NormalExcelConstants.PARAMS, exportParams); + mv.addObject(NormalExcelConstants.DATA_LIST, exportList); + String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS); + if (oConvertUtils.isNotEmpty(exportFields)) { + mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields); + } + return mv; } @RequiresPermissions("xslmes:mes_xsl_warehouse_area:importExcel") diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dto/MesXslAreaRemainQtySumDTO.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dto/MesXslAreaRemainQtySumDTO.java new file mode 100644 index 00000000..98bb31c8 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dto/MesXslAreaRemainQtySumDTO.java @@ -0,0 +1,20 @@ +package org.jeecg.modules.xslmes.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 按库区(trim 后编码)聚合剩余数量汇总 + */ +@Data +public class MesXslAreaRemainQtySumDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 库区编码(已 trim) */ + private String areaCode; + + /** 剩余数量合计 */ + private Long qtySum; +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslRawMaterialCardMapper.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslRawMaterialCardMapper.java index 7110eebd..74142e5e 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslRawMaterialCardMapper.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/MesXslRawMaterialCardMapper.java @@ -1,7 +1,12 @@ package org.jeecg.modules.xslmes.mapper; -import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.jeecg.modules.xslmes.dto.MesXslAreaRemainQtySumDTO; +import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard; + +import java.util.Collection; +import java.util.List; /** * @Description: 原材料卡片 @@ -10,4 +15,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; * @Version: V1.0 */ public interface MesXslRawMaterialCardMapper extends BaseMapper { + + /** + * 按 trim 后的库区编码汇总原材料卡片剩余数量(当前租户由拦截器补齐) + */ + List sumRemainingQtyByTrimmedWarehouseArea(@Param("areaCodes") Collection areaCodes); } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialCardMapper.xml b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialCardMapper.xml index f240dd25..d99a26b1 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialCardMapper.xml +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mapper/xml/MesXslRawMaterialCardMapper.xml @@ -1,4 +1,18 @@ + + diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialWarehouseBoardService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialWarehouseBoardService.java new file mode 100644 index 00000000..5c5f8698 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialWarehouseBoardService.java @@ -0,0 +1,18 @@ +package org.jeecg.modules.xslmes.service; + +import org.jeecg.modules.xslmes.vo.MesXslRawMaterialWarehouseBoardVO; + +/** + * 原材料库区看板(库区 + 原材料卡片聚合) + */ +public interface IMesXslRawMaterialWarehouseBoardService { + + /** + * 聚合看板数据 + * + * @param warehouseId 所属仓库ID,可空表示全部启用库区 + * @param keyword 物料名称/条码/批次模糊筛选(可空);非空时仅展示仍有匹配卡片的库区 + * @param measureType quantity=占用率按剩余数量与 maxCapacity 对齐;weight=按剩余重量 + */ + MesXslRawMaterialWarehouseBoardVO queryBoard(String warehouseId, String keyword, String measureType); +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslWarehouseAreaService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslWarehouseAreaService.java index 1046865e..273fa17f 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslWarehouseAreaService.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslWarehouseAreaService.java @@ -3,6 +3,8 @@ package org.jeecg.modules.xslmes.service; import com.baomidou.mybatisplus.extension.service.IService; import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; +import java.util.Collection; + /** * MES 库区管理 */ @@ -12,4 +14,9 @@ public interface IMesXslWarehouseAreaService extends IService areas); } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialWarehouseBoardServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialWarehouseBoardServiceImpl.java new file mode 100644 index 00000000..e6155fc1 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialWarehouseBoardServiceImpl.java @@ -0,0 +1,248 @@ +package org.jeecg.modules.xslmes.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard; +import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; +import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService; +import org.jeecg.modules.xslmes.service.IMesXslRawMaterialWarehouseBoardService; +import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService; +import org.jeecg.modules.xslmes.vo.MesXslRawMaterialWarehouseBoardVO; +import org.jeecg.modules.xslmes.vo.MesXslRawMaterialWarehouseBoardVO.BoardAreaCardVO; +import org.jeecg.modules.xslmes.vo.MesXslRawMaterialWarehouseBoardVO.BoardBandVO; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 原材料库区看板 + */ +@Service +@RequiredArgsConstructor +public class MesXslRawMaterialWarehouseBoardServiceImpl implements IMesXslRawMaterialWarehouseBoardService { + + private static final String MEASURE_WEIGHT = "weight"; + /** 库区编码首段形如 1F、2P、03A 等,仅作看板分组带标题(与仓库分类层级无「楼层」对应关系) */ + private static final Pattern BAND_FIRST_SEGMENT = Pattern.compile("^\\d+[A-Za-z]+$"); + + private final IMesXslWarehouseAreaService mesXslWarehouseAreaService; + private final IMesXslRawMaterialCardService mesXslRawMaterialCardService; + + @Override + public MesXslRawMaterialWarehouseBoardVO queryBoard(String warehouseId, String keyword, String measureType) { + String mt = StringUtils.defaultIfBlank(measureType, "quantity").toLowerCase(Locale.ROOT).trim(); + if (!MEASURE_WEIGHT.equals(mt)) { + mt = "quantity"; + } + MesXslRawMaterialWarehouseBoardVO vo = new MesXslRawMaterialWarehouseBoardVO(); + vo.setMeasureType(mt); + + QueryWrapper areaQw = new QueryWrapper<>(); + areaQw.eq("status", "0"); + if (StringUtils.isNotBlank(warehouseId)) { + areaQw.eq("warehouse_id", warehouseId.trim()); + } + areaQw.orderByAsc("area_code"); + List areas = mesXslWarehouseAreaService.list(areaQw); + if (areas.isEmpty()) { + return vo; + } + mesXslWarehouseAreaService.enrichDisplayedActualCapacity(areas); + + List areaCodes = areas.stream().map(MesXslWarehouseArea::getAreaCode).filter(StringUtils::isNotBlank).map(String::trim).distinct().collect(Collectors.toList()); + + QueryWrapper cardQw = new QueryWrapper<>(); + cardQw.in("warehouse_area", areaCodes); + cardQw.apply("(COALESCE(remaining_quantity,0) > 0 OR COALESCE(remaining_weight,0) > 0)"); + String kw = StringUtils.trimToNull(keyword); + if (kw != null) { + cardQw.and(w -> + w.like("material_name", kw).or().like("barcode", kw).or().like("batch_no", kw)); + } + List cards = mesXslRawMaterialCardService.list(cardQw); + + Map> byArea = + cards.stream().filter(c -> StringUtils.isNotBlank(c.getWarehouseArea())).collect(Collectors.groupingBy(c -> c.getWarehouseArea().trim(), Collectors.toList())); + + boolean filterByKeyword = kw != null; + Map bandMap = new LinkedHashMap<>(); + + for (MesXslWarehouseArea area : areas) { + String code = StringUtils.trimToEmpty(area.getAreaCode()); + if (code.isEmpty()) { + continue; + } + List list = byArea.getOrDefault(code, new ArrayList<>()); + if (filterByKeyword && list.isEmpty()) { + continue; + } + BoardAreaCardVO cardVo = aggregateArea(area, list, mt); + BandKey bk = resolveBand(area.getAreaCode()); + BoardBandVO band = bandMap.computeIfAbsent(bk.key, k -> { + BoardBandVO b = new BoardBandVO(); + b.setBandKey(bk.key); + b.setBandLabel(bk.label); + b.setBandSort(bk.sort); + return b; + }); + band.getAreas().add(cardVo); + } + + List bands = + bandMap.values().stream().sorted(Comparator.comparing(BoardBandVO::getBandSort, Comparator.nullsLast(Integer::compareTo)).thenComparing(BoardBandVO::getBandKey, Comparator.nullsLast(String::compareTo))).collect(Collectors.toList()); + + for (BoardBandVO band : bands) { + band.getAreas().sort(Comparator.comparing(BoardAreaCardVO::getAreaCode, Comparator.nullsLast(String::compareTo))); + } + vo.setBands(bands); + return vo; + } + + private BoardAreaCardVO aggregateArea(MesXslWarehouseArea area, List list, String measureType) { + BoardAreaCardVO vo = new BoardAreaCardVO(); + vo.setAreaId(area.getId()); + vo.setAreaCode(area.getAreaCode()); + vo.setAreaName(area.getAreaName()); + vo.setWarehouseId(area.getWarehouseId()); + vo.setWarehouseName(area.getWarehouseName()); + vo.setMaxCapacity(area.getMaxCapacity()); + vo.setActualCapacity(area.getActualCapacity()); + vo.setCardCount(list.size()); + + int sumQty = 0; + BigDecimal sumW = BigDecimal.ZERO; + LinkedHashSet materialNames = new LinkedHashSet<>(); + for (MesXslRawMaterialCard c : list) { + if (c.getRemainingQuantity() != null) { + sumQty += c.getRemainingQuantity(); + } + if (c.getRemainingWeight() != null) { + sumW = sumW.add(c.getRemainingWeight()); + } + if (StringUtils.isNotBlank(c.getMaterialName())) { + materialNames.add(c.getMaterialName().trim()); + } + } + vo.setCurrentQuantity(sumQty); + vo.setCurrentWeight(sumW.setScale(3, RoundingMode.HALF_UP)); + vo.setMaterialKindCount(materialNames.size()); + vo.setTopMaterialNames(materialNames.stream().limit(8).collect(Collectors.toList())); + + Double usage = null; + Integer maxCap = area.getMaxCapacity(); + if (maxCap != null && maxCap > 0) { + if (MEASURE_WEIGHT.equals(measureType)) { + usage = sumW.multiply(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(maxCap), 1, RoundingMode.HALF_UP).doubleValue(); + } else { + usage = BigDecimal.valueOf(sumQty).multiply(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(maxCap), 1, RoundingMode.HALF_UP).doubleValue(); + } + if (usage > 100) { + usage = 100d; + } + if (usage < 0) { + usage = 0d; + } + } + vo.setUsagePercent(usage); + vo.setAlertLevel(resolveAlert(usage, list.isEmpty())); + return vo; + } + + private static String resolveAlert(Double usagePercent, boolean emptyCards) { + if (emptyCards) { + return "empty"; + } + if (usagePercent == null) { + return "unknown"; + } + if (usagePercent <= 0) { + return "empty"; + } + if (usagePercent < 30) { + return "low"; + } + if (usagePercent < 90) { + return "normal"; + } + if (usagePercent < 100) { + return "high"; + } + return "full"; + } + + private BandKey resolveBand(String areaCode) { + if (StringUtils.isBlank(areaCode)) { + return new BandKey("zzz_other", "其它", 999_000); + } + String trimmed = areaCode.trim(); + String head = trimmed.contains("-") ? trimmed.substring(0, trimmed.indexOf('-')) : trimmed; + if (BAND_FIRST_SEGMENT.matcher(head).matches()) { + String normalized = normalizeBandToken(head); + int sort = bandSort(normalized); + return new BandKey(normalized.toLowerCase(Locale.ROOT), normalized, sort); + } + String label = StringUtils.left(head, 16); + if (label.isEmpty()) { + label = trimmed.length() > 16 ? trimmed.substring(0, 16) : trimmed; + } + return new BandKey("z_" + label.toLowerCase(Locale.ROOT), label, 888_000 + Math.abs(label.hashCode() % 1000)); + } + + private static String normalizeBandToken(String raw) { + if (raw == null) { + return "其它"; + } + String s = raw.toUpperCase(Locale.ROOT); + s = s.replace('F', 'F').replace('P', 'P'); + return s; + } + + /** + * 简单排序:按前缀中的数字升序(1F<2F<10F),末尾字母次之 + */ + private static int bandSort(String label) { + String digits = ""; + String tail = ""; + for (int i = 0; i < label.length(); i++) { + char ch = label.charAt(i); + if (Character.isDigit(ch)) { + digits += ch; + } else { + tail = label.substring(i); + break; + } + } + int num = 0; + try { + if (!digits.isEmpty()) { + num = Integer.parseInt(digits); + } + } catch (NumberFormatException ignored) { + num = 0; + } + return num * 100 + Math.min(Math.abs(tail.hashCode() % 99), 99); + } + + private static final class BandKey { + final String key; + final String label; + final int sort; + + BandKey(String key, String label, int sort) { + this.key = key; + this.label = label; + this.sort = sort; + } + } +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseAreaServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseAreaServiceImpl.java index d4a29085..b2db810a 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseAreaServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseAreaServiceImpl.java @@ -1,18 +1,25 @@ package org.jeecg.modules.xslmes.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.jeecg.modules.xslmes.capacity.WarehouseAreaDisplayedActualCapacityMediator; import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea; import org.jeecg.modules.xslmes.mapper.MesXslWarehouseAreaMapper; import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService; import org.springframework.stereotype.Service; +import java.util.Collection; + /** * MES 库区管理 */ @Service +@RequiredArgsConstructor public class MesXslWarehouseAreaServiceImpl extends ServiceImpl implements IMesXslWarehouseAreaService { + private final WarehouseAreaDisplayedActualCapacityMediator displayedActualCapacityMediator; + @Override public boolean existsSameAreaCode(String areaCode, String excludeId) { if (StringUtils.isBlank(areaCode)) { @@ -24,4 +31,9 @@ public class MesXslWarehouseAreaServiceImpl extends ServiceImpl 0L; } + + @Override + public void enrichDisplayedActualCapacity(Collection areas) { + displayedActualCapacityMediator.apply(areas); + } } diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseServiceImpl.java index 3333a358..f05ab9ca 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseServiceImpl.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslWarehouseServiceImpl.java @@ -41,11 +41,11 @@ public class MesXslWarehouseServiceImpl extends ServiceImpl bands = new ArrayList<>(); + + @Data + @Schema(description = "看板分组带(如 1F、2F)") + public static class BoardBandVO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "分组键(排序用)") + private String bandKey; + + @Schema(description = "分组标题") + private String bandLabel; + + @Schema(description = "排序号,越小越靠前") + private Integer bandSort; + + @Schema(description = "该组下的库区卡片") + private List areas = new ArrayList<>(); + } + + @Data + @Schema(description = "单个库区卡片数据") + public static class BoardAreaCardVO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "库区主键") + private String areaId; + + @Schema(description = "库区编码") + private String areaCode; + + @Schema(description = "库区名称") + private String areaName; + + @Schema(description = "所属仓库ID") + private String warehouseId; + + @Schema(description = "所属仓库名称") + private String warehouseName; + + @Schema(description = "最大存放量(库区主数据)") + private Integer maxCapacity; + + @Schema(description = "实际存放量(库区主数据快照)") + private Integer actualCapacity; + + @Schema(description = "在库卡片张数(有剩余量)") + private Integer cardCount; + + @Schema(description = "物料种类数(去重物料名称)") + private Integer materialKindCount; + + @Schema(description = "剩余数量合计") + private Integer currentQuantity; + + @Schema(description = "剩余重量合计") + private BigDecimal currentWeight; + + @Schema(description = "物料名称摘要(前若干种)") + private List topMaterialNames = new ArrayList<>(); + + @Schema(description = "占用率 0~100,无上限或上限为0时为 null") + private Double usagePercent; + + @Schema(description = "告警级别:empty/low/normal/high/full/unknown") + private String alertLevel; + } +} diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application.yml b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application.yml index 92b9835f..ffd80306 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application.yml +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application.yml @@ -2,6 +2,8 @@ spring: application: name: jeecg-system config: - import: optional:classpath:config/application-liteflow.yml + import: + - optional:classpath:config/application-liteflow.yml + - optional:classpath:config/application-xslmes-warehouse-area.yml profiles: active: '@profile.name@' \ No newline at end of file diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/config/application-xslmes-warehouse-area.yml b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/config/application-xslmes-warehouse-area.yml new file mode 100644 index 00000000..cfc96564 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/config/application-xslmes-warehouse-area.yml @@ -0,0 +1,11 @@ +# MES XSL — 库区「实际存放量」展示回填(不写库,可覆盖) +xslmes: + warehouse-area: + display-actual-capacity: + enabled: true + # 按「原材料库」分类编码解析 sys_category.id(推荐;业务树已无楼层语义,编码中 F1/F2 后缀若变化请改此处) + raw-material-warehouse-category-codes: + - XSLMES_WH_F1_YCL + - XSLMES_WH_F2_YCL + # 可选:直接写 warehouse_category=id,与上面的编码解析结果合并 + raw-material-warehouse-category-ids: [] diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_56__mes_xsl_raw_material_warehouse_board.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_56__mes_xsl_raw_material_warehouse_board.sql new file mode 100644 index 00000000..8f226b96 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_56__mes_xsl_raw_material_warehouse_board.sql @@ -0,0 +1,19 @@ +-- 原材料库区看板:菜单与查询权限(父菜单 MES XSL 1900000000000000300) + +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 '1900000000000000580', '1900000000000000300', '原材料库区看板', '/xslmes/mesXslRawMaterialWarehouseBoard', 'xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard', 1, NULL, NULL, 1, NULL, '0', 12.50, 0, 'ant-design:layout-outlined', 0, 1, 0, 0, '按库区聚合展示原材料卡片库存', 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000580'); + +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 '1900000000000000581', '1900000000000000580', '查询', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_raw_material_warehouse_board:list', '1', 1.00, 0, NULL, 1, 0, 0, 0, NULL, 'admin', NOW(), 'admin', NOW(), 0, 0, '1', 0 +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_permission` WHERE `id` = '1900000000000000581'); + +INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`) +SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000580', NULL, NOW(), '127.0.0.1' +FROM `sys_role` r WHERE r.`role_code` = 'admin' + AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000580'); + +INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`) +SELECT REPLACE(UUID(), '-', ''), r.id, '1900000000000000581', NULL, NOW(), '127.0.0.1' +FROM `sys_role` r WHERE r.`role_code` = 'admin' + AND NOT EXISTS (SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.id AND rp.`permission_id` = '1900000000000000581'); diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.api.ts b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.api.ts new file mode 100644 index 00000000..efa8a6a6 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.api.ts @@ -0,0 +1,8 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + board = '/xslmes/mesXslRawMaterialWarehouseBoard/board', +} + +export const fetchBoard = (params: { warehouseId?: string; keyword?: string; measureType?: string }) => + defHttp.get({ url: Api.board, params }); diff --git a/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.vue b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.vue new file mode 100644 index 00000000..0af75358 --- /dev/null +++ b/jeecgboot-vue3/src/views/xslmes/mesXslRawMaterialWarehouseBoard/MesXslRawMaterialWarehouseBoard.vue @@ -0,0 +1,725 @@ + + + + +