Merge branch 'main' into 生产基础资料
This commit is contained in:
@@ -18,5 +18,11 @@
|
||||
<groupId>org.jeecgframework.boot3</groupId>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
</dependency>
|
||||
<!-- 复用打印模板模块:打印机枚举、业务绑定、PDF 提交队列 -->
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot3</groupId>
|
||||
<artifactId>jeecg-system-biz</artifactId>
|
||||
<version>${jeecgboot.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package org.jeecg.modules.xslmes.bootstrap;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.print.entity.PrintBizPermEntity;
|
||||
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
|
||||
import org.jeecg.modules.print.util.PrintBizDetailPropertyScanner;
|
||||
import org.jeecg.modules.print.util.PrintBizEntityFieldIntrospector;
|
||||
import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
|
||||
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
|
||||
import org.jeecg.modules.system.entity.SysPermission;
|
||||
import org.jeecg.modules.system.service.ISysPermissionService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 启动后异步:遍历 {@code print_biz_perm_entity} 中已配置实体类的业务,反射主表与明细槽位字段并写入 {@code mes_xsl_biz_entity_field_*},
|
||||
* 供「业务打印绑定」弹窗读取(避免运行时频繁反射)。
|
||||
*
|
||||
* <p>关闭:{@code jeecg.print.biz-entity-field-catalog-sync=false}
|
||||
*
|
||||
* <p>建议在 {@link org.jeecg.modules.print.bootstrap.PrintBizPermEntityWarmupRunner}(Order 2000)之后执行。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(2100)
|
||||
public class BizEntityFieldCatalogSyncRunner implements ApplicationRunner {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Resource
|
||||
private IPrintBizPermEntityService printBizPermEntityService;
|
||||
|
||||
@Resource
|
||||
private IMesXslBizEntityFieldProfileService bizEntityFieldProfileService;
|
||||
|
||||
@Resource
|
||||
private ISysPermissionService sysPermissionService;
|
||||
|
||||
@Value("${jeecg.print.biz-entity-field-catalog-sync:true}")
|
||||
private boolean syncEnabled;
|
||||
|
||||
/** 延迟执行秒数,便于晚于「打印菜单实体映射预热」写完 print_biz_perm_entity */
|
||||
@Value("${jeecg.print.biz-entity-field-catalog-sync-delay-seconds:10}")
|
||||
private int catalogSyncDelaySeconds;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
if (!syncEnabled) {
|
||||
log.info("业务实体字段缓存同步已关闭(jeecg.print.biz-entity-field-catalog-sync=false)");
|
||||
return;
|
||||
}
|
||||
log.info(
|
||||
"业务实体字段缓存:将在 {} 秒后异步同步(来源 print_biz_perm_entity → mes_xsl_biz_entity_field_*)",
|
||||
Math.max(0, catalogSyncDelaySeconds));
|
||||
CompletableFuture.runAsync(
|
||||
this::doSync,
|
||||
CompletableFuture.delayedExecutor(
|
||||
Math.max(0, catalogSyncDelaySeconds),
|
||||
TimeUnit.SECONDS,
|
||||
ForkJoinPool.commonPool()));
|
||||
}
|
||||
|
||||
private void doSync() {
|
||||
try {
|
||||
log.info("业务实体字段缓存:开始异步同步(来源 print_biz_perm_entity)");
|
||||
List<PrintBizPermEntity> rows = printBizPermEntityService.list();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
log.info("业务实体字段缓存:print_biz_perm_entity 无数据,跳过");
|
||||
return;
|
||||
}
|
||||
int ok = 0;
|
||||
int skip = 0;
|
||||
for (PrintBizPermEntity row : rows) {
|
||||
if (row == null || StringUtils.isBlank(row.getPermId())) {
|
||||
skip++;
|
||||
continue;
|
||||
}
|
||||
String permId = row.getPermId().trim();
|
||||
String entityFqn = StringUtils.trimToNull(row.getEntityClass());
|
||||
if (entityFqn == null) {
|
||||
skip++;
|
||||
continue;
|
||||
}
|
||||
Class<?> clazz = PrintBizEntityFieldIntrospector.tryLoadClass(entityFqn);
|
||||
if (clazz == null) {
|
||||
skip++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
syncOne(permId, clazz, entityFqn);
|
||||
ok++;
|
||||
} catch (Exception ex) {
|
||||
log.warn("业务实体字段缓存同步失败 permId={} entity={}", permId, entityFqn, ex);
|
||||
skip++;
|
||||
}
|
||||
}
|
||||
log.info("业务实体字段缓存同步完成:成功 {} 条,跳过 {} 条", ok, skip);
|
||||
} catch (Exception e) {
|
||||
log.warn("业务实体字段缓存同步异常(不影响系统启动)", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void syncOne(String permId, Class<?> clazz, String entityFqn) throws Exception {
|
||||
List<PrintBizFieldItemVO> mainFields = PrintBizEntityFieldIntrospector.listFields(clazz);
|
||||
String mainJson = OBJECT_MAPPER.writeValueAsString(mainFields);
|
||||
|
||||
List<PrintBizDetailSlotVO> slots = PrintBizDetailPropertyScanner.listSlots(clazz);
|
||||
List<MesXslBizEntityFieldDetail> detailRows = new ArrayList<>();
|
||||
int sort = 0;
|
||||
Date now = new Date();
|
||||
for (PrintBizDetailSlotVO slot : slots) {
|
||||
Class<?> itemClazz =
|
||||
PrintBizDetailPropertyScanner.resolveItemClassForSlot(
|
||||
clazz, slot.getPropertyName(), slot.getSlotKind());
|
||||
if (itemClazz == null) {
|
||||
continue;
|
||||
}
|
||||
List<PrintBizFieldItemVO> itemFields = PrintBizEntityFieldIntrospector.listFields(itemClazz);
|
||||
MesXslBizEntityFieldDetail d = new MesXslBizEntityFieldDetail();
|
||||
d.setDetailPropertyName(slot.getPropertyName());
|
||||
d.setDetailSlotKind(slot.getSlotKind());
|
||||
d.setDetailName(slot.getLabel());
|
||||
d.setDetailEntityClassName(fqn(itemClazz));
|
||||
d.setDetailFieldsJson(OBJECT_MAPPER.writeValueAsString(itemFields));
|
||||
d.setSortNo(sort++);
|
||||
d.setCreateTime(now);
|
||||
d.setUpdateTime(now);
|
||||
detailRows.add(d);
|
||||
}
|
||||
|
||||
String bizName = resolveMenuName(permId);
|
||||
bizEntityFieldProfileService.upsertScannedProfile(permId, bizName, entityFqn, mainJson, detailRows);
|
||||
}
|
||||
|
||||
private static String fqn(Class<?> c) {
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
String cn = c.getCanonicalName();
|
||||
return cn != null ? cn : c.getName();
|
||||
}
|
||||
|
||||
private String resolveMenuName(String permId) {
|
||||
SysPermission p = sysPermissionService.getById(permId);
|
||||
if (p != null && StringUtils.isNotBlank(p.getName())) {
|
||||
return p.getName().trim();
|
||||
}
|
||||
return permId;
|
||||
}
|
||||
}
|
||||
@@ -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<MesXslWarehouseArea> areas);
|
||||
}
|
||||
@@ -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<WarehouseAreaActualCapacityContribution> contributions;
|
||||
|
||||
public void apply(Collection<MesXslWarehouseArea> areas) {
|
||||
if (areas == null || areas.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<MesXslWarehouseArea> list = new ArrayList<>(areas);
|
||||
List<WarehouseAreaActualCapacityContribution> ordered = contributions.stream().sorted(AnnotationAwareOrderComparator.INSTANCE).toList();
|
||||
for (WarehouseAreaActualCapacityContribution c : ordered) {
|
||||
c.contribute(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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.dto.MesXslAreaRemainQtySumDTO;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaCapacityCfgService;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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(不写库)。
|
||||
* 匹配哪些仓库分类由 {@link IMesXslWarehouseAreaCapacityCfgService}(页面配置 / YAML 兜底)决定。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(100)
|
||||
@RequiredArgsConstructor
|
||||
public class RawMaterialCardRemainQtyCapacityContribution implements WarehouseAreaActualCapacityContribution {
|
||||
|
||||
private final IMesXslWarehouseAreaCapacityCfgService capacityCfgService;
|
||||
private final MesXslRawMaterialCardMapper mesXslRawMaterialCardMapper;
|
||||
|
||||
@Override
|
||||
public void contribute(List<MesXslWarehouseArea> areas) {
|
||||
if (areas == null || areas.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!capacityCfgService.isActualCapacityBackfillEnabled()) {
|
||||
return;
|
||||
}
|
||||
Set<String> allowedCategories = capacityCfgService.resolveRawMaterialWarehouseCategoryIds();
|
||||
if (allowedCategories.isEmpty()) {
|
||||
log.debug("[WarehouseAreaActualCapacity] 原材料库分类未配置或解析为空,跳过 RawMaterialCard 策略");
|
||||
return;
|
||||
}
|
||||
|
||||
List<MesXslWarehouseArea> targets =
|
||||
areas.stream().filter(a -> a != null && allowedCategories.contains(normId(a.getWarehouseCategory())) && StringUtils.isNotBlank(a.getAreaCode()))
|
||||
.collect(Collectors.toList());
|
||||
if (targets.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<String> codes =
|
||||
targets.stream().map(MesXslWarehouseArea::getAreaCode).map(String::trim).filter(StringUtils::isNotBlank).distinct().collect(Collectors.toList());
|
||||
|
||||
Map<String, Long> 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<String, Long> loadSumMap(Collection<String> codes) {
|
||||
if (codes.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<MesXslAreaRemainQtySumDTO> rows = mesXslRawMaterialCardMapper.sumRemainingQtyByTrimmedWarehouseArea(codes);
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, Long> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 库区列表「实际存放量」展示兜底配置(不写库)
|
||||
* <p>
|
||||
* 当某租户在 {@code mes_xsl_warehouse_area_capacity_cfg} 中<strong>尚无已保存记录</strong>时,
|
||||
* 启用状态与分类 codes/ids 仍从本配置读取;一旦有库内记录,则仅以页面「匹配仓库」配置为准。
|
||||
* <p>
|
||||
* 推荐使用 codes 配置,由运行时解析为 sys_category.id;亦可直接配置 ids。
|
||||
*/
|
||||
@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<String> rawMaterialWarehouseCategoryCodes = defaultRawCodes();
|
||||
|
||||
/**
|
||||
* 直接指定 warehouse_category=sys_category.id(与 codes 解析结果<strong>合并</strong>)。
|
||||
* 可为空。
|
||||
*/
|
||||
private List<String> rawMaterialWarehouseCategoryIds = new ArrayList<>();
|
||||
|
||||
private static List<String> defaultRawCodes() {
|
||||
return new ArrayList<>(MesXslWarehouseCategory.RAW_MATERIAL_CATEGORY_CODES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.constant;
|
||||
|
||||
/**
|
||||
* 打印业务绑定 biz_code 使用菜单 permission id;与 print_biz_perm_entity、Flyway 中原材料卡片菜单一致。
|
||||
*/
|
||||
public final class MesXslPrintConstants {
|
||||
|
||||
/** 原材料卡片页面菜单(sys_permission.id) */
|
||||
public static final String RAW_MATERIAL_CARD_PERM_ID = "1900000000000000540";
|
||||
|
||||
/** 原料入场记录页面菜单(sys_permission.id,与 Flyway 中 parent 菜单一致) */
|
||||
public static final String RAW_MATERIAL_ENTRY_PERM_ID = "1900000000000000530";
|
||||
|
||||
private MesXslPrintConstants() {}
|
||||
}
|
||||
@@ -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})
|
||||
*
|
||||
* <p>说明:历史版本曾在编码上使用 F1/F2 后缀;当前业务树已不再按楼层区分,
|
||||
* 「F1」「F2」仅可能作为编码后缀保留,请以 {@link #RAW_MATERIAL_CATEGORY_CODES}、
|
||||
* 客户/供应商库编码集合为准做兼容。</p>
|
||||
*/
|
||||
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<String> RAW_MATERIAL_CATEGORY_CODES =
|
||||
Collections.unmodifiableList(Arrays.asList("XSLMES_WH_F1_YCL", "XSLMES_WH_F2_YCL"));
|
||||
|
||||
/** 兼容历史编号的「客户库」分类编码集合 */
|
||||
private static final Set<String> CUSTOMER_CATEGORY_CODES =
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("XSLMES_WH_F1_KH", "XSLMES_WH_F2_KH")));
|
||||
|
||||
/** 兼容历史编号的「供应商库」分类编码集合 */
|
||||
private static final Set<String> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
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 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.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 业务实体字段配置:主表存业务名称与主实体字段 JSON;子表存各明细表的字段 JSON。
|
||||
*/
|
||||
@Tag(name = "业务实体字段配置")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslBizEntityFieldProfile")
|
||||
@Slf4j
|
||||
public class MesXslBizEntityFieldProfileController extends JeecgController<MesXslBizEntityFieldProfile, IMesXslBizEntityFieldProfileService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslBizEntityFieldProfileService bizEntityFieldProfileService;
|
||||
|
||||
@Operation(summary = "分页列表(不含明细,减轻负载)")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslBizEntityFieldProfile>> queryPageList(
|
||||
MesXslBizEntityFieldProfile query,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslBizEntityFieldProfile> qw = QueryGenerator.initQueryWrapper(query, req.getParameterMap());
|
||||
Page<MesXslBizEntityFieldProfile> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslBizEntityFieldProfile> pageList = bizEntityFieldProfileService.page(page, qw);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@AutoLog(value = "业务实体字段配置-添加")
|
||||
@Operation(summary = "添加(请求体可含 detailList)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:add")
|
||||
@PostMapping(value = "/add")
|
||||
public Result<String> add(@RequestBody MesXslBizEntityFieldProfile entity) {
|
||||
if (oConvertUtils.isEmpty(entity.getBusinessName())) {
|
||||
return Result.error("业务名称不能为空");
|
||||
}
|
||||
if (oConvertUtils.isEmpty(entity.getBusinessCode())) {
|
||||
return Result.error("业务编码不能为空(建议填写菜单 permission id)");
|
||||
}
|
||||
bizEntityFieldProfileService.saveWithDetails(entity);
|
||||
return Result.OK("添加成功");
|
||||
}
|
||||
|
||||
@AutoLog(value = "业务实体字段配置-编辑")
|
||||
@Operation(summary = "编辑(明细全量替换)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslBizEntityFieldProfile entity) {
|
||||
if (oConvertUtils.isEmpty(entity.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
if (oConvertUtils.isEmpty(entity.getBusinessName())) {
|
||||
return Result.error("业务名称不能为空");
|
||||
}
|
||||
if (oConvertUtils.isEmpty(entity.getBusinessCode())) {
|
||||
return Result.error("业务编码不能为空(建议填写菜单 permission id)");
|
||||
}
|
||||
bizEntityFieldProfileService.updateWithDetails(entity);
|
||||
return Result.OK("编辑成功");
|
||||
}
|
||||
|
||||
@AutoLog(value = "业务实体字段配置-删除")
|
||||
@Operation(summary = "删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
bizEntityFieldProfileService.removeWithDetails(id);
|
||||
return Result.OK("删除成功");
|
||||
}
|
||||
|
||||
@AutoLog(value = "业务实体字段配置-批量删除")
|
||||
@Operation(summary = "批量删除")
|
||||
@RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:deleteBatch")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
bizEntityFieldProfileService.removeBatchWithDetails(Arrays.asList(ids.split(",")));
|
||||
return Result.OK("批量删除成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "按 id 查询(含 detailList)")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslBizEntityFieldProfile> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslBizEntityFieldProfile entity = bizEntityFieldProfileService.getByIdWithDetails(id);
|
||||
if (entity == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(entity);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslBizEntityFieldProfile query) {
|
||||
return super.exportXls(request, query, MesXslBizEntityFieldProfile.class, "业务实体字段配置");
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_biz_entity_field_profile:importExcel")
|
||||
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, MesXslBizEntityFieldProfile.class);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,20 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.print.entity.PrintBizTemplateBind;
|
||||
import org.jeecg.modules.print.entity.PrintTemplate;
|
||||
import org.jeecg.modules.print.service.IPrintBizTemplateBindService;
|
||||
import org.jeecg.modules.print.service.IPrintTemplateService;
|
||||
import org.jeecg.modules.print.util.PrintBizDataMappingUtil;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslPrintConstants;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
@@ -30,10 +40,16 @@ import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -59,6 +75,9 @@ public class MesXslDesktopAnonController {
|
||||
private final IMesXslWarehouseService warehouseService;
|
||||
private final IMesXslWarehouseAreaService warehouseAreaService;
|
||||
private final MesXslStompNotifyService stompNotify;
|
||||
private final IPrintBizTemplateBindService printBizTemplateBindService;
|
||||
private final IPrintTemplateService printTemplateService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
// ═══════════════════════════ 车辆管理 ═══════════════════════════
|
||||
|
||||
@@ -495,19 +514,48 @@ public class MesXslDesktopAnonController {
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密查询关联原材料卡片")
|
||||
@GetMapping("/xslmes/mesXslRawMaterialEntry/anon/linkedRawMaterialCards")
|
||||
public Result<List<MesXslRawMaterialCardBriefVO>> rawMaterialEntryAnonLinkedRawMaterialCards(
|
||||
@RequestParam(name = "ids") String ids) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
return Result.OK(rawMaterialEntryService.listLinkedRawMaterialCards(idList));
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密删除")
|
||||
@DeleteMapping("/xslmes/mesXslRawMaterialEntry/anon/delete")
|
||||
public Result<String> rawMaterialEntryAnonDelete(@RequestParam(name = "id") String id) {
|
||||
rawMaterialEntryService.removeById(id);
|
||||
stompNotify.publishRawMaterialEntryChanged("delete", id);
|
||||
public Result<?> rawMaterialEntryAnonDelete(
|
||||
@RequestParam(name = "id") String id,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "true") boolean cascadeDeleteCards) {
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
rawMaterialEntryService.removeEntriesRespectingLinkedCards(List.of(id), cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"存在已生成的原材料卡片但未允许级联删除,请传 cascadeDeleteCards=true。", blocked.get());
|
||||
}
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密批量删除")
|
||||
@DeleteMapping("/xslmes/mesXslRawMaterialEntry/anon/deleteBatch")
|
||||
public Result<String> rawMaterialEntryAnonDeleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
rawMaterialEntryService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishRawMaterialEntryChanged("batchDelete", ids);
|
||||
public Result<?> rawMaterialEntryAnonDeleteBatch(
|
||||
@RequestParam(name = "ids") String ids,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "true") boolean cascadeDeleteCards) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
rawMaterialEntryService.removeEntriesRespectingLinkedCards(idList, cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"存在已生成的原材料卡片但未允许级联删除,请传 cascadeDeleteCards=true。", blocked.get());
|
||||
}
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@@ -547,9 +595,11 @@ public class MesXslDesktopAnonController {
|
||||
if (oConvertUtils.isEmpty(entity.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
boolean ok = rawMaterialCardService.updateById(entity);
|
||||
boolean ok =
|
||||
rawMaterialCardService.updateEditableInventoryFields(
|
||||
entity, "桌面端免密编辑", "桌面端(免密)");
|
||||
if (!ok) {
|
||||
return Result.error("数据已被他人修改,请刷新后重试");
|
||||
return Result.error("未找到数据或更新失败,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishRawMaterialCardChanged("edit", entity.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
@@ -606,6 +656,61 @@ public class MesXslDesktopAnonController {
|
||||
return Result.OK((int) count);
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "原材料卡片-免密准备原生打印数据(桌面端用)")
|
||||
@GetMapping("/xslmes/mesXslRawMaterialCard/anon/prepareNativePrint")
|
||||
public Result<Map<String, Object>> rawMaterialCardAnonPrepareNativePrint(@RequestParam(name = "id") String id) {
|
||||
try {
|
||||
MesXslRawMaterialCard card = rawMaterialCardService.getById(id);
|
||||
if (card == null) return Result.error("未找到原材料卡片");
|
||||
PrintBizTemplateBind bind =
|
||||
printBizTemplateBindService.getByBizCode(MesXslPrintConstants.RAW_MATERIAL_CARD_PERM_ID);
|
||||
if (bind == null) return Result.error("请先在「业务打印绑定」中配置原材料卡片与打印模板");
|
||||
PrintTemplate tpl = printTemplateService.getById(bind.getTemplateId());
|
||||
if (tpl == null) return Result.error("绑定的打印模板不存在");
|
||||
ArrayNode mapping = PrintBizDataMappingUtil.parseMappingArray(bind.getFieldMappingJson());
|
||||
JsonNode bizRoot = objectMapper.valueToTree(card);
|
||||
ObjectNode printData = PrintBizDataMappingUtil.mapBizToPrintData(bizRoot, mapping);
|
||||
PrintBizDataMappingUtil.fillMissingDataBindingParamKeys(printData, tpl.getTemplateJson());
|
||||
Map<String, Object> out = new HashMap<>(8);
|
||||
out.put("cardId", card.getId());
|
||||
out.put("templateCode", bind.getTemplateCode());
|
||||
out.put("templateJson", tpl.getTemplateJson());
|
||||
out.put("printData", objectMapper.convertValue(printData, Map.class));
|
||||
return Result.OK(out);
|
||||
} catch (Exception e) {
|
||||
log.error("原材料卡片-免密准备打印数据失败 id={}", id, e);
|
||||
return Result.error("准备打印数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-免密准备原生打印数据(桌面端用)")
|
||||
@GetMapping("/xslmes/mesXslRawMaterialEntry/anon/prepareNativePrint")
|
||||
public Result<Map<String, Object>> rawMaterialEntryAnonPrepareNativePrint(@RequestParam(name = "id") String id) {
|
||||
try {
|
||||
MesXslRawMaterialEntry entry = rawMaterialEntryService.getById(id);
|
||||
if (entry == null) return Result.error("未找到原料入场记录");
|
||||
PrintBizTemplateBind bind =
|
||||
printBizTemplateBindService.getByBizCode(MesXslPrintConstants.RAW_MATERIAL_ENTRY_PERM_ID);
|
||||
if (bind == null) return Result.error("请先在「业务打印绑定」中配置原料入场记录与打印模板");
|
||||
PrintTemplate tpl = printTemplateService.getById(bind.getTemplateId());
|
||||
if (tpl == null) return Result.error("绑定的打印模板不存在");
|
||||
ArrayNode mapping = PrintBizDataMappingUtil.parseMappingArray(bind.getFieldMappingJson());
|
||||
JsonNode bizRoot = objectMapper.valueToTree(entry);
|
||||
ObjectNode printData = PrintBizDataMappingUtil.mapBizToPrintData(bizRoot, mapping);
|
||||
PrintBizDataMappingUtil.fillMissingDataBindingParamKeys(printData, tpl.getTemplateJson());
|
||||
Map<String, Object> out = new HashMap<>(8);
|
||||
out.put("entryId", entry.getId());
|
||||
out.put("templateCode", bind.getTemplateCode());
|
||||
out.put("templateJson", tpl.getTemplateJson());
|
||||
out.put("printData", objectMapper.convertValue(printData, Map.class));
|
||||
return Result.OK(out);
|
||||
} catch (Exception e) {
|
||||
log.error("原料入场记录-免密准备打印数据失败 id={}", id, e);
|
||||
return Result.error("准备打印数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════ 仓库管理(只读,供桌面端下拉选取) ═══════════════════════════
|
||||
|
||||
@Operation(summary = "仓库-免密分页列表查询(供桌面端筛选使用)")
|
||||
@@ -632,6 +737,7 @@ public class MesXslDesktopAnonController {
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslWarehouseArea> qw = QueryGenerator.initQueryWrapper(mesXslWarehouseArea, req.getParameterMap());
|
||||
IPage<MesXslWarehouseArea> page = warehouseAreaService.page(new Page<>(pageNo, pageSize), qw);
|
||||
warehouseAreaService.enrichDisplayedActualCapacity(page.getRecords());
|
||||
return Result.OK(page);
|
||||
}
|
||||
|
||||
@@ -639,7 +745,11 @@ public class MesXslDesktopAnonController {
|
||||
@GetMapping("/xslmes/mesXslWarehouseArea/anon/queryById")
|
||||
public Result<MesXslWarehouseArea> 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 = "库区-免密添加")
|
||||
|
||||
@@ -1,27 +1,50 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
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.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;
|
||||
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.modules.print.entity.PrintBizTemplateBind;
|
||||
import org.jeecg.modules.print.entity.PrintTemplate;
|
||||
import org.jeecg.modules.print.service.IPrintBizTemplateBindService;
|
||||
import org.jeecg.modules.print.service.IPrintTemplateService;
|
||||
import org.jeecg.modules.print.support.PrintServerEnvironmentService;
|
||||
import org.jeecg.modules.print.support.PrintServerPdfJobService;
|
||||
import org.jeecg.modules.print.util.PrintBizDataMappingUtil;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslPrintConstants;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
@@ -34,10 +57,21 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialCard")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialCardController extends JeecgController<MesXslRawMaterialCard, IMesXslRawMaterialCardService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialCardService mesXslRawMaterialCardService;
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
@Autowired
|
||||
private PrintServerEnvironmentService printServerEnvironmentService;
|
||||
@Autowired
|
||||
private PrintServerPdfJobService printServerPdfJobService;
|
||||
@Autowired
|
||||
private IPrintBizTemplateBindService printBizTemplateBindService;
|
||||
@Autowired
|
||||
private IPrintTemplateService printTemplateService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
@@ -48,7 +82,36 @@ public class MesXslRawMaterialCardController extends JeecgController<MesXslRawMa
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
// 库区条件:去掉实体上的值避免 QueryGenerator 再拼一班;用 TRIM 与看板聚合一致(避免首尾空格导致「有汇总无明细」)
|
||||
String rawWarehouseArea = mesXslRawMaterialCard != null ? mesXslRawMaterialCard.getWarehouseArea() : null;
|
||||
String areaEq = oConvertUtils.isNotEmpty(rawWarehouseArea) ? rawWarehouseArea.trim() : "";
|
||||
boolean filterByWarehouseArea = !areaEq.isEmpty();
|
||||
if (filterByWarehouseArea) {
|
||||
mesXslRawMaterialCard.setWarehouseArea(null);
|
||||
}
|
||||
QueryWrapper<MesXslRawMaterialCard> 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<MesXslRawMaterialCard> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslRawMaterialCard> pageList = mesXslRawMaterialCardService.page(page, queryWrapper);
|
||||
return Result.OK(pageList);
|
||||
@@ -75,7 +138,12 @@ public class MesXslRawMaterialCardController extends JeecgController<MesXslRawMa
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslRawMaterialCard mesXslRawMaterialCard) {
|
||||
mesXslRawMaterialCardService.updateById(mesXslRawMaterialCard);
|
||||
boolean ok =
|
||||
mesXslRawMaterialCardService.updateEditableInventoryFields(
|
||||
mesXslRawMaterialCard, "Web端原材料卡片", null);
|
||||
if (!ok) {
|
||||
return Result.error("未找到数据或更新失败,请刷新后重试");
|
||||
}
|
||||
stompNotify.publishRawMaterialCardChanged("edit", mesXslRawMaterialCard.getId());
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
@@ -122,6 +190,88 @@ public class MesXslRawMaterialCardController extends JeecgController<MesXslRawMa
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询可用打印机(与 /print/template/queryPrinters 返回结构一致)
|
||||
*/
|
||||
@Operation(summary = "原材料卡片-查询可用打印机")
|
||||
@GetMapping(value = "/queryPrinters")
|
||||
/** 有 list 可进页拉打印机;有 edit 可打印拉打印机,避免单权限缺另一项时 403 */
|
||||
@RequiresPermissions(
|
||||
value = {"xslmes:mes_xsl_raw_material_card:list", "xslmes:mes_xsl_raw_material_card:edit"},
|
||||
logical = Logical.OR)
|
||||
public Result<Map<String, Object>> queryPrinters() {
|
||||
return Result.OK(printServerEnvironmentService.buildPrinterQueryResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据业务打印绑定生成模板 JSON + 映射后的 printData,供前端生成 PDF 后调用 printPdf
|
||||
*/
|
||||
@Operation(summary = "原材料卡片-准备原生打印数据")
|
||||
@GetMapping(value = "/prepareNativePrint")
|
||||
/** 与 printPdf 一致:准备模板与打印数据属于「打印」动作,使用 edit 权限 */
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:edit")
|
||||
public Result<Map<String, Object>> prepareNativePrint(@RequestParam(name = "id") String id) {
|
||||
try {
|
||||
MesXslRawMaterialCard card = mesXslRawMaterialCardService.getById(id);
|
||||
if (card == null) {
|
||||
return Result.error("未找到原材料卡片");
|
||||
}
|
||||
PrintBizTemplateBind bind =
|
||||
printBizTemplateBindService.getByBizCode(MesXslPrintConstants.RAW_MATERIAL_CARD_PERM_ID);
|
||||
if (bind == null) {
|
||||
return Result.error("请先在「业务打印绑定」中配置原材料卡片与打印模板");
|
||||
}
|
||||
PrintTemplate tpl = printTemplateService.getById(bind.getTemplateId());
|
||||
if (tpl == null) {
|
||||
return Result.error("绑定的打印模板不存在");
|
||||
}
|
||||
ArrayNode mapping =
|
||||
PrintBizDataMappingUtil.parseMappingArray(bind.getFieldMappingJson());
|
||||
JsonNode bizRoot = objectMapper.valueToTree(card);
|
||||
ObjectNode printData = PrintBizDataMappingUtil.mapBizToPrintData(bizRoot, mapping);
|
||||
PrintBizDataMappingUtil.fillMissingDataBindingParamKeys(printData, tpl.getTemplateJson());
|
||||
Map<String, Object> out = new HashMap<>(8);
|
||||
out.put("cardId", card.getId());
|
||||
out.put("templateCode", bind.getTemplateCode());
|
||||
out.put("templateJson", tpl.getTemplateJson());
|
||||
out.put("paperWidthMm", tpl.getPaperWidthMm());
|
||||
out.put("paperHeightMm", tpl.getPaperHeightMm());
|
||||
out.put("paperOrientation", tpl.getPaperOrientation());
|
||||
out.put("printData", objectMapper.convertValue(printData, Map.class));
|
||||
return Result.OK(out);
|
||||
} catch (Exception e) {
|
||||
log.error("原材料卡片准备打印数据失败 id={}", id, e);
|
||||
return Result.error("准备打印数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将前端生成的 PDF Base64 提交到服务器打印机(与 /print/template/directPrintPdf 一致)
|
||||
*/
|
||||
@AutoLog(value = "原材料卡片-PDF后端打印")
|
||||
@Operation(summary = "原材料卡片-PDF后端打印")
|
||||
@PostMapping(value = "/printPdf")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card:edit")
|
||||
public Result<String> printPdf(@RequestBody Map<String, Object> body) {
|
||||
String id = String.valueOf(body.getOrDefault("id", "")).trim();
|
||||
String printerName = String.valueOf(body.getOrDefault("printerName", "")).trim();
|
||||
String pdfBase64 = String.valueOf(body.getOrDefault("pdfBase64", "")).trim();
|
||||
String fileName = String.valueOf(body.getOrDefault("fileName", "")).trim();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
return Result.error("id 不能为空");
|
||||
}
|
||||
MesXslRawMaterialCard card = mesXslRawMaterialCardService.getById(id);
|
||||
if (card == null) {
|
||||
return Result.error("未找到原材料卡片");
|
||||
}
|
||||
String prefix =
|
||||
StringUtils.isNotBlank(card.getBarcode()) ? card.getBarcode() : card.getId();
|
||||
String fn =
|
||||
StringUtils.isNotBlank(fileName) ? fileName : ("原材料卡片-" + prefix + ".pdf");
|
||||
return printServerPdfJobService.submitPdfBase64(
|
||||
printerName, pdfBase64, fn, "RAW_CARD_" + prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardEditLogService;
|
||||
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;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志(只读查询)
|
||||
*/
|
||||
@Tag(name = "原材料卡片修改日志")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialCardEditLog")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialCardEditLogController
|
||||
extends JeecgController<MesXslRawMaterialCardEditLog, IMesXslRawMaterialCardEditLogService> {
|
||||
|
||||
@Operation(summary = "原材料卡片修改日志-分页列表查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card_edit_log:list")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialCardEditLog>> queryPageList(
|
||||
MesXslRawMaterialCardEditLog entity,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslRawMaterialCardEditLog> qw = QueryGenerator.initQueryWrapper(entity, req.getParameterMap());
|
||||
qw.orderByDesc("modify_time");
|
||||
Page<MesXslRawMaterialCardEditLog> page = new Page<>(pageNo, pageSize);
|
||||
return Result.OK(service.page(page, qw));
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料卡片修改日志-通过id查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card_edit_log:list")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslRawMaterialCardEditLog> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslRawMaterialCardEditLog row = service.getById(id);
|
||||
if (row == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(row);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_card_edit_log:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialCardEditLog entity) {
|
||||
return super.exportXls(request, entity, MesXslRawMaterialCardEditLog.class, "原材料卡片修改日志");
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,52 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
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.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.print.entity.PrintBizTemplateBind;
|
||||
import org.jeecg.modules.print.entity.PrintTemplate;
|
||||
import org.jeecg.modules.print.service.IPrintBizTemplateBindService;
|
||||
import org.jeecg.modules.print.service.IPrintTemplateService;
|
||||
import org.jeecg.modules.print.support.PrintServerEnvironmentService;
|
||||
import org.jeecg.modules.print.support.PrintServerPdfJobService;
|
||||
import org.jeecg.modules.print.util.PrintBizDataMappingUtil;
|
||||
import org.jeecg.modules.xslmes.constant.MesXslPrintConstants;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
|
||||
/**
|
||||
* @Description: 原料入场记录
|
||||
@@ -40,6 +64,16 @@ public class MesXslRawMaterialEntryController extends JeecgController<MesXslRawM
|
||||
private IMesXslRawMaterialEntryService mesXslRawMaterialEntryService;
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
@Autowired
|
||||
private PrintServerEnvironmentService printServerEnvironmentService;
|
||||
@Autowired
|
||||
private PrintServerPdfJobService printServerPdfJobService;
|
||||
@Autowired
|
||||
private IPrintBizTemplateBindService printBizTemplateBindService;
|
||||
@Autowired
|
||||
private IPrintTemplateService printTemplateService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Operation(summary = "原料入场记录-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
@@ -73,35 +107,72 @@ public class MesXslRawMaterialEntryController extends JeecgController<MesXslRawM
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-查询关联的已生成原材料卡片(删除前校验)")
|
||||
@GetMapping("/linkedRawMaterialCards")
|
||||
@RequiresPermissions(
|
||||
value = {"xslmes:mes_xsl_raw_material_entry:delete", "xslmes:mes_xsl_raw_material_entry:deleteBatch"},
|
||||
logical = Logical.OR)
|
||||
public Result<List<MesXslRawMaterialCardBriefVO>> linkedRawMaterialCards(
|
||||
@RequestParam(name = "ids", required = true) String ids) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
return Result.OK(mesXslRawMaterialEntryService.listLinkedRawMaterialCards(idList));
|
||||
}
|
||||
|
||||
@AutoLog(value = "原料入场记录-通过id删除")
|
||||
@Operation(summary = "原料入场记录-通过id删除")
|
||||
@Operation(summary = "原料入场记录-通过id删除(cascadeDeleteCards=true 时同时删除关联原材料卡片)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
mesXslRawMaterialEntryService.removeById(id);
|
||||
stompNotify.publishRawMaterialEntryChanged("delete", id);
|
||||
public Result<?> delete(
|
||||
@RequestParam(name = "id", required = true) String id,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "false") boolean cascadeDeleteCards) {
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
mesXslRawMaterialEntryService.removeEntriesRespectingLinkedCards(
|
||||
List.of(id), cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"该原料入场记录已生成原材料卡片,确认删除时将一并删除下列卡片。请传 cascadeDeleteCards=true 重试。",
|
||||
blocked.get());
|
||||
}
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "原料入场记录-批量结存入库")
|
||||
@Operation(summary = "原料入场记录-批量结存入库:将选中记录的入库结存改为「是」")
|
||||
@Operation(summary = "原料入场记录-批量结存入库:将选中记录结存并自动汇总原材料库存")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:stockIn")
|
||||
@PutMapping(value = "/batchStockIn")
|
||||
public Result<String> batchStockIn(@RequestParam(name = "ids", required = true) String ids) {
|
||||
UpdateWrapper<MesXslRawMaterialEntry> uw = new UpdateWrapper<>();
|
||||
uw.in("id", Arrays.asList(ids.split(","))).set("stock_balance", "1");
|
||||
mesXslRawMaterialEntryService.update(uw);
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.toList();
|
||||
mesXslRawMaterialEntryService.batchStockInAndSyncInventory(idList);
|
||||
stompNotify.publishRawMaterialEntryChanged("batchStockIn", ids);
|
||||
return Result.OK("结存入库成功!");
|
||||
return Result.OK("结存入库成功,库存已自动汇总!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "原料入场记录-批量删除")
|
||||
@Operation(summary = "原料入场记录-批量删除")
|
||||
@Operation(summary = "原料入场记录-批量删除(cascadeDeleteCards=true 时同时删除关联原材料卡片)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:deleteBatch")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
this.mesXslRawMaterialEntryService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
stompNotify.publishRawMaterialEntryChanged("batchDelete", ids);
|
||||
public Result<?> deleteBatch(
|
||||
@RequestParam(name = "ids", required = true) String ids,
|
||||
@RequestParam(name = "cascadeDeleteCards", defaultValue = "false") boolean cascadeDeleteCards) {
|
||||
List<String> idList = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> blocked =
|
||||
mesXslRawMaterialEntryService.removeEntriesRespectingLinkedCards(idList, cascadeDeleteCards);
|
||||
if (blocked.isPresent()) {
|
||||
return Result.error(
|
||||
"选中记录关联的原材料卡片仍存在,确认删除时将一并删除下列卡片。请传 cascadeDeleteCards=true 重试。",
|
||||
blocked.get());
|
||||
}
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@@ -115,6 +186,79 @@ public class MesXslRawMaterialEntryController extends JeecgController<MesXslRawM
|
||||
return Result.OK(mesXslRawMaterialEntry);
|
||||
}
|
||||
|
||||
@Operation(summary = "原料入场记录-查询可用打印机")
|
||||
@GetMapping(value = "/queryPrinters")
|
||||
@RequiresPermissions(
|
||||
value = {"xslmes:mes_xsl_raw_material_entry:list", "xslmes:mes_xsl_raw_material_entry:edit"},
|
||||
logical = Logical.OR)
|
||||
public Result<Map<String, Object>> queryPrinters() {
|
||||
return Result.OK(printServerEnvironmentService.buildPrinterQueryResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据业务打印绑定生成模板 JSON + 映射后的 printData,供前端生成 PDF 后调用 printPdf
|
||||
*/
|
||||
@Operation(summary = "原料入场记录-准备原生打印数据")
|
||||
@GetMapping(value = "/prepareNativePrint")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:edit")
|
||||
public Result<Map<String, Object>> prepareNativePrint(@RequestParam(name = "id") String id) {
|
||||
try {
|
||||
MesXslRawMaterialEntry entry = mesXslRawMaterialEntryService.getById(id);
|
||||
if (entry == null) {
|
||||
return Result.error("未找到原料入场记录");
|
||||
}
|
||||
PrintBizTemplateBind bind =
|
||||
printBizTemplateBindService.getByBizCode(MesXslPrintConstants.RAW_MATERIAL_ENTRY_PERM_ID);
|
||||
if (bind == null) {
|
||||
return Result.error("请先在「业务打印绑定」中配置原料入场记录与打印模板");
|
||||
}
|
||||
PrintTemplate tpl = printTemplateService.getById(bind.getTemplateId());
|
||||
if (tpl == null) {
|
||||
return Result.error("绑定的打印模板不存在");
|
||||
}
|
||||
ArrayNode mapping = PrintBizDataMappingUtil.parseMappingArray(bind.getFieldMappingJson());
|
||||
JsonNode bizRoot = objectMapper.valueToTree(entry);
|
||||
ObjectNode printData = PrintBizDataMappingUtil.mapBizToPrintData(bizRoot, mapping);
|
||||
PrintBizDataMappingUtil.fillMissingDataBindingParamKeys(printData, tpl.getTemplateJson());
|
||||
Map<String, Object> out = new HashMap<>(8);
|
||||
out.put("entryId", entry.getId());
|
||||
out.put("templateCode", bind.getTemplateCode());
|
||||
out.put("templateJson", tpl.getTemplateJson());
|
||||
out.put("paperWidthMm", tpl.getPaperWidthMm());
|
||||
out.put("paperHeightMm", tpl.getPaperHeightMm());
|
||||
out.put("paperOrientation", tpl.getPaperOrientation());
|
||||
out.put("printData", objectMapper.convertValue(printData, Map.class));
|
||||
return Result.OK(out);
|
||||
} catch (Exception e) {
|
||||
log.error("原料入场记录准备打印数据失败 id={}", id, e);
|
||||
return Result.error("准备打印数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@AutoLog(value = "原料入场记录-PDF后端打印")
|
||||
@Operation(summary = "原料入场记录-PDF后端打印")
|
||||
@PostMapping(value = "/printPdf")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:edit")
|
||||
public Result<String> printPdf(@RequestBody Map<String, Object> body) {
|
||||
String id = String.valueOf(body.getOrDefault("id", "")).trim();
|
||||
String printerName = String.valueOf(body.getOrDefault("printerName", "")).trim();
|
||||
String pdfBase64 = String.valueOf(body.getOrDefault("pdfBase64", "")).trim();
|
||||
String fileName = String.valueOf(body.getOrDefault("fileName", "")).trim();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
return Result.error("id 不能为空");
|
||||
}
|
||||
MesXslRawMaterialEntry entry = mesXslRawMaterialEntryService.getById(id);
|
||||
if (entry == null) {
|
||||
return Result.error("未找到原料入场记录");
|
||||
}
|
||||
String prefix =
|
||||
StringUtils.isNotBlank(entry.getBarcode()) ? entry.getBarcode() : entry.getId();
|
||||
String fn =
|
||||
StringUtils.isNotBlank(fileName) ? fileName : ("原料入场记录-" + prefix + ".pdf");
|
||||
return printServerPdfJobService.submitPdfBase64(
|
||||
printerName, pdfBase64, fn, "RAW_ENTRY_" + prefix);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialEntry mesXslRawMaterialEntry) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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 org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
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/mesXslRawMaterialEntryDeleteLog")
|
||||
public class MesXslRawMaterialEntryDeleteLogController {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialEntryService mesXslRawMaterialEntryService;
|
||||
|
||||
@Operation(summary = "原料入场删除日志-分页查询")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_entry_delete_log:list")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialEntryDeleteLogVO>> queryPageList(
|
||||
MesXslRawMaterialEntry query,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
MesXslRawMaterialEntry q = query == null ? new MesXslRawMaterialEntry() : query;
|
||||
q.setDelFlag(null);
|
||||
QueryWrapper<MesXslRawMaterialEntry> qw = QueryGenerator.initQueryWrapper(q, req.getParameterMap());
|
||||
qw.eq("del_flag", CommonConstant.DEL_FLAG_1);
|
||||
qw.orderByDesc("update_time").orderByDesc("create_time");
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page = new Page<>(pageNo, pageSize);
|
||||
return Result.OK(mesXslRawMaterialEntryService.pageDeletedLog(page, qw));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialInventoryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
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;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 原材料库存
|
||||
*/
|
||||
@Tag(name = "原材料库存")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialInventory")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialInventoryController extends JeecgController<MesXslRawMaterialInventory, IMesXslRawMaterialInventoryService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialInventoryService mesXslRawMaterialInventoryService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Operation(summary = "原材料库存-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialInventory>> queryPageList(
|
||||
MesXslRawMaterialInventory mesXslRawMaterialInventory,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslRawMaterialInventory> queryWrapper = QueryGenerator.initQueryWrapper(mesXslRawMaterialInventory, req.getParameterMap());
|
||||
Page<MesXslRawMaterialInventory> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslRawMaterialInventory> pageList = mesXslRawMaterialInventoryService.page(page, queryWrapper);
|
||||
fillMaterialDisplay(pageList.getRecords());
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@Operation(summary = "原材料库存-通过id查询")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslRawMaterialInventory> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
MesXslRawMaterialInventory entity = mesXslRawMaterialInventoryService.getById(id);
|
||||
if (entity == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
fillMaterialDisplay(Collections.singletonList(entity));
|
||||
return Result.OK(entity);
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_inventory:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialInventory mesXslRawMaterialInventory) {
|
||||
return super.exportXls(request, mesXslRawMaterialInventory, MesXslRawMaterialInventory.class, "原材料库存");
|
||||
}
|
||||
|
||||
private void fillMaterialDisplay(List<MesXslRawMaterialInventory> rows) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> materialIds = rows.stream()
|
||||
.map(MesXslRawMaterialInventory::getMaterialId)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
if (materialIds.isEmpty()) {
|
||||
fillWarehouseDisplay(rows);
|
||||
return;
|
||||
}
|
||||
List<String> idList = materialIds.stream().toList();
|
||||
String placeholders = String.join(",", Collections.nCopies(idList.size(), "?"));
|
||||
String sql = "SELECT id, material_code, material_name FROM mes_mixer_material WHERE id IN (" + placeholders + ") "
|
||||
+ "AND (del_flag = 0 OR del_flag IS NULL)";
|
||||
Map<String, String> codeMap = new HashMap<>();
|
||||
Map<String, String> nameMap = new HashMap<>();
|
||||
jdbcTemplate.query(sql, rs -> {
|
||||
String id = trim(rs.getString("id"));
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
codeMap.put(id, trim(rs.getString("material_code")));
|
||||
nameMap.put(id, trim(rs.getString("material_name")));
|
||||
}, idList.toArray());
|
||||
|
||||
for (MesXslRawMaterialInventory row : rows) {
|
||||
String id = trim(row.getMaterialId());
|
||||
if (id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String code = codeMap.get(id);
|
||||
String name = nameMap.get(id);
|
||||
if (code != null && !code.isBlank()) {
|
||||
row.setMaterialCode(code);
|
||||
}
|
||||
if (name != null && !name.isBlank()) {
|
||||
row.setMaterialName(name);
|
||||
}
|
||||
}
|
||||
fillWarehouseDisplay(rows);
|
||||
}
|
||||
|
||||
private void fillWarehouseDisplay(List<MesXslRawMaterialInventory> rows) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> warehouseIds = rows.stream()
|
||||
.map(MesXslRawMaterialInventory::getWarehouseId)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
if (warehouseIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> idList = warehouseIds.stream().toList();
|
||||
String placeholders = String.join(",", Collections.nCopies(idList.size(), "?"));
|
||||
String sql = "SELECT id, warehouse_name FROM mes_xsl_warehouse WHERE id IN (" + placeholders + ") "
|
||||
+ "AND (del_flag = 0 OR del_flag IS NULL)";
|
||||
Map<String, String> nameMap = new HashMap<>();
|
||||
jdbcTemplate.query(sql, rs -> {
|
||||
String id = trim(rs.getString("id"));
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
nameMap.put(id, trim(rs.getString("warehouse_name")));
|
||||
}, idList.toArray());
|
||||
for (MesXslRawMaterialInventory row : rows) {
|
||||
String id = trim(row.getWarehouseId());
|
||||
if (id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String name = nameMap.get(id);
|
||||
if (name != null && !name.isBlank()) {
|
||||
row.setWarehouseName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String trim(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
}
|
||||
@@ -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<MesXslRawMaterialWarehouseBoardVO> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
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 java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialWorkshopRemain;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialWorkshopRemainService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 原材料车间剩余量
|
||||
*/
|
||||
@Tag(name = "原材料车间剩余量")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/mesXslRawMaterialWorkshopRemain")
|
||||
@Slf4j
|
||||
public class MesXslRawMaterialWorkshopRemainController
|
||||
extends JeecgController<MesXslRawMaterialWorkshopRemain, IMesXslRawMaterialWorkshopRemainService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialWorkshopRemainService mesXslRawMaterialWorkshopRemainService;
|
||||
|
||||
@Operation(summary = "原材料车间剩余量-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslRawMaterialWorkshopRemain>> queryPageList(
|
||||
MesXslRawMaterialWorkshopRemain query,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslRawMaterialWorkshopRemain> queryWrapper =
|
||||
QueryGenerator.initQueryWrapper(query, req.getParameterMap());
|
||||
Page<MesXslRawMaterialWorkshopRemain> page = new Page<>(pageNo, pageSize);
|
||||
return Result.OK(mesXslRawMaterialWorkshopRemainService.page(page, queryWrapper));
|
||||
}
|
||||
|
||||
@AutoLog(value = "原材料车间剩余量-批量设置优先使用")
|
||||
@Operation(summary = "原材料车间剩余量-批量设置优先使用")
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_workshop_remain:edit")
|
||||
@PutMapping(value = "/batchUpdatePriority")
|
||||
public Result<String> batchUpdatePriority(
|
||||
@RequestParam(name = "ids") String ids,
|
||||
@RequestParam(name = "priorityPickup") String priorityPickup) {
|
||||
List<String> idList =
|
||||
Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toList();
|
||||
if (idList.isEmpty()) {
|
||||
return Result.error("请先选择记录");
|
||||
}
|
||||
if (!"0".equals(priorityPickup) && !"1".equals(priorityPickup)) {
|
||||
return Result.error("priorityPickup 仅支持 0 或 1");
|
||||
}
|
||||
UpdateWrapper<MesXslRawMaterialWorkshopRemain> uw = new UpdateWrapper<>();
|
||||
uw.in("id", idList).set("priority_pickup", priorityPickup);
|
||||
mesXslRawMaterialWorkshopRemainService.update(uw);
|
||||
return Result.OK("批量更新成功!");
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_raw_material_workshop_remain:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslRawMaterialWorkshopRemain query) {
|
||||
return super.exportXls(request, query, MesXslRawMaterialWorkshopRemain.class, "原材料车间剩余量");
|
||||
}
|
||||
}
|
||||
@@ -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,24 @@ 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.IMesXslWarehouseAreaCapacityCfgService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslWarehouseAreaCapacityMatchVO;
|
||||
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,9 +46,15 @@ public class MesXslWarehouseAreaController extends JeecgController<MesXslWarehou
|
||||
@Autowired
|
||||
private IMesXslWarehouseAreaService mesXslWarehouseAreaService;
|
||||
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Autowired
|
||||
private IMesXslWarehouseAreaCapacityCfgService mesXslWarehouseAreaCapacityCfgService;
|
||||
|
||||
@Operation(summary = "MES库区管理-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslWarehouseArea>> queryPageList(
|
||||
@@ -49,6 +65,7 @@ public class MesXslWarehouseAreaController extends JeecgController<MesXslWarehou
|
||||
QueryWrapper<MesXslWarehouseArea> queryWrapper = QueryGenerator.initQueryWrapper(mesXslWarehouseArea, req.getParameterMap());
|
||||
Page<MesXslWarehouseArea> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslWarehouseArea> pageList = mesXslWarehouseAreaService.page(page, queryWrapper);
|
||||
mesXslWarehouseAreaService.enrichDisplayedActualCapacity(pageList.getRecords());
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@@ -166,13 +183,49 @@ public class MesXslWarehouseAreaController extends JeecgController<MesXslWarehou
|
||||
if (entity == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
mesXslWarehouseAreaService.enrichDisplayedActualCapacity(Collections.singletonList(entity));
|
||||
return Result.OK(entity);
|
||||
}
|
||||
|
||||
@Operation(summary = "MES库区管理-匹配仓库配置(实际存放量按原材料卡片汇总的分类)")
|
||||
@RequiresPermissions("xslmes:mes_xsl_warehouse_area:capacityMatch")
|
||||
@GetMapping(value = "/capacityMatchConfig")
|
||||
public Result<MesXslWarehouseAreaCapacityMatchVO> capacityMatchConfig() {
|
||||
return Result.OK(mesXslWarehouseAreaCapacityCfgService.getCapacityMatchForEdit());
|
||||
}
|
||||
|
||||
@AutoLog(value = "MES库区管理-保存匹配仓库配置")
|
||||
@Operation(summary = "MES库区管理-保存匹配仓库配置")
|
||||
@RequiresPermissions("xslmes:mes_xsl_warehouse_area:capacityMatch")
|
||||
@PostMapping(value = "/capacityMatchConfig")
|
||||
public Result<String> saveCapacityMatchConfig(@RequestBody MesXslWarehouseAreaCapacityMatchVO vo) {
|
||||
mesXslWarehouseAreaCapacityCfgService.saveCapacityMatch(vo);
|
||||
return Result.OK("保存成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions("xslmes:mes_xsl_warehouse_area:exportXls")
|
||||
@RequestMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, MesXslWarehouseArea mesXslWarehouseArea) {
|
||||
return super.exportXls(request, mesXslWarehouseArea, MesXslWarehouseArea.class, "MES库区管理");
|
||||
QueryWrapper<MesXslWarehouseArea> 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<MesXslWarehouseArea> 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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 业务实体字段配置子表:一类明细表对应一行,字段清单存 JSON。
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_biz_entity_field_detail")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "业务实体字段配置-明细表字段清单")
|
||||
public class MesXslBizEntityFieldDetail implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "主表ID")
|
||||
private String profileId;
|
||||
|
||||
@Schema(description = "主实体上明细属性名(与打印绑定 detailProperty 一致,如 lines)")
|
||||
private String detailPropertyName;
|
||||
|
||||
@Schema(description = "槽位类型:LIST 或 OBJECT")
|
||||
private String detailSlotKind;
|
||||
|
||||
@Schema(description = "明细展示名称")
|
||||
private String detailName;
|
||||
|
||||
@Schema(description = "明细实体 Java 全限定类名")
|
||||
private String detailEntityClassName;
|
||||
|
||||
/** 明细表字段列表 JSON */
|
||||
@Schema(description = "明细表字段列表(JSON 数组)")
|
||||
private String detailFieldsJson;
|
||||
|
||||
@Schema(description = "排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "更新时间")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
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 java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 业务实体字段配置主表:业务名称、主实体类名、主表字段 JSON;明细通过 detailList 关联子表。
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_biz_entity_field_profile")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "业务实体字段配置主表")
|
||||
public class MesXslBizEntityFieldProfile implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "业务名称")
|
||||
private String businessName;
|
||||
|
||||
@Schema(description = "业务编码(菜单 permission id,与业务打印绑定 biz_code、print_biz_perm_entity.perm_id 一致)")
|
||||
private String businessCode;
|
||||
|
||||
@Schema(description = "主实体 Java 全限定类名")
|
||||
private String entityClassName;
|
||||
|
||||
/** 主表实体字段列表 JSON */
|
||||
@Schema(description = "主表实体字段列表(JSON 数组)")
|
||||
private String mainFieldsJson;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
/** 各明细表对应的字段清单(不落主表) */
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "明细表字段配置列表")
|
||||
private List<MesXslBizEntityFieldDetail> detailList;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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.math.BigDecimal;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志(编辑剩余量/库区时落库)
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_raw_material_card_edit_log")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "原材料卡片修改日志")
|
||||
public class MesXslRawMaterialCardEditLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
|
||||
@Excel(name = "卡片ID", width = 28)
|
||||
@Schema(description = "原材料卡片ID")
|
||||
private String cardId;
|
||||
|
||||
@Excel(name = "条码", width = 22)
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Excel(name = "批次号", width = 18)
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Excel(name = "物料名称", width = 22)
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Excel(name = "物料ID", width = 22)
|
||||
@Schema(description = "物料ID")
|
||||
private String materialId;
|
||||
|
||||
@Excel(name = "修改前重量", width = 14)
|
||||
@Schema(description = "修改前剩余重量")
|
||||
private BigDecimal beforeRemainingWeight;
|
||||
|
||||
@Excel(name = "修改前数量", width = 12)
|
||||
@Schema(description = "修改前剩余数量")
|
||||
private Integer beforeRemainingQty;
|
||||
|
||||
@Excel(name = "修改后重量", width = 14)
|
||||
@Schema(description = "修改后剩余重量")
|
||||
private BigDecimal afterRemainingWeight;
|
||||
|
||||
@Excel(name = "修改前库区", width = 16)
|
||||
@Schema(description = "修改前库区")
|
||||
private String beforeWarehouseArea;
|
||||
|
||||
@Excel(name = "修改后数量", width = 12)
|
||||
@Schema(description = "修改后剩余数量")
|
||||
private Integer afterRemainingQty;
|
||||
|
||||
@Excel(name = "修改后库区", width = 16)
|
||||
@Schema(description = "修改后库区")
|
||||
private String afterWarehouseArea;
|
||||
|
||||
@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")
|
||||
@Schema(description = "修改时间")
|
||||
private Date modifyTime;
|
||||
|
||||
@Excel(name = "修改人姓名", width = 16)
|
||||
@Schema(description = "修改人姓名")
|
||||
private String modifyByName;
|
||||
|
||||
@Excel(name = "租户ID", width = 10)
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Excel(name = "数值来源", width = 22)
|
||||
@Schema(description = "数值来源")
|
||||
private String dataSource;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
@@ -204,6 +205,11 @@ public class MesXslRawMaterialEntry implements Serializable {
|
||||
@Schema(description = "更新日期")
|
||||
private Date updateTime;
|
||||
|
||||
/**逻辑删除(0正常 1已删除);删除接口改为打标,便于「删除日志」查询。*/
|
||||
@Schema(description = "逻辑删除(0正常 1已删除)")
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 原材料库存
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("mes_xsl_raw_material_inventory")
|
||||
@Schema(description = "原材料库存")
|
||||
public class MesXslRawMaterialInventory extends JeecgEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Excel(name = "所在仓库", width = 20)
|
||||
@Schema(description = "所在仓库")
|
||||
private String warehouseName;
|
||||
|
||||
@Schema(description = "所在仓库ID(关联 mes_xsl_warehouse.id)")
|
||||
private String warehouseId;
|
||||
|
||||
@Schema(description = "物料ID(关联 mes_mixer_material.id)")
|
||||
private String materialId;
|
||||
|
||||
@Excel(name = "物料名称", width = 20)
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Excel(name = "物料编码", width = 20)
|
||||
@Schema(description = "物料编码")
|
||||
private String materialCode;
|
||||
|
||||
@Excel(name = "状态检验", width = 12, dicCode = "xslmes_test_result")
|
||||
@Dict(dicCode = "xslmes_test_result")
|
||||
@Schema(description = "状态检验(字典 xslmes_test_result)")
|
||||
private String testResult;
|
||||
|
||||
@Excel(name = "总包数", width = 12)
|
||||
@Schema(description = "总包数")
|
||||
private BigDecimal totalPackages;
|
||||
|
||||
@Excel(name = "总重量", width = 12)
|
||||
@Schema(description = "总重量")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 原材料车间剩余量
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("mes_xsl_raw_material_card")
|
||||
@Schema(description = "原材料车间剩余量")
|
||||
public class MesXslRawMaterialWorkshopRemain extends JeecgEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Excel(name = "条码", width = 20)
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Excel(name = "批次号", width = 20)
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Excel(name = "入场日期", width = 15, format = "yyyy-MM-dd")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@Schema(description = "入场日期")
|
||||
private Date entryDate;
|
||||
|
||||
@Excel(name = "物料名称", width = 20)
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Excel(name = "供应商", width = 20)
|
||||
@Schema(description = "供应商")
|
||||
private String supplierName;
|
||||
|
||||
@Excel(name = "保质期", width = 15)
|
||||
@Schema(description = "保质期")
|
||||
private String shelfLife;
|
||||
|
||||
@Excel(name = "总重", width = 12)
|
||||
@Schema(description = "总重")
|
||||
private BigDecimal totalWeight;
|
||||
|
||||
@Excel(name = "剩余重量", width = 12)
|
||||
@Schema(description = "剩余重量")
|
||||
private BigDecimal remainingWeight;
|
||||
|
||||
@Excel(name = "剩余数量", width = 12)
|
||||
@Schema(description = "剩余数量")
|
||||
private Integer remainingQuantity;
|
||||
|
||||
@Excel(name = "检测结果", width = 12, dicCode = "xslmes_test_result")
|
||||
@Dict(dicCode = "xslmes_test_result")
|
||||
@Schema(description = "检测结果")
|
||||
private String testResult;
|
||||
|
||||
@Excel(name = "状态", width = 10, dicCode = "xslmes_card_status")
|
||||
@Dict(dicCode = "xslmes_card_status")
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Excel(name = "优先使用", width = 10, dicCode = "yn")
|
||||
@Dict(dicCode = "yn")
|
||||
@TableField("priority_pickup")
|
||||
@Schema(description = "优先使用(yn:1是 0否)")
|
||||
private String priorityPickup;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.jeecg.modules.xslmes.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 库区「实际存放量」展示回填规则(按租户;匹配哪些仓库分类走原材料卡片汇总)
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("mes_xsl_warehouse_area_capacity_cfg")
|
||||
@Schema(description = "MES库区实际存放量回填配置")
|
||||
public class MesXslWarehouseAreaCapacityCfg extends JeecgEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "是否启用回填 0否 1是")
|
||||
private Integer enabled;
|
||||
|
||||
@Schema(description = "仓库分类 sys_category.id,逗号分隔")
|
||||
private String warehouseCategoryIds;
|
||||
|
||||
@Schema(description = "仓库分类编码,逗号分隔")
|
||||
private String warehouseCategoryCodes;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
|
||||
|
||||
@Mapper
|
||||
public interface MesXslBizEntityFieldDetailMapper extends BaseMapper<MesXslBizEntityFieldDetail> {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
|
||||
|
||||
@Mapper
|
||||
public interface MesXslBizEntityFieldProfileMapper extends BaseMapper<MesXslBizEntityFieldProfile> {}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志
|
||||
*/
|
||||
public interface MesXslRawMaterialCardEditLogMapper extends BaseMapper<MesXslRawMaterialCardEditLog> {}
|
||||
@@ -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<MesXslRawMaterialCard> {
|
||||
|
||||
/**
|
||||
* 按 trim 后的库区编码汇总原材料卡片剩余数量(当前租户由拦截器补齐)
|
||||
*/
|
||||
List<MesXslAreaRemainQtySumDTO> sumRemainingQtyByTrimmedWarehouseArea(@Param("areaCodes") Collection<String> areaCodes);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
|
||||
/**
|
||||
* @Description: 原料入场记录
|
||||
@@ -10,4 +16,11 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface MesXslRawMaterialEntryMapper extends BaseMapper<MesXslRawMaterialEntry> {
|
||||
|
||||
/**
|
||||
* 分页查询已逻辑删除的入场记录(自定义 SQL,不按未删除过滤)
|
||||
*/
|
||||
IPage<MesXslRawMaterialEntryDeleteLogVO> selectDeletedPage(
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page,
|
||||
@Param(Constants.WRAPPER) QueryWrapper<MesXslRawMaterialEntry> queryWrapper);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
|
||||
/**
|
||||
* 原材料库存 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MesXslRawMaterialInventoryMapper extends BaseMapper<MesXslRawMaterialInventory> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialWorkshopRemain;
|
||||
|
||||
/**
|
||||
* 原材料车间剩余量 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MesXslRawMaterialWorkshopRemainMapper extends BaseMapper<MesXslRawMaterialWorkshopRemain> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.xslmes.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouseAreaCapacityCfg;
|
||||
|
||||
/**
|
||||
* MES 库区实际存放量回填规则
|
||||
*/
|
||||
public interface MesXslWarehouseAreaCapacityCfgMapper extends BaseMapper<MesXslWarehouseAreaCapacityCfg> {}
|
||||
@@ -1,4 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper">
|
||||
<!-- 库区编码已与卡片 warehouse_area 按 TRIM 对齐(见库区/卡片筛选逻辑) -->
|
||||
<select id="sumRemainingQtyByTrimmedWarehouseArea"
|
||||
resultType="org.jeecg.modules.xslmes.dto.MesXslAreaRemainQtySumDTO">
|
||||
SELECT TRIM(t.warehouse_area) AS areaCode,
|
||||
COALESCE(SUM(COALESCE(t.remaining_quantity, 0)), 0) AS qtySum
|
||||
FROM mes_xsl_raw_material_card t
|
||||
WHERE t.warehouse_area IS NOT NULL
|
||||
AND TRIM(t.warehouse_area) != ''
|
||||
AND TRIM(t.warehouse_area) IN
|
||||
<foreach collection="areaCodes" item="c" open="(" separator="," close=")">
|
||||
#{c}
|
||||
</foreach>
|
||||
GROUP BY TRIM(t.warehouse_area)
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.xslmes.mapper.MesXslRawMaterialEntryMapper">
|
||||
|
||||
<select id="selectDeletedPage" resultType="org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO">
|
||||
SELECT
|
||||
t.barcode,
|
||||
t.batch_no,
|
||||
t.create_by,
|
||||
t.create_time,
|
||||
t.material_name
|
||||
FROM mes_xsl_raw_material_entry t
|
||||
${ew.customSqlSegment}
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package org.jeecg.modules.xslmes.print.catalog;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.print.catalog.IPrintBizEntityFieldCatalogProvider;
|
||||
import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
|
||||
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslBizEntityFieldDetailMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 将 mes_xsl_biz_entity_field_* 中的缓存提供给打印绑定接口(SPI 实现)。
|
||||
*
|
||||
* <p>bizCode = {@code print_biz_perm_entity.perm_id}。
|
||||
*
|
||||
* <p>「新增绑定」下拉组装时对每条业务若单独查库会产生严重 N+1;{@link #beginBulkLookup(Collection)} 在同一请求线程内改为单次 IN 查询。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PrintBizEntityFieldCatalogProviderImpl implements IPrintBizEntityFieldCatalogProvider {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
/** null=非批量模式;非 null=批量模式(可能为空 Map) */
|
||||
private static final ThreadLocal<Map<String, MesXslBizEntityFieldProfile>> BULK_PROFILE_BY_CODE =
|
||||
new ThreadLocal<>();
|
||||
|
||||
@Resource
|
||||
private IMesXslBizEntityFieldProfileService bizEntityFieldProfileService;
|
||||
|
||||
@Resource
|
||||
private MesXslBizEntityFieldDetailMapper detailMapper;
|
||||
|
||||
@Override
|
||||
public void beginBulkLookup(Collection<String> bizCodes) {
|
||||
endBulkLookup();
|
||||
if (bizCodes == null || bizCodes.isEmpty()) {
|
||||
BULK_PROFILE_BY_CODE.set(Collections.emptyMap());
|
||||
return;
|
||||
}
|
||||
List<String> ids =
|
||||
bizCodes.stream()
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (ids.isEmpty()) {
|
||||
BULK_PROFILE_BY_CODE.set(Collections.emptyMap());
|
||||
return;
|
||||
}
|
||||
List<MesXslBizEntityFieldProfile> list =
|
||||
bizEntityFieldProfileService
|
||||
.lambdaQuery()
|
||||
.in(MesXslBizEntityFieldProfile::getBusinessCode, ids)
|
||||
.list();
|
||||
Map<String, MesXslBizEntityFieldProfile> map = new HashMap<>(Math.max(16, list.size() * 2));
|
||||
for (MesXslBizEntityFieldProfile p : list) {
|
||||
if (p != null && StringUtils.isNotBlank(p.getBusinessCode())) {
|
||||
map.put(p.getBusinessCode().trim(), p);
|
||||
}
|
||||
}
|
||||
BULK_PROFILE_BY_CODE.set(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endBulkLookup() {
|
||||
BULK_PROFILE_BY_CODE.remove();
|
||||
}
|
||||
|
||||
/** 批量模式下读线程 Map;否则单次按编码查询 */
|
||||
private MesXslBizEntityFieldProfile resolveProfile(String bizCode) {
|
||||
if (StringUtils.isBlank(bizCode)) {
|
||||
return null;
|
||||
}
|
||||
Map<String, MesXslBizEntityFieldProfile> bulk = BULK_PROFILE_BY_CODE.get();
|
||||
if (bulk != null) {
|
||||
return bulk.get(bizCode.trim());
|
||||
}
|
||||
return bizEntityFieldProfileService.getByBusinessCode(bizCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityClassFqn(String bizCode) {
|
||||
MesXslBizEntityFieldProfile p = resolveProfile(bizCode);
|
||||
return p != null ? StringUtils.trimToNull(p.getEntityClassName()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCatalogForBiz(String bizCode) {
|
||||
return resolveProfile(bizCode) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrintBizFieldItemVO> listMainFields(String bizCode) {
|
||||
MesXslBizEntityFieldProfile p = resolveProfile(bizCode);
|
||||
if (p == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return parseFieldItems(p.getMainFieldsJson());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrintBizDetailSlotVO> listDetailSlots(String bizCode) {
|
||||
MesXslBizEntityFieldProfile p = resolveProfile(bizCode);
|
||||
if (p == null || StringUtils.isBlank(p.getId())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<MesXslBizEntityFieldDetail> lines =
|
||||
detailMapper.selectList(
|
||||
new LambdaQueryWrapper<MesXslBizEntityFieldDetail>()
|
||||
.eq(MesXslBizEntityFieldDetail::getProfileId, p.getId())
|
||||
.orderByAsc(MesXslBizEntityFieldDetail::getSortNo)
|
||||
.orderByAsc(MesXslBizEntityFieldDetail::getId));
|
||||
List<PrintBizDetailSlotVO> out = new ArrayList<>();
|
||||
for (MesXslBizEntityFieldDetail line : lines) {
|
||||
if (StringUtils.isBlank(line.getDetailPropertyName())) {
|
||||
continue;
|
||||
}
|
||||
out.add(
|
||||
new PrintBizDetailSlotVO(
|
||||
line.getDetailPropertyName(),
|
||||
StringUtils.defaultString(line.getDetailEntityClassName()),
|
||||
StringUtils.defaultIfBlank(line.getDetailSlotKind(), "LIST"),
|
||||
StringUtils.defaultIfBlank(line.getDetailName(), line.getDetailPropertyName())));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrintBizFieldItemVO> listPrefixedDetailFields(String bizCode, String detailProperty, String slotKind) {
|
||||
if (StringUtils.isAnyBlank(bizCode, detailProperty)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
MesXslBizEntityFieldProfile p = resolveProfile(bizCode);
|
||||
if (p == null || StringUtils.isBlank(p.getId())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String prop = detailProperty.trim();
|
||||
String reqKind = StringUtils.trimToEmpty(slotKind);
|
||||
List<MesXslBizEntityFieldDetail> lines =
|
||||
detailMapper.selectList(
|
||||
new LambdaQueryWrapper<MesXslBizEntityFieldDetail>()
|
||||
.eq(MesXslBizEntityFieldDetail::getProfileId, p.getId()));
|
||||
MesXslBizEntityFieldDetail hit = null;
|
||||
for (MesXslBizEntityFieldDetail line : lines) {
|
||||
if (!prop.equals(line.getDetailPropertyName())) {
|
||||
continue;
|
||||
}
|
||||
if (!slotKindMatch(reqKind, line.getDetailSlotKind())) {
|
||||
continue;
|
||||
}
|
||||
hit = line;
|
||||
break;
|
||||
}
|
||||
if (hit == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<PrintBizFieldItemVO> raw = parseFieldItems(hit.getDetailFieldsJson());
|
||||
List<PrintBizFieldItemVO> out = new ArrayList<>(raw.size());
|
||||
for (PrintBizFieldItemVO x : raw) {
|
||||
String path = prop + "." + x.getFieldKey();
|
||||
String label = "明细「" + prop + "」→ " + x.getLabel();
|
||||
out.add(PrintBizFieldItemVO.copyWithPrefixedPath(x, path, label));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean slotKindMatch(String requested, String stored) {
|
||||
String r = StringUtils.trimToEmpty(requested);
|
||||
String s = StringUtils.trimToEmpty(stored);
|
||||
if (StringUtils.isBlank(s)) {
|
||||
return true;
|
||||
}
|
||||
if (StringUtils.isBlank(r)) {
|
||||
return true;
|
||||
}
|
||||
return r.equalsIgnoreCase(s);
|
||||
}
|
||||
|
||||
/** 解析 JSON 数组:支持 VO 对象元素或纯字符串字段名 */
|
||||
private List<PrintBizFieldItemVO> parseFieldItems(String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(json);
|
||||
if (!root.isArray()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<PrintBizFieldItemVO> out = new ArrayList<>();
|
||||
for (JsonNode n : root) {
|
||||
if (n.isTextual()) {
|
||||
String k = n.asText();
|
||||
if (StringUtils.isNotBlank(k)) {
|
||||
out.add(PrintBizFieldItemVO.plainStringField(k));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n.isObject()) {
|
||||
PrintBizFieldItemVO vo = OBJECT_MAPPER.treeToValue(n, PrintBizFieldItemVO.class);
|
||||
if (vo != null && StringUtils.isNotBlank(vo.getFieldKey())) {
|
||||
out.add(vo);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
} catch (Exception e) {
|
||||
log.warn("解析业务实体字段缓存 JSON 失败: {}", e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import java.util.List;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
|
||||
|
||||
/** 业务实体字段配置(主子表) */
|
||||
public interface IMesXslBizEntityFieldProfileService extends IService<MesXslBizEntityFieldProfile> {
|
||||
|
||||
/** 按业务编码(菜单 permission id)查询主表,不含 detailList */
|
||||
MesXslBizEntityFieldProfile getByBusinessCode(String businessCode);
|
||||
|
||||
/**
|
||||
* 按业务编码 upsert(用于启动扫描写入);明细全量替换。
|
||||
*
|
||||
* @param businessCode 与 print_biz_perm_entity.perm_id、biz_code 一致
|
||||
*/
|
||||
void upsertScannedProfile(
|
||||
String businessCode,
|
||||
String businessName,
|
||||
String entityFqn,
|
||||
String mainFieldsJson,
|
||||
List<MesXslBizEntityFieldDetail> detailRows);
|
||||
|
||||
/** 新增主表并保存明细 */
|
||||
void saveWithDetails(MesXslBizEntityFieldProfile profile);
|
||||
|
||||
/** 更新主表并重写明细 */
|
||||
void updateWithDetails(MesXslBizEntityFieldProfile profile);
|
||||
|
||||
/** 按主键删除主表及明细 */
|
||||
void removeWithDetails(String id);
|
||||
|
||||
/** 批量删除主表及明细 */
|
||||
void removeBatchWithDetails(java.util.Collection<String> ids);
|
||||
|
||||
/** 查询主表并填充 detailList */
|
||||
MesXslBizEntityFieldProfile getByIdWithDetails(String id);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志
|
||||
*/
|
||||
public interface IMesXslRawMaterialCardEditLogService extends IService<MesXslRawMaterialCardEditLog> {}
|
||||
@@ -10,4 +10,15 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface IMesXslRawMaterialCardService extends IService<MesXslRawMaterialCard> {
|
||||
|
||||
/**
|
||||
* 列表/桌面端「编辑」仅允许调整剩余数量、剩余重量与库区,其它字段忽略且不写库。
|
||||
*
|
||||
* @param incoming 至少包含 id;仅读取 remainingQuantity、remainingWeight、warehouseArea
|
||||
* @param dataSource 数值来源说明(如 Web端原材料卡片、桌面端免密编辑),空则默认 Web端
|
||||
* @param modifierRealnameOverride 修改人姓名;空则取当前登录用户真实姓名(免密接口可传入固定文案)
|
||||
* @return 是否存在主键且更新成功(受影响行大于 0)
|
||||
*/
|
||||
boolean updateEditableInventoryFields(
|
||||
MesXslRawMaterialCard incoming, String dataSource, String modifierRealnameOverride);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @Description: 原料入场记录
|
||||
@@ -15,6 +22,26 @@ import java.util.Map;
|
||||
*/
|
||||
public interface IMesXslRawMaterialEntryService extends IService<MesXslRawMaterialEntry> {
|
||||
|
||||
/**
|
||||
* 原料入场删除日志:分页查询已逻辑删除的记录(仅条码/批次/创建人/创建时间/物料名称)。
|
||||
*/
|
||||
IPage<MesXslRawMaterialEntryDeleteLogVO> pageDeletedLog(
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page,
|
||||
QueryWrapper<MesXslRawMaterialEntry> queryWrapper);
|
||||
|
||||
/**
|
||||
* 根据入场记录主键查询已生成的原材料卡片(按拆码明细 ID 优先,否则按条码+批次等降级关联)。
|
||||
*/
|
||||
List<MesXslRawMaterialCardBriefVO> listLinkedRawMaterialCards(Collection<String> entryIds);
|
||||
|
||||
/**
|
||||
* 删除入场记录;若存在关联卡片且 cascadeDeleteCards=false,不删除任何数据并返回关联列表。
|
||||
*
|
||||
* @return empty 表示已删除入场记录(并在 cascade 为 true 时删除关联卡片);非空则为被拦截时的关联卡片
|
||||
*/
|
||||
Optional<List<MesXslRawMaterialCardBriefVO>> removeEntriesRespectingLinkedCards(
|
||||
Collection<String> entryIds, boolean cascadeDeleteCards);
|
||||
|
||||
/**
|
||||
* 生成条码/批次号
|
||||
* 格式:QH + 物料编码 + 日期(yyMMdd) + 当日序号(001起)
|
||||
@@ -34,4 +61,15 @@ public interface IMesXslRawMaterialEntryService extends IService<MesXslRawMateri
|
||||
* @return billNo -> 累计已入场重量;查不到的 billNo 不会出现在 map 中
|
||||
*/
|
||||
Map<String, BigDecimal> sumEnteredWeightByBillNos(Collection<String> billNos);
|
||||
|
||||
/**
|
||||
* 结存入库并汇总原材料库存。
|
||||
* <p>
|
||||
* 对本次选中的入场记录(仅处理 stock_balance != 1)按
|
||||
* 「所在仓库 + 物料编码 + 检验状态」汇总总包数/总重量并累加到库存表,
|
||||
* 同时将入场记录 stock_balance 更新为 1。
|
||||
*
|
||||
* @param ids 入场记录主键集合
|
||||
*/
|
||||
void batchStockInAndSyncInventory(Collection<String> ids);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
|
||||
/**
|
||||
* 原材料库存
|
||||
*/
|
||||
public interface IMesXslRawMaterialInventoryService extends IService<MesXslRawMaterialInventory> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialWorkshopRemain;
|
||||
|
||||
/**
|
||||
* 原材料车间剩余量
|
||||
*/
|
||||
public interface IMesXslRawMaterialWorkshopRemainService extends IService<MesXslRawMaterialWorkshopRemain> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.xslmes.service;
|
||||
|
||||
import org.jeecg.modules.xslmes.vo.MesXslWarehouseAreaCapacityMatchVO;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 库区「实际存放量」回填:页面配置存储 + 运行时解析
|
||||
*/
|
||||
public interface IMesXslWarehouseAreaCapacityCfgService {
|
||||
|
||||
/**
|
||||
* 弹窗回显:库内有配置读库;否则回显 YAML / 默认编码(configSource 区分)
|
||||
*/
|
||||
MesXslWarehouseAreaCapacityMatchVO getCapacityMatchForEdit();
|
||||
|
||||
/** 保存或更新当前租户配置 */
|
||||
void saveCapacityMatch(MesXslWarehouseAreaCapacityMatchVO vo);
|
||||
|
||||
/** 是否启用回填(库内 saved=0 则关闭,不回落 YAML) */
|
||||
boolean isActualCapacityBackfillEnabled();
|
||||
|
||||
/**
|
||||
* 参与「原材料卡片 remaining 汇总」的仓库分类 sys_category.id 集合。
|
||||
* 无库内记录时等同原 YAML + 编码解析逻辑。
|
||||
*/
|
||||
Set<String> resolveRawMaterialWarehouseCategoryIds();
|
||||
}
|
||||
@@ -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<MesXslWarehouseAre
|
||||
* 同租户下是否已存在相同库区编码(编辑时传 excludeId 排除自身)
|
||||
*/
|
||||
boolean existsSameAreaCode(String areaCode, String excludeId);
|
||||
|
||||
/**
|
||||
* 按配置策略回填列表中的「实际存放量」展示值(不写库)
|
||||
*/
|
||||
void enrichDisplayedActualCapacity(Collection<MesXslWarehouseArea> areas);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
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.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldDetail;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslBizEntityFieldProfile;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslBizEntityFieldDetailMapper;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslBizEntityFieldProfileMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslBizEntityFieldProfileService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class MesXslBizEntityFieldProfileServiceImpl extends ServiceImpl<MesXslBizEntityFieldProfileMapper, MesXslBizEntityFieldProfile>
|
||||
implements IMesXslBizEntityFieldProfileService {
|
||||
|
||||
/** 标记为启动任务根据 print_biz_perm_entity 写入,便于区分手工维护数据 */
|
||||
private static final String REMARK_PRINT_PERM_SCAN = "print_biz_perm_entity 启动异步扫描";
|
||||
|
||||
@Resource
|
||||
private MesXslBizEntityFieldDetailMapper detailMapper;
|
||||
|
||||
@Override
|
||||
public MesXslBizEntityFieldProfile getByBusinessCode(String businessCode) {
|
||||
if (StringUtils.isBlank(businessCode)) {
|
||||
return null;
|
||||
}
|
||||
return this.lambdaQuery()
|
||||
.eq(MesXslBizEntityFieldProfile::getBusinessCode, businessCode.trim())
|
||||
.last("LIMIT 1")
|
||||
.one();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void upsertScannedProfile(
|
||||
String businessCode,
|
||||
String businessName,
|
||||
String entityFqn,
|
||||
String mainFieldsJson,
|
||||
List<MesXslBizEntityFieldDetail> detailRows) {
|
||||
if (StringUtils.isBlank(businessCode)) {
|
||||
return;
|
||||
}
|
||||
String code = businessCode.trim();
|
||||
Date now = new Date();
|
||||
MesXslBizEntityFieldProfile existing = getByBusinessCode(code);
|
||||
if (existing == null) {
|
||||
MesXslBizEntityFieldProfile p = new MesXslBizEntityFieldProfile();
|
||||
p.setBusinessCode(code);
|
||||
p.setBusinessName(StringUtils.defaultIfBlank(businessName, code));
|
||||
p.setEntityClassName(entityFqn);
|
||||
p.setMainFieldsJson(mainFieldsJson);
|
||||
p.setRemark(REMARK_PRINT_PERM_SCAN);
|
||||
p.setCreateTime(now);
|
||||
p.setUpdateTime(now);
|
||||
p.setDetailList(detailRows != null ? detailRows : List.of());
|
||||
saveWithDetails(p);
|
||||
return;
|
||||
}
|
||||
existing.setBusinessName(StringUtils.defaultIfBlank(businessName, existing.getBusinessName()));
|
||||
existing.setEntityClassName(entityFqn);
|
||||
existing.setMainFieldsJson(mainFieldsJson);
|
||||
existing.setRemark(REMARK_PRINT_PERM_SCAN);
|
||||
existing.setUpdateTime(now);
|
||||
existing.setDetailList(detailRows != null ? detailRows : List.of());
|
||||
updateWithDetails(existing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveWithDetails(MesXslBizEntityFieldProfile profile) {
|
||||
this.save(profile);
|
||||
insertDetailRows(profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateWithDetails(MesXslBizEntityFieldProfile profile) {
|
||||
this.updateById(profile);
|
||||
detailMapper.delete(new LambdaQueryWrapper<MesXslBizEntityFieldDetail>().eq(MesXslBizEntityFieldDetail::getProfileId, profile.getId()));
|
||||
insertDetailRows(profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeWithDetails(String id) {
|
||||
if (StringUtils.isBlank(id)) {
|
||||
return;
|
||||
}
|
||||
detailMapper.delete(new LambdaQueryWrapper<MesXslBizEntityFieldDetail>().eq(MesXslBizEntityFieldDetail::getProfileId, id));
|
||||
this.removeById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeBatchWithDetails(Collection<String> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> trimmed = new ArrayList<>();
|
||||
for (String id : ids) {
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
String t = id.trim();
|
||||
trimmed.add(t);
|
||||
detailMapper.delete(new LambdaQueryWrapper<MesXslBizEntityFieldDetail>().eq(MesXslBizEntityFieldDetail::getProfileId, t));
|
||||
}
|
||||
}
|
||||
if (!trimmed.isEmpty()) {
|
||||
this.removeByIds(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MesXslBizEntityFieldProfile getByIdWithDetails(String id) {
|
||||
MesXslBizEntityFieldProfile profile = this.getById(id);
|
||||
if (profile == null) {
|
||||
return null;
|
||||
}
|
||||
List<MesXslBizEntityFieldDetail> lines =
|
||||
detailMapper.selectList(
|
||||
new LambdaQueryWrapper<MesXslBizEntityFieldDetail>()
|
||||
.eq(MesXslBizEntityFieldDetail::getProfileId, id)
|
||||
.orderByAsc(MesXslBizEntityFieldDetail::getSortNo)
|
||||
.orderByAsc(MesXslBizEntityFieldDetail::getId));
|
||||
profile.setDetailList(lines);
|
||||
return profile;
|
||||
}
|
||||
|
||||
/** 写入子表(编辑时已清空旧数据) */
|
||||
private void insertDetailRows(MesXslBizEntityFieldProfile profile) {
|
||||
if (profile.getDetailList() == null || profile.getDetailList().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Date now = new Date();
|
||||
int seq = 0;
|
||||
for (MesXslBizEntityFieldDetail row : profile.getDetailList()) {
|
||||
row.setId(null);
|
||||
row.setProfileId(profile.getId());
|
||||
if (row.getSortNo() == null) {
|
||||
row.setSortNo(seq++);
|
||||
}
|
||||
if (row.getCreateTime() == null) {
|
||||
row.setCreateTime(now);
|
||||
}
|
||||
if (row.getUpdateTime() == null) {
|
||||
row.setUpdateTime(now);
|
||||
}
|
||||
if (StringUtils.isBlank(row.getCreateBy())) {
|
||||
row.setCreateBy(profile.getCreateBy());
|
||||
}
|
||||
if (StringUtils.isBlank(row.getUpdateBy())) {
|
||||
row.setUpdateBy(profile.getUpdateBy());
|
||||
}
|
||||
detailMapper.insert(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardEditLogMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardEditLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 原材料卡片修改日志
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialCardEditLogServiceImpl
|
||||
extends ServiceImpl<MesXslRawMaterialCardEditLogMapper, MesXslRawMaterialCardEditLog>
|
||||
implements IMesXslRawMaterialCardEditLogService {}
|
||||
@@ -1,10 +1,20 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCardEditLog;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialCardMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardEditLogService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @Description: 原材料卡片
|
||||
@@ -13,5 +23,94 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialCardServiceImpl extends ServiceImpl<MesXslRawMaterialCardMapper, MesXslRawMaterialCard> implements IMesXslRawMaterialCardService {
|
||||
public class MesXslRawMaterialCardServiceImpl extends ServiceImpl<MesXslRawMaterialCardMapper, MesXslRawMaterialCard>
|
||||
implements IMesXslRawMaterialCardService {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialCardEditLogService editLogService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateEditableInventoryFields(
|
||||
MesXslRawMaterialCard incoming, String dataSource, String modifierRealnameOverride) {
|
||||
if (incoming == null || StringUtils.isBlank(incoming.getId())) {
|
||||
return false;
|
||||
}
|
||||
MesXslRawMaterialCard existing = getById(incoming.getId());
|
||||
if (existing == null) {
|
||||
return false;
|
||||
}
|
||||
String area = incoming.getWarehouseArea();
|
||||
String areaTrimmed = area == null ? null : StringUtils.trimToNull(area);
|
||||
|
||||
boolean weightChanged = !bigDecimalEquals(existing.getRemainingWeight(), incoming.getRemainingWeight());
|
||||
boolean qtyChanged = !Objects.equals(existing.getRemainingQuantity(), incoming.getRemainingQuantity());
|
||||
boolean areaChanged = !Objects.equals(normArea(existing.getWarehouseArea()), areaTrimmed);
|
||||
boolean anyChanged = weightChanged || qtyChanged || areaChanged;
|
||||
|
||||
boolean ok = lambdaUpdate()
|
||||
.eq(MesXslRawMaterialCard::getId, incoming.getId())
|
||||
.set(MesXslRawMaterialCard::getRemainingWeight, incoming.getRemainingWeight())
|
||||
.set(MesXslRawMaterialCard::getRemainingQuantity, incoming.getRemainingQuantity())
|
||||
.set(MesXslRawMaterialCard::getWarehouseArea, areaTrimmed)
|
||||
.update();
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
if (!anyChanged) {
|
||||
return true;
|
||||
}
|
||||
String ds = StringUtils.isNotBlank(dataSource) ? dataSource : "Web端原材料卡片";
|
||||
String modifier = resolveModifierName(modifierRealnameOverride);
|
||||
MesXslRawMaterialCardEditLog logRow = new MesXslRawMaterialCardEditLog();
|
||||
logRow.setCardId(existing.getId());
|
||||
logRow.setBarcode(existing.getBarcode());
|
||||
logRow.setBatchNo(existing.getBatchNo());
|
||||
logRow.setMaterialName(existing.getMaterialName());
|
||||
logRow.setMaterialId(existing.getMaterialId());
|
||||
logRow.setBeforeRemainingWeight(existing.getRemainingWeight());
|
||||
logRow.setBeforeRemainingQty(existing.getRemainingQuantity());
|
||||
logRow.setAfterRemainingWeight(incoming.getRemainingWeight());
|
||||
logRow.setBeforeWarehouseArea(existing.getWarehouseArea());
|
||||
logRow.setAfterRemainingQty(incoming.getRemainingQuantity());
|
||||
logRow.setAfterWarehouseArea(areaTrimmed);
|
||||
logRow.setModifyTime(new Date());
|
||||
logRow.setModifyByName(modifier);
|
||||
logRow.setTenantId(existing.getTenantId());
|
||||
logRow.setDataSource(ds);
|
||||
editLogService.save(logRow);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String normArea(String s) {
|
||||
return s == null ? null : StringUtils.trimToNull(s);
|
||||
}
|
||||
|
||||
private static boolean bigDecimalEquals(BigDecimal a, BigDecimal b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
return a.compareTo(b) == 0;
|
||||
}
|
||||
|
||||
private static String resolveModifierName(String override) {
|
||||
if (StringUtils.isNotBlank(override)) {
|
||||
return override;
|
||||
}
|
||||
try {
|
||||
Object p = SecurityUtils.getSubject().getPrincipal();
|
||||
if (p instanceof LoginUser) {
|
||||
String name = ((LoginUser) p).getRealname();
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 免密或未登录场景
|
||||
}
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialEntry;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialEntryMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialInventoryService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialEntryService;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService;
|
||||
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialEntryDeleteLogVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -30,6 +51,126 @@ public class MesXslRawMaterialEntryServiceImpl
|
||||
extends ServiceImpl<MesXslRawMaterialEntryMapper, MesXslRawMaterialEntry>
|
||||
implements IMesXslRawMaterialEntryService {
|
||||
|
||||
@Autowired
|
||||
private IMesXslRawMaterialInventoryService mesXslRawMaterialInventoryService;
|
||||
@Autowired
|
||||
private IMesXslRawMaterialCardService mesXslRawMaterialCardService;
|
||||
@Autowired
|
||||
private IMesXslWarehouseAreaService mesXslWarehouseAreaService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private MesXslStompNotifyService stompNotify;
|
||||
|
||||
@Override
|
||||
public IPage<MesXslRawMaterialEntryDeleteLogVO> pageDeletedLog(
|
||||
Page<MesXslRawMaterialEntryDeleteLogVO> page, QueryWrapper<MesXslRawMaterialEntry> queryWrapper) {
|
||||
return baseMapper.selectDeletedPage(page, queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MesXslRawMaterialCardBriefVO> listLinkedRawMaterialCards(Collection<String> entryIds) {
|
||||
if (entryIds == null || entryIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> distinctIds = entryIds.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (distinctIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<MesXslRawMaterialEntry> entries = this.listByIds(distinctIds);
|
||||
Map<String, MesXslRawMaterialCard> byId = new LinkedHashMap<>();
|
||||
for (MesXslRawMaterialEntry entry : entries) {
|
||||
for (MesXslRawMaterialCard card : findLinkedCardsForEntry(entry)) {
|
||||
if (card.getId() != null) {
|
||||
byId.putIfAbsent(card.getId(), card);
|
||||
}
|
||||
}
|
||||
}
|
||||
return byId.values().stream().map(MesXslRawMaterialEntryServiceImpl::toCardBrief).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Optional<List<MesXslRawMaterialCardBriefVO>> removeEntriesRespectingLinkedCards(
|
||||
Collection<String> entryIds, boolean cascadeDeleteCards) {
|
||||
if (entryIds == null || entryIds.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<String> distinctIds = entryIds.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.distinct()
|
||||
.toList();
|
||||
if (distinctIds.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<MesXslRawMaterialCardBriefVO> linked = listLinkedRawMaterialCards(distinctIds);
|
||||
if (!linked.isEmpty() && !cascadeDeleteCards) {
|
||||
return Optional.of(linked);
|
||||
}
|
||||
if (!linked.isEmpty()) {
|
||||
List<String> cardIds = linked.stream()
|
||||
.map(MesXslRawMaterialCardBriefVO::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (!cardIds.isEmpty()) {
|
||||
mesXslRawMaterialCardService.removeByIds(cardIds);
|
||||
stompNotify.publishRawMaterialCardChanged("batchDelete", String.join(",", cardIds));
|
||||
}
|
||||
}
|
||||
this.removeByIds(distinctIds);
|
||||
String action = distinctIds.size() > 1 ? "batchDelete" : "delete";
|
||||
stompNotify.publishRawMaterialEntryChanged(action, String.join(",", distinctIds));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** 单条入场记录关联的原材料卡片:优先拆码明细 GUID,其次条码+批次等 */
|
||||
private List<MesXslRawMaterialCard> findLinkedCardsForEntry(MesXslRawMaterialEntry entry) {
|
||||
if (entry == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String[] splitIds = splitJoined(entry.getPortionDetailIds());
|
||||
if (splitIds.length > 0) {
|
||||
return mesXslRawMaterialCardService
|
||||
.lambdaQuery()
|
||||
.in(MesXslRawMaterialCard::getSplitDetailId, Arrays.asList(splitIds))
|
||||
.list();
|
||||
}
|
||||
String bc = normalizeText(entry.getBarcode());
|
||||
String bn = normalizeText(entry.getBatchNo());
|
||||
if (!bc.isEmpty() && !bn.isEmpty()) {
|
||||
return mesXslRawMaterialCardService
|
||||
.lambdaQuery()
|
||||
.eq(MesXslRawMaterialCard::getBarcode, bc)
|
||||
.eq(MesXslRawMaterialCard::getBatchNo, bn)
|
||||
.list();
|
||||
}
|
||||
if (!bc.isEmpty()) {
|
||||
return mesXslRawMaterialCardService.lambdaQuery().eq(MesXslRawMaterialCard::getBarcode, bc).list();
|
||||
}
|
||||
if (!bn.isEmpty()) {
|
||||
return mesXslRawMaterialCardService.lambdaQuery().eq(MesXslRawMaterialCard::getBatchNo, bn).list();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static MesXslRawMaterialCardBriefVO toCardBrief(MesXslRawMaterialCard c) {
|
||||
MesXslRawMaterialCardBriefVO v = new MesXslRawMaterialCardBriefVO();
|
||||
v.setId(c.getId());
|
||||
v.setBarcode(c.getBarcode());
|
||||
v.setBatchNo(c.getBatchNo());
|
||||
v.setMaterialName(c.getMaterialName());
|
||||
v.setSplitDetailId(c.getSplitDetailId());
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateBarcode(String materialCode) {
|
||||
if (materialCode == null) materialCode = "";
|
||||
@@ -76,6 +217,166 @@ public class MesXslRawMaterialEntryServiceImpl
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void batchStockInAndSyncInventory(Collection<String> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> distinctIds = ids.stream()
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
if (distinctIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅处理未结存记录,避免重复点击导致库存重复累加。
|
||||
List<MesXslRawMaterialEntry> entries = this.lambdaQuery()
|
||||
.in(MesXslRawMaterialEntry::getId, distinctIds)
|
||||
.and(w -> w.ne(MesXslRawMaterialEntry::getStockBalance, "1")
|
||||
.or()
|
||||
.isNull(MesXslRawMaterialEntry::getStockBalance))
|
||||
.list();
|
||||
if (entries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<String, WarehouseRef> warehouseByEntryId = resolveWarehouseByEntry(entries);
|
||||
Set<String> materialIds = entries.stream()
|
||||
.map(MesXslRawMaterialEntry::getMaterialId)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> materialCodes = entries.stream()
|
||||
.map(e -> {
|
||||
String code = normalizeText(e.getMaterialCode());
|
||||
return code.isEmpty() ? parseMaterialCodeFromBarcode(e.getBarcode()) : code;
|
||||
})
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
Map<String, MaterialBaseInfo> materialInfoById = loadMaterialInfoByIds(materialIds);
|
||||
Map<String, MaterialBaseInfo> materialInfoByCode = loadMaterialInfoByCodes(materialCodes);
|
||||
|
||||
Map<String, InventoryAgg> aggMap = new HashMap<>();
|
||||
for (MesXslRawMaterialEntry entry : entries) {
|
||||
WarehouseRef warehouseRef = warehouseByEntryId.get(entry.getId());
|
||||
String warehouseId = warehouseRef == null ? "" : normalizeText(warehouseRef.id);
|
||||
String warehouseName = warehouseRef == null ? "" : normalizeText(warehouseRef.name);
|
||||
if (warehouseName.isEmpty()) {
|
||||
warehouseName = normalizeText(entry.getWarehouseLocation());
|
||||
}
|
||||
String materialId = normalizeText(entry.getMaterialId());
|
||||
String materialCode = normalizeText(entry.getMaterialCode());
|
||||
if (materialCode.isEmpty()) {
|
||||
materialCode = parseMaterialCodeFromBarcode(entry.getBarcode());
|
||||
}
|
||||
MaterialBaseInfo materialInfo = null;
|
||||
if (!materialId.isEmpty()) {
|
||||
materialInfo = materialInfoById.get(materialId);
|
||||
}
|
||||
if (materialInfo == null && !materialCode.isEmpty()) {
|
||||
materialInfo = materialInfoByCode.get(materialCode);
|
||||
if (materialInfo != null && materialId.isEmpty()) {
|
||||
materialId = normalizeText(materialInfo.id);
|
||||
}
|
||||
}
|
||||
if (materialInfo != null) {
|
||||
if (!normalizeText(materialInfo.materialCode).isEmpty()) {
|
||||
materialCode = normalizeText(materialInfo.materialCode);
|
||||
}
|
||||
}
|
||||
String materialName = materialInfo == null ? normalizeText(entry.getMaterialName()) : normalizeText(materialInfo.materialName);
|
||||
String testResult = normalizeText(entry.getTestResult());
|
||||
String materialKey = materialId.isEmpty() ? ("CODE:" + materialCode) : ("ID:" + materialId);
|
||||
String warehouseKey = warehouseId.isEmpty() ? ("NAME:" + warehouseName) : ("ID:" + warehouseId);
|
||||
String key = warehouseKey + "|" + materialKey;
|
||||
|
||||
BigDecimal packageCount = resolvePackageCount(entry);
|
||||
BigDecimal weight = resolveWeight(entry);
|
||||
|
||||
InventoryAgg agg = aggMap.computeIfAbsent(key, k -> new InventoryAgg());
|
||||
if (agg.warehouseId == null || agg.warehouseId.isBlank()) {
|
||||
agg.warehouseId = warehouseId;
|
||||
}
|
||||
agg.warehouseName = warehouseName;
|
||||
if (agg.materialId == null || agg.materialId.isBlank()) {
|
||||
agg.materialId = materialId;
|
||||
}
|
||||
agg.materialCode = materialCode;
|
||||
agg.testResult = testResult;
|
||||
if (agg.materialName == null || agg.materialName.isBlank()) {
|
||||
agg.materialName = materialName;
|
||||
}
|
||||
agg.totalPackages = agg.totalPackages.add(packageCount);
|
||||
agg.totalWeight = agg.totalWeight.add(weight);
|
||||
}
|
||||
|
||||
List<MesXslRawMaterialInventory> toSave = new ArrayList<>();
|
||||
for (InventoryAgg agg : aggMap.values()) {
|
||||
MesXslRawMaterialInventory existing = mesXslRawMaterialInventoryService.lambdaQuery()
|
||||
.eq(agg.warehouseId != null && !agg.warehouseId.isBlank(), MesXslRawMaterialInventory::getWarehouseId, agg.warehouseId)
|
||||
.eq(agg.warehouseId == null || agg.warehouseId.isBlank(), MesXslRawMaterialInventory::getWarehouseName, agg.warehouseName)
|
||||
.eq(agg.materialId != null && !agg.materialId.isBlank(), MesXslRawMaterialInventory::getMaterialId, agg.materialId)
|
||||
.eq(agg.materialId == null || agg.materialId.isBlank(), MesXslRawMaterialInventory::getMaterialCode, agg.materialCode)
|
||||
.one();
|
||||
if (existing == null && agg.warehouseId != null && !agg.warehouseId.isBlank()) {
|
||||
existing = mesXslRawMaterialInventoryService.lambdaQuery()
|
||||
.eq(MesXslRawMaterialInventory::getWarehouseName, agg.warehouseName)
|
||||
.eq(agg.materialId != null && !agg.materialId.isBlank(), MesXslRawMaterialInventory::getMaterialId, agg.materialId)
|
||||
.eq(agg.materialId == null || agg.materialId.isBlank(), MesXslRawMaterialInventory::getMaterialCode, agg.materialCode)
|
||||
.and(w -> w.isNull(MesXslRawMaterialInventory::getWarehouseId)
|
||||
.or()
|
||||
.eq(MesXslRawMaterialInventory::getWarehouseId, ""))
|
||||
.one();
|
||||
}
|
||||
if (existing == null && agg.materialId != null && !agg.materialId.isBlank()) {
|
||||
existing = mesXslRawMaterialInventoryService.lambdaQuery()
|
||||
.eq(MesXslRawMaterialInventory::getWarehouseName, agg.warehouseName)
|
||||
.eq(MesXslRawMaterialInventory::getTestResult, agg.testResult)
|
||||
.eq(MesXslRawMaterialInventory::getMaterialCode, agg.materialCode)
|
||||
.and(w -> w.isNull(MesXslRawMaterialInventory::getMaterialId)
|
||||
.or()
|
||||
.eq(MesXslRawMaterialInventory::getMaterialId, ""))
|
||||
.one();
|
||||
}
|
||||
if (existing == null) {
|
||||
MesXslRawMaterialInventory inventory = new MesXslRawMaterialInventory();
|
||||
inventory.setWarehouseName(agg.warehouseName);
|
||||
inventory.setWarehouseId(agg.warehouseId);
|
||||
inventory.setMaterialId(agg.materialId);
|
||||
inventory.setMaterialName(agg.materialName);
|
||||
inventory.setMaterialCode(agg.materialCode);
|
||||
inventory.setTestResult(agg.testResult);
|
||||
inventory.setTotalPackages(agg.totalPackages);
|
||||
inventory.setTotalWeight(agg.totalWeight);
|
||||
toSave.add(inventory);
|
||||
continue;
|
||||
}
|
||||
if (normalizeText(existing.getWarehouseId()).isEmpty() && agg.warehouseId != null && !agg.warehouseId.isBlank()) {
|
||||
existing.setWarehouseId(agg.warehouseId);
|
||||
}
|
||||
existing.setWarehouseName((agg.warehouseName == null || agg.warehouseName.isBlank()) ? existing.getWarehouseName() : agg.warehouseName);
|
||||
if (normalizeText(existing.getMaterialId()).isEmpty() && agg.materialId != null && !agg.materialId.isBlank()) {
|
||||
existing.setMaterialId(agg.materialId);
|
||||
}
|
||||
existing.setMaterialCode((agg.materialCode == null || agg.materialCode.isBlank()) ? existing.getMaterialCode() : agg.materialCode);
|
||||
existing.setMaterialName((agg.materialName == null || agg.materialName.isBlank()) ? existing.getMaterialName() : agg.materialName);
|
||||
if (normalizeText(existing.getTestResult()).isEmpty() && agg.testResult != null && !agg.testResult.isBlank()) {
|
||||
existing.setTestResult(agg.testResult);
|
||||
}
|
||||
existing.setTotalPackages(nullSafe(existing.getTotalPackages()).add(agg.totalPackages));
|
||||
existing.setTotalWeight(nullSafe(existing.getTotalWeight()).add(agg.totalWeight));
|
||||
mesXslRawMaterialInventoryService.updateById(existing);
|
||||
}
|
||||
if (!toSave.isEmpty()) {
|
||||
mesXslRawMaterialInventoryService.saveBatch(toSave);
|
||||
}
|
||||
|
||||
List<String> processedIds = entries.stream().map(MesXslRawMaterialEntry::getId).toList();
|
||||
LambdaUpdateWrapper<MesXslRawMaterialEntry> uw = new LambdaUpdateWrapper<>();
|
||||
uw.in(MesXslRawMaterialEntry::getId, processedIds)
|
||||
.set(MesXslRawMaterialEntry::getStockBalance, "1");
|
||||
this.update(uw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一条入场记录的 totalPortions/portionWeight 按 "x/y/z/" 解析后,逐位 (份数 × 每份重量) 累加。
|
||||
* 任一位置解析失败或缺失,跳过该位置(不抛异常,确保前端展示降级可用)。
|
||||
@@ -114,4 +415,295 @@ public class MesXslRawMaterialEntryServiceImpl
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static BigDecimal resolvePackageCount(MesXslRawMaterialEntry entry) {
|
||||
BigDecimal fromSplit = sumByPortion(entry.getTotalPortions(), entry.getPortionPackages());
|
||||
if (fromSplit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
return fromSplit;
|
||||
}
|
||||
BigDecimal fallback = tryParseBigDecimal(entry.getTotalPortions());
|
||||
return fallback == null ? BigDecimal.ZERO : fallback;
|
||||
}
|
||||
|
||||
private static BigDecimal resolveWeight(MesXslRawMaterialEntry entry) {
|
||||
BigDecimal fromSplit = sumByPortion(entry.getTotalPortions(), entry.getPortionWeight());
|
||||
if (fromSplit.compareTo(BigDecimal.ZERO) > 0) {
|
||||
return fromSplit;
|
||||
}
|
||||
return nullSafe(entry.getTotalWeight());
|
||||
}
|
||||
|
||||
private static BigDecimal sumByPortion(String portionsRaw, String valueRaw) {
|
||||
String[] portionsArr = splitJoined(portionsRaw);
|
||||
String[] valuesArr = splitJoined(valueRaw);
|
||||
if (portionsArr.length == 0 || valuesArr.length == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
int n = Math.min(portionsArr.length, valuesArr.length);
|
||||
BigDecimal sum = BigDecimal.ZERO;
|
||||
for (int i = 0; i < n; i++) {
|
||||
BigDecimal portions = tryParseBigDecimal(portionsArr[i]);
|
||||
BigDecimal val = tryParseBigDecimal(valuesArr[i]);
|
||||
if (portions == null || val == null) {
|
||||
continue;
|
||||
}
|
||||
sum = sum.add(portions.multiply(val));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private static String normalizeText(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static BigDecimal nullSafe(BigDecimal value) {
|
||||
return value == null ? BigDecimal.ZERO : value;
|
||||
}
|
||||
|
||||
private static class InventoryAgg {
|
||||
private String warehouseId;
|
||||
private String warehouseName;
|
||||
private String materialId;
|
||||
private String materialName;
|
||||
private String materialCode;
|
||||
private String testResult;
|
||||
private BigDecimal totalPackages = BigDecimal.ZERO;
|
||||
private BigDecimal totalWeight = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从入场记录关联的「原材料卡片 -> 库区 -> 所属仓库」反查库存仓库名称。
|
||||
* 优先通过 splitDetailId 关联;若无 splitDetailId,再降级按 barcode+batchNo 取卡片。
|
||||
*/
|
||||
private Map<String, WarehouseRef> resolveWarehouseByEntry(List<MesXslRawMaterialEntry> entries) {
|
||||
Map<String, WarehouseRef> result = new HashMap<>();
|
||||
if (entries == null || entries.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, List<String>> splitIdsByEntryId = new HashMap<>();
|
||||
Set<String> allSplitIds = new HashSet<>();
|
||||
for (MesXslRawMaterialEntry entry : entries) {
|
||||
String[] splitIds = splitJoined(entry.getPortionDetailIds());
|
||||
if (splitIds.length == 0) {
|
||||
continue;
|
||||
}
|
||||
List<String> list = java.util.Arrays.stream(splitIds).toList();
|
||||
splitIdsByEntryId.put(entry.getId(), list);
|
||||
allSplitIds.addAll(list);
|
||||
}
|
||||
|
||||
Map<String, WarehouseRef> splitIdToWarehouse = new HashMap<>();
|
||||
if (!allSplitIds.isEmpty()) {
|
||||
List<MesXslRawMaterialCard> cards = mesXslRawMaterialCardService.lambdaQuery()
|
||||
.in(MesXslRawMaterialCard::getSplitDetailId, allSplitIds)
|
||||
.select(MesXslRawMaterialCard::getSplitDetailId, MesXslRawMaterialCard::getWarehouseArea)
|
||||
.list();
|
||||
Map<String, WarehouseRef> areaToWarehouse = resolveAreaToWarehouseMap(cards.stream()
|
||||
.map(MesXslRawMaterialCard::getWarehouseArea)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet()));
|
||||
for (MesXslRawMaterialCard card : cards) {
|
||||
String splitId = normalizeText(card.getSplitDetailId());
|
||||
if (splitId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
WarehouseRef warehouse = resolveWarehouse(card.getWarehouseArea(), areaToWarehouse);
|
||||
if (warehouse == null || (warehouse.id.isEmpty() && warehouse.name.isEmpty())) {
|
||||
continue;
|
||||
}
|
||||
splitIdToWarehouse.putIfAbsent(splitId, warehouse);
|
||||
}
|
||||
}
|
||||
|
||||
for (MesXslRawMaterialEntry entry : entries) {
|
||||
List<String> splitIds = splitIdsByEntryId.get(entry.getId());
|
||||
if (splitIds == null || splitIds.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (String splitId : splitIds) {
|
||||
WarehouseRef warehouse = splitIdToWarehouse.get(splitId);
|
||||
if (warehouse != null && (!warehouse.id.isBlank() || !warehouse.name.isBlank())) {
|
||||
result.put(entry.getId(), warehouse);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 降级:没有 splitDetailId 时,按 barcode+batchNo 找一张关联卡片取仓库。
|
||||
List<MesXslRawMaterialEntry> unresolved = entries.stream()
|
||||
.filter(e -> !result.containsKey(e.getId()))
|
||||
.filter(e -> !normalizeText(e.getBarcode()).isEmpty() || !normalizeText(e.getBatchNo()).isEmpty())
|
||||
.toList();
|
||||
if (unresolved.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
Set<String> barcodes = unresolved.stream()
|
||||
.map(MesXslRawMaterialEntry::getBarcode)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> batchNos = unresolved.stream()
|
||||
.map(MesXslRawMaterialEntry::getBatchNo)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
if (barcodes.isEmpty() && batchNos.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
LambdaQueryWrapper<MesXslRawMaterialCard> cardQw = new LambdaQueryWrapper<>();
|
||||
if (!barcodes.isEmpty() && !batchNos.isEmpty()) {
|
||||
cardQw.and(w -> w.in(MesXslRawMaterialCard::getBarcode, barcodes)
|
||||
.or()
|
||||
.in(MesXslRawMaterialCard::getBatchNo, batchNos));
|
||||
} else if (!barcodes.isEmpty()) {
|
||||
cardQw.in(MesXslRawMaterialCard::getBarcode, barcodes);
|
||||
} else {
|
||||
cardQw.in(MesXslRawMaterialCard::getBatchNo, batchNos);
|
||||
}
|
||||
cardQw.select(MesXslRawMaterialCard::getBarcode, MesXslRawMaterialCard::getBatchNo, MesXslRawMaterialCard::getWarehouseArea);
|
||||
List<MesXslRawMaterialCard> cards = mesXslRawMaterialCardService.list(cardQw);
|
||||
Map<String, WarehouseRef> areaToWarehouse = resolveAreaToWarehouseMap(cards.stream()
|
||||
.map(MesXslRawMaterialCard::getWarehouseArea)
|
||||
.filter(s -> s != null && !s.isBlank())
|
||||
.collect(Collectors.toSet()));
|
||||
for (MesXslRawMaterialEntry entry : unresolved) {
|
||||
String barcode = normalizeText(entry.getBarcode());
|
||||
String batchNo = normalizeText(entry.getBatchNo());
|
||||
for (MesXslRawMaterialCard card : cards) {
|
||||
boolean match = false;
|
||||
if (!barcode.isEmpty() && barcode.equals(normalizeText(card.getBarcode()))) {
|
||||
match = true;
|
||||
}
|
||||
if (!match && !batchNo.isEmpty() && batchNo.equals(normalizeText(card.getBatchNo()))) {
|
||||
match = true;
|
||||
}
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
WarehouseRef warehouse = resolveWarehouse(card.getWarehouseArea(), areaToWarehouse);
|
||||
if (warehouse != null && (!warehouse.id.isBlank() || !warehouse.name.isBlank())) {
|
||||
result.put(entry.getId(), warehouse);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, WarehouseRef> resolveAreaToWarehouseMap(Set<String> areaRefs) {
|
||||
if (areaRefs == null || areaRefs.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<MesXslWarehouseArea> areas = mesXslWarehouseAreaService.lambdaQuery()
|
||||
.and(w -> w.in(MesXslWarehouseArea::getId, areaRefs)
|
||||
.or()
|
||||
.in(MesXslWarehouseArea::getAreaName, areaRefs))
|
||||
.select(MesXslWarehouseArea::getId, MesXslWarehouseArea::getAreaName, MesXslWarehouseArea::getWarehouseId, MesXslWarehouseArea::getWarehouseName)
|
||||
.list();
|
||||
Map<String, WarehouseRef> map = new HashMap<>();
|
||||
for (MesXslWarehouseArea area : areas) {
|
||||
WarehouseRef warehouse = new WarehouseRef();
|
||||
warehouse.id = normalizeText(area.getWarehouseId());
|
||||
warehouse.name = normalizeText(area.getWarehouseName());
|
||||
if (warehouse.id.isEmpty() && warehouse.name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String id = normalizeText(area.getId());
|
||||
if (!id.isEmpty()) {
|
||||
map.putIfAbsent(id, warehouse);
|
||||
}
|
||||
String name = normalizeText(area.getAreaName());
|
||||
if (!name.isEmpty()) {
|
||||
map.putIfAbsent(name, warehouse);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private WarehouseRef resolveWarehouse(String areaRef, Map<String, WarehouseRef> areaToWarehouse) {
|
||||
String area = normalizeText(areaRef);
|
||||
if (area.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return areaToWarehouse.get(area);
|
||||
}
|
||||
|
||||
private static String parseMaterialCodeFromBarcode(String barcodeRaw) {
|
||||
String barcode = normalizeText(barcodeRaw);
|
||||
// 规则:QH + 物料编码 + yyMMdd + 3位流水;最短长度 QH+1+6+3=12
|
||||
if (!barcode.startsWith("QH") || barcode.length() < 12) {
|
||||
return "";
|
||||
}
|
||||
int endExclusive = barcode.length() - 9;
|
||||
if (endExclusive <= 2) {
|
||||
return "";
|
||||
}
|
||||
return barcode.substring(2, endExclusive).trim();
|
||||
}
|
||||
|
||||
private Map<String, MaterialBaseInfo> loadMaterialInfoByIds(Set<String> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<String> idList = ids.stream().filter(s -> s != null && !s.isBlank()).toList();
|
||||
if (idList.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
String placeholders = String.join(",", Collections.nCopies(idList.size(), "?"));
|
||||
String sql = "SELECT id, material_code, material_name FROM mes_mixer_material WHERE id IN (" + placeholders + ") "
|
||||
+ "AND (del_flag = 0 OR del_flag IS NULL)";
|
||||
List<MaterialBaseInfo> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
MaterialBaseInfo info = new MaterialBaseInfo();
|
||||
info.id = normalizeText(rs.getString("id"));
|
||||
info.materialCode = normalizeText(rs.getString("material_code"));
|
||||
info.materialName = normalizeText(rs.getString("material_name"));
|
||||
return info;
|
||||
}, idList.toArray());
|
||||
Map<String, MaterialBaseInfo> map = new HashMap<>();
|
||||
for (MaterialBaseInfo row : rows) {
|
||||
if (row.id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
map.putIfAbsent(row.id, row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, MaterialBaseInfo> loadMaterialInfoByCodes(Set<String> codes) {
|
||||
if (codes == null || codes.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<String> codeList = codes.stream().filter(s -> s != null && !s.isBlank()).toList();
|
||||
if (codeList.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
String placeholders = String.join(",", Collections.nCopies(codeList.size(), "?"));
|
||||
String sql = "SELECT id, material_code, material_name FROM mes_mixer_material WHERE material_code IN (" + placeholders + ") "
|
||||
+ "AND (del_flag = 0 OR del_flag IS NULL)";
|
||||
List<MaterialBaseInfo> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
MaterialBaseInfo info = new MaterialBaseInfo();
|
||||
info.id = normalizeText(rs.getString("id"));
|
||||
info.materialCode = normalizeText(rs.getString("material_code"));
|
||||
info.materialName = normalizeText(rs.getString("material_name"));
|
||||
return info;
|
||||
}, codeList.toArray());
|
||||
Map<String, MaterialBaseInfo> map = new HashMap<>();
|
||||
for (MaterialBaseInfo row : rows) {
|
||||
if (row.materialCode.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
map.putIfAbsent(row.materialCode, row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static class MaterialBaseInfo {
|
||||
private String id;
|
||||
private String materialCode;
|
||||
private String materialName;
|
||||
}
|
||||
|
||||
private static class WarehouseRef {
|
||||
private String id = "";
|
||||
private String name = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInventory;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialInventoryMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialInventoryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 原材料库存
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialInventoryServiceImpl extends ServiceImpl<MesXslRawMaterialInventoryMapper, MesXslRawMaterialInventory>
|
||||
implements IMesXslRawMaterialInventoryService {
|
||||
}
|
||||
@@ -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<MesXslWarehouseArea> areaQw = new QueryWrapper<>();
|
||||
areaQw.eq("status", "0");
|
||||
if (StringUtils.isNotBlank(warehouseId)) {
|
||||
areaQw.eq("warehouse_id", warehouseId.trim());
|
||||
}
|
||||
areaQw.orderByAsc("area_code");
|
||||
List<MesXslWarehouseArea> areas = mesXslWarehouseAreaService.list(areaQw);
|
||||
if (areas.isEmpty()) {
|
||||
return vo;
|
||||
}
|
||||
mesXslWarehouseAreaService.enrichDisplayedActualCapacity(areas);
|
||||
|
||||
List<String> areaCodes = areas.stream().map(MesXslWarehouseArea::getAreaCode).filter(StringUtils::isNotBlank).map(String::trim).distinct().collect(Collectors.toList());
|
||||
|
||||
QueryWrapper<MesXslRawMaterialCard> 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<MesXslRawMaterialCard> cards = mesXslRawMaterialCardService.list(cardQw);
|
||||
|
||||
Map<String, List<MesXslRawMaterialCard>> byArea =
|
||||
cards.stream().filter(c -> StringUtils.isNotBlank(c.getWarehouseArea())).collect(Collectors.groupingBy(c -> c.getWarehouseArea().trim(), Collectors.toList()));
|
||||
|
||||
boolean filterByKeyword = kw != null;
|
||||
Map<String, BoardBandVO> bandMap = new LinkedHashMap<>();
|
||||
|
||||
for (MesXslWarehouseArea area : areas) {
|
||||
String code = StringUtils.trimToEmpty(area.getAreaCode());
|
||||
if (code.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
List<MesXslRawMaterialCard> 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<BoardBandVO> 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<MesXslRawMaterialCard> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialWorkshopRemain;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslRawMaterialWorkshopRemainMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialWorkshopRemainService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 原材料车间剩余量
|
||||
*/
|
||||
@Service
|
||||
public class MesXslRawMaterialWorkshopRemainServiceImpl
|
||||
extends ServiceImpl<MesXslRawMaterialWorkshopRemainMapper, MesXslRawMaterialWorkshopRemain>
|
||||
implements IMesXslRawMaterialWorkshopRemainService {
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package org.jeecg.modules.xslmes.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.config.TenantContext;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.system.service.ISysCategoryService;
|
||||
import org.jeecg.modules.xslmes.config.XslMesWarehouseAreaCapacityProperties;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslWarehouseAreaCapacityCfg;
|
||||
import org.jeecg.modules.xslmes.mapper.MesXslWarehouseAreaCapacityCfgMapper;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaCapacityCfgService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslWarehouseAreaCapacityMatchVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 库区实际存放量回填规则(租户级页面配置)
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MesXslWarehouseAreaCapacityCfgServiceImpl extends ServiceImpl<MesXslWarehouseAreaCapacityCfgMapper, MesXslWarehouseAreaCapacityCfg>
|
||||
implements IMesXslWarehouseAreaCapacityCfgService {
|
||||
|
||||
private final XslMesWarehouseAreaCapacityProperties yamlProperties;
|
||||
private final ISysCategoryService sysCategoryService;
|
||||
|
||||
/** 当前租户下已保存的配置(无记录则返回 null,走 YAML 兜底) */
|
||||
private MesXslWarehouseAreaCapacityCfg findTenantRowStrict() {
|
||||
int tid = resolveTenantIdBestEffort();
|
||||
if (tid <= 0) {
|
||||
return null;
|
||||
}
|
||||
return this.lambdaQuery().eq(MesXslWarehouseAreaCapacityCfg::getTenantId, tid).last("LIMIT 1").one();
|
||||
}
|
||||
|
||||
private int resolveTenantIdBestEffort() {
|
||||
String t = TenantContext.getTenant();
|
||||
if (oConvertUtils.isEmpty(t)) {
|
||||
try {
|
||||
t = TokenUtils.getTenantIdByRequest(SpringContextUtils.getHttpServletRequest());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return oConvertUtils.getInt(t, 0);
|
||||
}
|
||||
|
||||
/** 页面保存必须用有效租户 */
|
||||
private int requireTenantIdForSave() {
|
||||
int tid = resolveTenantIdBestEffort();
|
||||
if (tid <= 0) {
|
||||
throw new JeecgBootException("未获取到租户信息,无法保存匹配仓库配置");
|
||||
}
|
||||
return tid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MesXslWarehouseAreaCapacityMatchVO getCapacityMatchForEdit() {
|
||||
MesXslWarehouseAreaCapacityCfg row = findTenantRowStrict();
|
||||
MesXslWarehouseAreaCapacityMatchVO vo = new MesXslWarehouseAreaCapacityMatchVO();
|
||||
if (row != null) {
|
||||
vo.setEnabled(Integer.valueOf(1).equals(row.getEnabled()));
|
||||
vo.setWarehouseCategoryIds(StringUtils.trimToEmpty(row.getWarehouseCategoryIds()));
|
||||
vo.setWarehouseCategoryCodes(StringUtils.trimToEmpty(row.getWarehouseCategoryCodes()));
|
||||
vo.setRemark(row.getRemark());
|
||||
vo.setConfigSource("db");
|
||||
vo.setBootstrapHint(null);
|
||||
return vo;
|
||||
}
|
||||
vo.setEnabled(yamlProperties.isEnabled());
|
||||
vo.setWarehouseCategoryIds("");
|
||||
List<String> codes = yamlProperties.getRawMaterialWarehouseCategoryCodes();
|
||||
if (codes != null && !codes.isEmpty()) {
|
||||
vo.setWarehouseCategoryCodes(codes.stream().filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.joining(",")));
|
||||
} else {
|
||||
vo.setWarehouseCategoryCodes("");
|
||||
}
|
||||
vo.setRemark(null);
|
||||
vo.setBootstrapHint("尚未保存页面配置:当前展示为服务端 YAML 兜底,保存后以库内配置为准。");
|
||||
vo.setConfigSource("yaml");
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveCapacityMatch(MesXslWarehouseAreaCapacityMatchVO vo) {
|
||||
if (vo == null) {
|
||||
return;
|
||||
}
|
||||
boolean en = Boolean.TRUE.equals(vo.getEnabled());
|
||||
String ids = normalizeCsv(vo.getWarehouseCategoryIds());
|
||||
String codes = normalizeCsv(vo.getWarehouseCategoryCodes());
|
||||
String remark = StringUtils.trimToNull(vo.getRemark());
|
||||
|
||||
int tenantId = requireTenantIdForSave();
|
||||
MesXslWarehouseAreaCapacityCfg row = findTenantRowStrict();
|
||||
LoginUser user = SecurityUtils.getSubject() != null && SecurityUtils.getSubject().getPrincipal() instanceof LoginUser
|
||||
? (LoginUser) SecurityUtils.getSubject().getPrincipal()
|
||||
: null;
|
||||
String username = user != null ? user.getUsername() : null;
|
||||
java.util.Date now = new java.util.Date();
|
||||
|
||||
if (row == null) {
|
||||
row = new MesXslWarehouseAreaCapacityCfg();
|
||||
row.setTenantId(tenantId);
|
||||
row.setEnabled(en ? 1 : 0);
|
||||
row.setWarehouseCategoryIds(StringUtils.isBlank(ids) ? null : ids);
|
||||
row.setWarehouseCategoryCodes(StringUtils.isBlank(codes) ? null : codes);
|
||||
row.setRemark(remark);
|
||||
row.setCreateBy(username);
|
||||
row.setCreateTime(now);
|
||||
row.setUpdateBy(username);
|
||||
row.setUpdateTime(now);
|
||||
this.save(row);
|
||||
} else {
|
||||
row.setEnabled(en ? 1 : 0);
|
||||
row.setWarehouseCategoryIds(StringUtils.isBlank(ids) ? null : ids);
|
||||
row.setWarehouseCategoryCodes(StringUtils.isBlank(codes) ? null : codes);
|
||||
row.setRemark(remark);
|
||||
row.setUpdateBy(username);
|
||||
row.setUpdateTime(now);
|
||||
this.updateById(row);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActualCapacityBackfillEnabled() {
|
||||
MesXslWarehouseAreaCapacityCfg row = findTenantRowStrict();
|
||||
if (row != null) {
|
||||
return Integer.valueOf(1).equals(row.getEnabled());
|
||||
}
|
||||
return yamlProperties.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> resolveRawMaterialWarehouseCategoryIds() {
|
||||
MesXslWarehouseAreaCapacityCfg row = findTenantRowStrict();
|
||||
if (row != null) {
|
||||
if (!Integer.valueOf(1).equals(row.getEnabled())) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return mergeCategoryIds(row.getWarehouseCategoryIds(), row.getWarehouseCategoryCodes());
|
||||
}
|
||||
if (!yamlProperties.isEnabled()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return mergeYamlCategoryIds();
|
||||
}
|
||||
|
||||
/** 逗号拆分并 trim */
|
||||
private static List<String> splitComma(String csv) {
|
||||
if (StringUtils.isBlank(csv)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(csv.split(",")).map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static String normalizeCsv(String raw) {
|
||||
if (StringUtils.isBlank(raw)) {
|
||||
return "";
|
||||
}
|
||||
return splitComma(raw).stream().distinct().collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private Set<String> mergeCategoryIds(String idsCsv, String codesCsv) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (String id : splitComma(idsCsv)) {
|
||||
set.add(id);
|
||||
}
|
||||
for (String code : splitComma(codesCsv)) {
|
||||
resolveCode(code).ifPresent(set::add);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private Optional<String> resolveCode(String code) {
|
||||
try {
|
||||
String id = sysCategoryService.queryIdByCode(code);
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
return Optional.of(id.trim());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("[WarehouseAreaCapacityCfg] 分类编码解析失败 code={}: {}", code, ex.getMessage());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Set<String> mergeYamlCategoryIds() {
|
||||
Set<String> set = new HashSet<>();
|
||||
Collection<String> yamlIds = yamlProperties.getRawMaterialWarehouseCategoryIds();
|
||||
if (yamlIds != null) {
|
||||
for (String id : yamlIds) {
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
set.add(id.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
Collection<String> yamlCodes = yamlProperties.getRawMaterialWarehouseCategoryCodes();
|
||||
if (yamlCodes != null) {
|
||||
for (String c : yamlCodes) {
|
||||
if (StringUtils.isBlank(c)) {
|
||||
continue;
|
||||
}
|
||||
resolveCode(c.trim()).ifPresent(set::add);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
@@ -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<MesXslWarehouseAreaMapper, MesXslWarehouseArea> 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<MesXslWarehouseA
|
||||
.ne(StringUtils.isNotBlank(excludeId), MesXslWarehouseArea::getId, excludeId)
|
||||
.count() > 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enrichDisplayedActualCapacity(Collection<MesXslWarehouseArea> areas) {
|
||||
displayedActualCapacityMediator.apply(areas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ public class MesXslWarehouseServiceImpl extends ServiceImpl<MesXslWarehouseMappe
|
||||
return;
|
||||
}
|
||||
String code = baseMapper.queryCategoryCodeById(e.getWarehouseCategory());
|
||||
if (!MesXslWarehouseCategory.CUSTOMER_CATEGORY_CODE.equals(code)) {
|
||||
if (!MesXslWarehouseCategory.isCustomerWarehouseCategoryCode(code)) {
|
||||
e.setCustomerId(null);
|
||||
e.setCustomerShortName(null);
|
||||
}
|
||||
if (!MesXslWarehouseCategory.SUPPLIER_CATEGORY_CODE.equals(code)) {
|
||||
if (!MesXslWarehouseCategory.isSupplierWarehouseCategoryCode(code)) {
|
||||
e.setSupplierId(null);
|
||||
e.setSupplierShortName(null);
|
||||
}
|
||||
@@ -56,11 +56,11 @@ public class MesXslWarehouseServiceImpl extends ServiceImpl<MesXslWarehouseMappe
|
||||
return;
|
||||
}
|
||||
String code = baseMapper.queryCategoryCodeById(e.getWarehouseCategory());
|
||||
if (MesXslWarehouseCategory.CUSTOMER_CATEGORY_CODE.equals(code)) {
|
||||
if (MesXslWarehouseCategory.isCustomerWarehouseCategoryCode(code)) {
|
||||
if (oConvertUtils.isEmpty(e.getCustomerId())) {
|
||||
throw new JeecgBootException("仓库分类为客户库时,请选择客户");
|
||||
}
|
||||
} else if (MesXslWarehouseCategory.SUPPLIER_CATEGORY_CODE.equals(code)) {
|
||||
} else if (MesXslWarehouseCategory.isSupplierWarehouseCategoryCode(code)) {
|
||||
if (oConvertUtils.isEmpty(e.getSupplierId())) {
|
||||
throw new JeecgBootException("仓库分类为供应商库时,请选择供应商");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 原材料卡片简要信息(入场删除确认弹窗用)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "原材料卡片简要信息")
|
||||
public class MesXslRawMaterialCardBriefVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "卡片主键")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
|
||||
@Schema(description = "关联拆码明细行ID(可空)")
|
||||
private String splitDetailId;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
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 org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 原料入场「删除日志」列表专用 VO(仅展示字段,不落单独日志表)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "原料入场删除日志展示")
|
||||
public class MesXslRawMaterialEntryDeleteLogVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "物料名称")
|
||||
private String materialName;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 原材料库区看板:按库区编码前缀等规则形成的「分组带」汇总展示(不按业务楼层拆分)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "原材料库区看板")
|
||||
public class MesXslRawMaterialWarehouseBoardVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "计量口径回显:quantity=按剩余数量 weight=按剩余重量")
|
||||
private String measureType;
|
||||
|
||||
@Schema(description = "分组带列表")
|
||||
private List<BoardBandVO> 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<BoardAreaCardVO> 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<String> topMaterialNames = new ArrayList<>();
|
||||
|
||||
@Schema(description = "占用率 0~100,无上限或上限为0时为 null")
|
||||
private Double usagePercent;
|
||||
|
||||
@Schema(description = "告警级别:empty/low/normal/high/full/unknown")
|
||||
private String alertLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 库区管理「匹配仓库」配置(读写)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "库区实际存放量回填-匹配仓库配置")
|
||||
public class MesXslWarehouseAreaCapacityMatchVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "是否启用回填")
|
||||
private Boolean enabled;
|
||||
|
||||
/** 与前端 JCategorySelect 多选一致:sys_category.id 逗号分隔 */
|
||||
@Schema(description = "MES仓库分类ID,逗号分隔")
|
||||
private String warehouseCategoryIds;
|
||||
|
||||
@Schema(description = "MES仓库分类编码,逗号分隔(可选补充,与 YAML 兜底逻辑一致)")
|
||||
private String warehouseCategoryCodes;
|
||||
|
||||
@Schema(description = "配置来源 db=库内已保存 yaml=暂无库内配置使用文件兜底 preview")
|
||||
private String configSource;
|
||||
|
||||
@Schema(description = "首次打开且无库内配置时给出的提示(不落库)")
|
||||
private String bootstrapHint;
|
||||
|
||||
@Schema(description = "备注(落库)")
|
||||
private String remark;
|
||||
}
|
||||
Reference in New Issue
Block a user