This commit is contained in:
jhd
2026-06-02 13:52:06 +08:00
89 changed files with 7529 additions and 1892 deletions

View File

@@ -148,7 +148,10 @@ public class WmsDeliveryWaybillDetailController extends BaseController {
return new TableDataInfo<>();
}
bo.setCoilIds(boundCoilIds.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(",")));
bo.setStatusFirst(true); // 未发货的排在前面
bo.setStatusFirst(true);
if (planId == null) {
bo.setOrderByPlanDesc(true);
}
return iWmsMaterialCoilService.queryPageListWithBindInfo(bo, pageQuery);
}

View File

@@ -5,8 +5,6 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import java.util.HashMap;
@@ -33,7 +31,6 @@ import com.klp.common.core.validate.AddGroup;
import com.klp.common.core.validate.EditGroup;
import com.klp.common.enums.BusinessType;
import com.klp.common.utils.poi.ExcelUtil;
import com.klp.common.utils.StringUtils;
import com.klp.domain.bo.WmsMaterialCoilBo;
import com.klp.domain.bo.WmsMaterialCoilReportSummaryBo;
import com.klp.domain.vo.dashboard.CoilTrimStatisticsVo;
@@ -135,25 +132,6 @@ public class WmsMaterialCoilController extends BaseController {
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilExportVo.class, response);
}
/**
* 个性化导出:前端传入要导出的字段名,仅导出选中列
* columns 参数为逗号分隔的 Java 字段名,如 "itemTypeDesc,enterCoilNo,netWeight"
* 不传 columns 时等同于 /exportAll 导出全部字段
*/
@Log(title = "钢卷物料表", businessType = BusinessType.EXPORT)
@PostMapping("/exportCustom")
public void exportCustom(WmsMaterialCoilBo bo,
@RequestParam(required = false) String columns,
HttpServletResponse response) {
List<WmsMaterialCoilAllExportVo> list = iWmsMaterialCoilService.queryExportListAll(bo);
if (StringUtils.isNotBlank(columns)) {
Set<String> includeFields = new HashSet<>(Arrays.asList(columns.split(",")));
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilAllExportVo.class, includeFields, response);
} else {
ExcelUtil.exportExcel(list, "钢卷物料表", WmsMaterialCoilAllExportVo.class, response);
}
}
/**
* 获取可导出的列元数据(供前端列选择器使用)
* 返回 { "fieldName": "中文列名" } 的映射,基于完整导出字段
@@ -195,9 +173,41 @@ public class WmsMaterialCoilController extends BaseController {
columns.put("actualThickness", "实测厚度");
columns.put("transferType", "调拨类型");
columns.put("team", "班组");
columns.put("theoreticalThickness", "理论厚度");
columns.put("theoreticalLength", "理论长度");
columns.put("chromePlateCoilNo", "镀铬卷号");
return R.ok(columns);
}
/**
* 自定义导出(指定列顺序):前端传入按导出顺序排列的字段名
* columnsOrdered 参数为逗号分隔的有序字段名,如 "team,enterCoilNo,netWeight,remark"
*/
@Log(title = "钢卷物料表", businessType = BusinessType.EXPORT)
@PostMapping("/exportCustomOrdered")
public void exportCustomOrdered(WmsMaterialCoilBo bo,
@RequestParam String columnsOrdered,
HttpServletResponse response) {
List<WmsMaterialCoilAllExportVo> list = iWmsMaterialCoilService.queryExportListAll(bo);
List<String> orderedFields = Arrays.asList(columnsOrdered.split(","));
ExcelUtil.exportExcelOrdered(list, "钢卷物料表", orderedFields,
getAllExportFieldLabelMap(), response);
}
/**
* 从 WmsMaterialCoilAllExportVo 注解中提取字段名->中文列名映射
*/
private Map<String, String> getAllExportFieldLabelMap() {
Map<String, String> map = new LinkedHashMap<>();
for (java.lang.reflect.Field field : WmsMaterialCoilAllExportVo.class.getDeclaredFields()) {
com.alibaba.excel.annotation.ExcelProperty ep = field.getAnnotation(com.alibaba.excel.annotation.ExcelProperty.class);
if (ep != null && ep.value().length > 0) {
map.put(field.getName(), ep.value()[0]);
}
}
return map;
}
/**
* 导出钢卷物料表列表(完整字段版本)
* 导出全部字段

View File

@@ -213,5 +213,20 @@ public class WmsMaterialCoil extends BaseEntity {
private Long specId;
private Long versionId;
/**
* 理论厚度(单位:毫米)
*/
private BigDecimal theoreticalThickness;
/**
* 理论长度(单位:米)
*/
private BigDecimal theoreticalLength;
/**
* 镀铬卷号
*/
private String chromePlateCoilNo;
}

View File

@@ -381,5 +381,26 @@ public class WmsMaterialCoilBo extends BaseEntity {
private Long specId;
private Long versionId;
/**
* 理论厚度(单位:毫米)
*/
private BigDecimal theoreticalThickness;
/**
* 理论长度(单位:米)
*/
private BigDecimal theoreticalLength;
/**
* 镀铬卷号
*/
private String chromePlateCoilNo;
/**
* 是否按计划创建时间倒序排序已绑定钢卷列表无planId时按计划新旧排列
*/
@TableField(exist = false)
private Boolean orderByPlanDesc;
}

View File

@@ -208,4 +208,22 @@ public class WmsMaterialCoilExportVo {
*/
@ExcelProperty(value = "调拨类型")
private String transferType;
/**
* 理论厚度(单位:毫米)
*/
@ExcelProperty(value = "理论厚度")
private BigDecimal theoreticalThickness;
/**
* 理论长度(单位:米)
*/
@ExcelProperty(value = "理论长度")
private BigDecimal theoreticalLength;
/**
* 镀铬卷号
*/
@ExcelProperty(value = "镀铬卷号")
private String chromePlateCoilNo;
}

View File

@@ -339,5 +339,20 @@ public class WmsMaterialCoilVo extends BaseEntity {
private Long versionId;
private String specCode;
private String versionCode;
/**
* 理论厚度(单位:毫米)
*/
private BigDecimal theoreticalThickness;
/**
* 理论长度(单位:米)
*/
private BigDecimal theoreticalLength;
/**
* 镀铬卷号
*/
private String chromePlateCoilNo;
}

View File

@@ -45,6 +45,11 @@ public interface WmsMaterialCoilMapper extends BaseMapperPlus<WmsMaterialCoilMap
*/
Page<WmsMaterialCoilVo> selectVoPagePlusOrderBy(Page<Object> build, @Param("ew") QueryWrapper<WmsMaterialCoil> lqw);
/**
* orderByPlanDesc=true 时使用:包含发货计划 join支持按计划创建时间排序
*/
Page<WmsMaterialCoilVo> selectVoPagePlusPlanOrder(Page<Object> build, @Param("ew") QueryWrapper<WmsMaterialCoil> lqw);
List<WmsMaterialCoilVo> selectVoListWithDynamicJoin(@Param("ew")QueryWrapper<WmsMaterialCoil> lqw);
Map<String, Object> selectCountForSpecSync(@Param("ew") QueryWrapper<WmsMaterialCoil> qw);
@@ -91,7 +96,7 @@ public interface WmsMaterialCoilMapper extends BaseMapperPlus<WmsMaterialCoilMap
* @param coilIds 钢卷ID集合
* @return 发货报表导出数据
*/
List<WmsMaterialCoilDeliveryExportVo> selectDeliveryExportListByCoilIds(@Param("coilIds") java.util.Collection<Long> coilIds);
List<WmsMaterialCoilDeliveryExportVo> selectDeliveryExportListByCoilIds(@Param("coilIds") java.util.Collection<Long> coilIds);
/**
* 退火报表导出按钢卷ID列表联查钢卷 + 退火计划 + 退火计划钢卷关系)

View File

@@ -49,7 +49,9 @@ import java.util.stream.Collectors;
public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService {
private static final List<String> STATUS_SEVERITY = java.util.Arrays.asList("normal", "late_warn", "early_warn",
"late_one", "early_one", "late_two", "early_two", "absent_half");
"late_one", "early_one", "late_two", "early_two", "absent_half", "missed_start", "missed_end", "missed");
private static final long LATE_EARLY_MAX_SECONDS = 120 * 60L;
private static final int BATCH_SIZE = 500;
@@ -386,18 +388,91 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
if (hasPeriod2) {
LocalTime p1End = toLocalTime(schedule.getShiftEndTime());
LocalTime p2Start = toLocalTime(schedule.getShiftStartTime2());
LocalTime split = LocalTime.of(
(p1End.getHour() + p2Start.getHour()) / 2,
(p1End.getMinute() + p2Start.getMinute()) / 2);
// 按理论时间范围分割p1End之前、中间区间、p2Start之后
List<AttendanceRecords> beforeGap = new ArrayList<>();
List<AttendanceRecords> inGap = new ArrayList<>();
List<AttendanceRecords> afterGap = new ArrayList<>();
for (AttendanceRecords r : records) {
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
if (!t.isAfter(p1End)) {
beforeGap.add(r);
} else if (!t.isBefore(p2Start)) {
afterGap.add(r);
} else {
inGap.add(r);
}
}
List<AttendanceRecords> p1Records = new ArrayList<>();
List<AttendanceRecords> p2Records = new ArrayList<>();
for (AttendanceRecords r : records) {
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
if (t.isBefore(split)) {
p1Records.add(r);
} else {
p2Records.add(r);
boolean lunchPatternFound = false;
// 检测[p1End, p2Start]区间内是否有午休打卡(取最远两条判断>10分钟按最大连续间隔切开
if (inGap.size() >= 2) {
LocalDateTime firstInGap = toLocalDateTime(inGap.get(0).getChecktime());
LocalDateTime lastInGap = toLocalDateTime(inGap.get(inGap.size() - 1).getChecktime());
if (Duration.between(firstInGap, lastInGap).getSeconds() > 600) {
int splitIdx = 0;
long maxGap = 0;
for (int i = 0; i < inGap.size() - 1; i++) {
LocalDateTime t1 = toLocalDateTime(inGap.get(i).getChecktime());
LocalDateTime t2 = toLocalDateTime(inGap.get(i + 1).getChecktime());
long gap = Duration.between(t1, t2).getSeconds();
if (gap > maxGap) {
maxGap = gap;
splitIdx = i;
}
if (gap > 600) {
break;
}
}
p1Records = new ArrayList<>(beforeGap);
p1Records.addAll(inGap.subList(0, splitIdx + 1));
p2Records = new ArrayList<>(inGap.subList(splitIdx + 1, inGap.size()));
p2Records.addAll(afterGap);
lunchPatternFound = true;
}
}
// 检测边界beforeGap最后一条 与 inGap第一条 间隔>10分钟提前下班+午饭回来)
if (!lunchPatternFound && !beforeGap.isEmpty() && !inGap.isEmpty()) {
LocalDateTime lastBefore = toLocalDateTime(beforeGap.get(beforeGap.size() - 1).getChecktime());
LocalDateTime firstIn = toLocalDateTime(inGap.get(0).getChecktime());
if (Duration.between(lastBefore, firstIn).getSeconds() > 600) {
p1Records = new ArrayList<>(beforeGap);
p2Records = new ArrayList<>(inGap);
p2Records.addAll(afterGap);
lunchPatternFound = true;
}
}
// 检测边界inGap最后一条 与 afterGap第一条 间隔>10分钟午休晚归
if (!lunchPatternFound && !inGap.isEmpty() && !afterGap.isEmpty()) {
LocalDateTime lastIn = toLocalDateTime(inGap.get(inGap.size() - 1).getChecktime());
LocalDateTime firstAfter = toLocalDateTime(afterGap.get(0).getChecktime());
if (Duration.between(lastIn, firstAfter).getSeconds() > 600) {
p1Records = new ArrayList<>(beforeGap);
p1Records.addAll(inGap);
p2Records = new ArrayList<>(afterGap);
lunchPatternFound = true;
}
}
if (!lunchPatternFound) {
// 没有午休打卡模式,回退原中值法
LocalTime split = LocalTime.of(
(p1End.getHour() + p2Start.getHour()) / 2,
(p1End.getMinute() + p2Start.getMinute()) / 2);
p1Records = new ArrayList<>();
p2Records = new ArrayList<>();
for (AttendanceRecords r : records) {
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
if (t.isBefore(split)) {
p1Records.add(r);
} else {
p2Records.add(r);
}
}
}
@@ -457,6 +532,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period,
List<AttendanceRecords> periodRecords, Date expectedStart, Date expectedEnd) {
// 旷工0条打卡记录
if (periodRecords.isEmpty()) {
if (period == 1) {
check.setP1Status("missed");
@@ -477,53 +553,72 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
int earlyMinutes = 0;
BigDecimal deduct = BigDecimal.ZERO;
String status = "normal";
boolean startMissed = false;
boolean endMissed = false;
if (expStart != null && firstCheck.isAfter(expStart)) {
lateMinutes = (int) Duration.between(expStart, firstCheck).toMinutes();
if (lateMinutes > rule.getAbsentHalfDay()) {
status = "absent_half";
} else if (lateMinutes > rule.getLateOne()) {
status = "late_two";
deduct = deduct.add(rule.getDeductTwo());
} else if (lateMinutes > rule.getLateWarn()) {
status = "late_one";
deduct = deduct.add(rule.getDeductOne());
} else {
status = "late_warn";
// 上班检测:晚于理论上班时间俩小时以上 → 漏打卡
if (expStart != null) {
long lateSecs = Duration.between(expStart, firstCheck).getSeconds();
if (lateSecs > LATE_EARLY_MAX_SECONDS) {
startMissed = true;
} else if (lateSecs > 0) {
lateMinutes = (int) (lateSecs / 60);
if (lateMinutes > rule.getAbsentHalfDay()) {
status = "absent_half";
} else if (lateMinutes > rule.getLateOne()) {
status = "late_two";
deduct = deduct.add(rule.getDeductTwo());
} else if (lateMinutes > rule.getLateWarn()) {
status = "late_one";
deduct = deduct.add(rule.getDeductOne());
} else {
status = "late_warn";
}
}
}
// 下班检测:早于理论下班时间俩小时以上 → 漏打卡
if (expEnd != null && lastCheck.isBefore(expEnd)) {
int min = (int) Duration.between(lastCheck, expEnd).toMinutes();
if (min > rule.getAbsentHalfDay()) {
status = maxSeverity(status, "absent_half");
earlyMinutes = min;
} else if (min > rule.getLateOne()) {
status = maxSeverity(status, "early_two");
deduct = deduct.add(rule.getDeductTwo());
earlyMinutes = min;
} else if (min > rule.getLateWarn()) {
status = maxSeverity(status, "early_one");
deduct = deduct.add(rule.getDeductOne());
earlyMinutes = min;
} else {
if ("normal".equals(status)) {
status = "early_warn";
long earlySecs = Duration.between(lastCheck, expEnd).getSeconds();
if (earlySecs > LATE_EARLY_MAX_SECONDS) {
endMissed = true;
} else if (earlySecs > 0) {
earlyMinutes = (int) (earlySecs / 60);
if (earlyMinutes > rule.getAbsentHalfDay()) {
status = maxSeverity(status, "absent_half");
} else if (earlyMinutes > rule.getLateOne()) {
status = maxSeverity(status, "early_two");
deduct = deduct.add(rule.getDeductTwo());
} else if (earlyMinutes > rule.getLateWarn()) {
status = maxSeverity(status, "early_one");
deduct = deduct.add(rule.getDeductOne());
} else {
if ("normal".equals(status)) {
status = "early_warn";
}
}
earlyMinutes = min;
}
}
// 上班漏打卡或下班漏打卡会覆盖原有状态
if (startMissed && endMissed) {
status = "missed";
} else if (startMissed) {
status = maxSeverity(status, "missed_start");
} else if (endMissed) {
status = maxSeverity(status, "missed_end");
}
if (period == 1) {
check.setP1FirstCheck(firstRec.getChecktime());
check.setP1LastCheck(lastRec.getChecktime());
check.setP1FirstCheck(startMissed ? null : firstRec.getChecktime());
check.setP1LastCheck(endMissed ? null : lastRec.getChecktime());
check.setP1LateMinutes(lateMinutes);
check.setP1EarlyMinutes(earlyMinutes);
check.setP1Status(status);
check.setP1Deduct(deduct);
} else {
check.setP2FirstCheck(firstRec.getChecktime());
check.setP2LastCheck(lastRec.getChecktime());
check.setP2FirstCheck(startMissed ? null : firstRec.getChecktime());
check.setP2LastCheck(endMissed ? null : lastRec.getChecktime());
check.setP2LateMinutes(lateMinutes);
check.setP2EarlyMinutes(earlyMinutes);
check.setP2Status(status);
@@ -549,19 +644,27 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
if ("absent_half".equals(check.getP1Status()) || "absent_half".equals(check.getP2Status())) {
hasAbsentHalf = true;
}
if ("missed".equals(check.getP1Status())) {
if (check.getP2StartTime() != null) {
if ("missed".equals(check.getP2Status())) {
check.setAbsentType("full_day");
check.setOverallStatus("absent_full");
return;
}
boolean p1Missed = "missed".equals(check.getP1Status());
boolean p2Missed = check.getP2Status() != null && "missed".equals(check.getP2Status());
boolean hasP2 = check.getP2StartTime() != null;
if (p1Missed) {
if (hasP2 && !p2Missed) {
check.setAbsentType("half_day");
check.setOverallStatus("absent_half");
return;
} else {
check.setAbsentType("full_day");
check.setOverallStatus("absent_full");
return;
}
}
if (p2Missed) {
check.setAbsentType("half_day");
check.setOverallStatus("absent_half");
return;
}
if (check.getP1StartTime() == null && check.getP2StartTime() == null) {
check.setAbsentType("full_day");
check.setOverallStatus("absent_full");

View File

@@ -41,6 +41,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
import java.util.stream.Collectors;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.io.IOException;
import java.net.URLEncoder;
@@ -486,7 +487,9 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
private Page<WmsMaterialCoilVo> queryMaterialCoilPage(WmsMaterialCoilBo bo, PageQuery pageQuery) {
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
Page<WmsMaterialCoilVo> result;
if (Boolean.TRUE.equals(bo.getOrderBy())) {
if (Boolean.TRUE.equals(bo.getOrderByPlanDesc())) {
result = baseMapper.selectVoPagePlusPlanOrder(pageQuery.build(), qw);
} else if (Boolean.TRUE.equals(bo.getOrderBy())) {
result = baseMapper.selectVoPagePlusOrderBy(pageQuery.build(), qw);
} else {
result = baseMapper.selectVoPagePlus(pageQuery.build(), qw);
@@ -697,6 +700,12 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
qw.eq(bo.getActualWidth() != null, "mc.actual_width", bo.getActualWidth());
// 实测厚度
qw.eq(StringUtils.isNotBlank(bo.getActualThickness()), "mc.actual_thickness", bo.getActualThickness());
// 理论厚度
qw.eq(bo.getTheoreticalThickness() != null, "mc.theoretical_thickness", bo.getTheoreticalThickness());
// 理论长度
qw.eq(bo.getTheoreticalLength() != null, "mc.theoretical_length", bo.getTheoreticalLength());
// 镀铬卷号
qw.like(StringUtils.isNotBlank(bo.getChromePlateCoilNo()), "mc.chrome_plate_coil_no", bo.getChromePlateCoilNo());
// 生产开始时间
qw.eq(bo.getProductionStartTime() != null, "mc.production_start_time", bo.getProductionStartTime());
// 生产结束时间
@@ -926,6 +935,10 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
qw.apply("COALESCE(ca.abnormal_count, 0) >= {0}", bo.getMinAbnormalCount());
}
// 排序:
// 按计划创建时间倒序已绑定钢卷列表无planId时优先按计划排序
if (Boolean.TRUE.equals(bo.getOrderByPlanDesc())) {
qw.orderByDesc("pl.create_time");
}
// 已绑定钢卷列表中,未发货(status=0)的排在前面
if (Boolean.TRUE.equals(bo.getStatusFirst())) {
qw.orderByAsc("mc.status = 1");
@@ -1028,6 +1041,134 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
return '(' + String.join(" OR ", parts) + ')';
}
/**
* 根据净重(吨)、规格厚度和宽度自动计算理论厚度(mm)和理论长度(m)
* 理论厚度 = 净重(吨) × 1000 / 7.85 / 实测长度(mm) / 宽度(mm) * 1000
* 理论长度 = 净重(吨) × 1000 / 7.85 / 厚度(mm) / 宽度(mm) * 1000
*/
private void calculateTheoretical(WmsMaterialCoilBo bo) {
if (bo.getNetWeight() == null) {
return;
}
if (StringUtils.isBlank(bo.getItemType()) || bo.getItemId() == null) {
return;
}
String specThickness = null;
String specWidth = null;
try {
String specification = null;
if ("raw_material".equals(bo.getItemType())) {
WmsRawMaterial rm = rawMaterialMapper.selectById(bo.getItemId());
if (rm != null) {
specification = rm.getSpecification();
}
} else if ("product".equals(bo.getItemType())) {
WmsProduct p = productMapper.selectById(bo.getItemId());
if (p != null) {
specification = p.getSpecification();
}
}
if (StringUtils.isNotBlank(specification)) {
String[] parts = specification.split("\\*");
if (parts.length >= 2) {
specThickness = parts[0].trim();
specWidth = parts[parts.length - 1].trim();
}
}
} catch (Exception e) {
log.warn("查询物品规格失败, itemType: {}, itemId: {}", bo.getItemType(), bo.getItemId(), e);
return;
}
if (StringUtils.isBlank(specThickness) || StringUtils.isBlank(specWidth)) {
return;
}
try {
BigDecimal weight = bo.getNetWeight();
BigDecimal thickness = new BigDecimal(specThickness);
BigDecimal width = new BigDecimal(specWidth);
BigDecimal volume = weight.multiply(new BigDecimal("1000")).divide(new BigDecimal("7.85"), 10, RoundingMode.HALF_UP);
// 计算理论厚度(需要实测长度)
if (bo.getTheoreticalThickness() == null && bo.getActualLength() != null && bo.getActualWidth().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal length = new BigDecimal(bo.getActualLength());
BigDecimal theoreticalThickness = volume.divide(length, 10, RoundingMode.HALF_UP).divide(width, 10, RoundingMode.HALF_UP).multiply(new BigDecimal("1000"));
bo.setTheoreticalThickness(theoreticalThickness);
}
// 计算理论长度
if (bo.getTheoreticalLength() == null) {
BigDecimal theoreticalLength = volume.divide(thickness, 10, RoundingMode.HALF_UP).divide(width, 10, RoundingMode.HALF_UP).multiply(new BigDecimal("1000"));
bo.setTheoreticalLength(theoreticalLength);
}
} catch (Exception e) {
log.warn("计算理论厚度/长度失败", e);
}
}
/**
* 根据物品类型和ID获取规格中的宽度规格格式厚度*宽度,取 * 后面部分)
*/
private BigDecimal getSpecWidth(String itemType, Long itemId) {
if (StringUtils.isBlank(itemType) || itemId == null) {
return null;
}
try {
String specification = null;
if ("raw_material".equals(itemType)) {
WmsRawMaterial rm = rawMaterialMapper.selectById(itemId);
if (rm != null) {
specification = rm.getSpecification();
}
} else if ("product".equals(itemType)) {
WmsProduct p = productMapper.selectById(itemId);
if (p != null) {
specification = p.getSpecification();
}
}
if (StringUtils.isNotBlank(specification)) {
String[] parts = specification.split("\\*");
if (parts.length >= 2) {
return new BigDecimal(parts[parts.length - 1].trim());
}
}
} catch (Exception e) {
log.warn("获取规格宽度失败, itemType: {}, itemId: {}", itemType, itemId, e);
}
return null;
}
/**
* 根据物品类型和ID获取规格中的厚度规格格式厚度*宽度,取 * 前面部分)
*/
private BigDecimal getSpecThickness(String itemType, Long itemId) {
if (StringUtils.isBlank(itemType) || itemId == null) {
return null;
}
try {
String specification = null;
if ("raw_material".equals(itemType)) {
WmsRawMaterial rm = rawMaterialMapper.selectById(itemId);
if (rm != null) {
specification = rm.getSpecification();
}
} else if ("product".equals(itemType)) {
WmsProduct p = productMapper.selectById(itemId);
if (p != null) {
specification = p.getSpecification();
}
}
if (StringUtils.isNotBlank(specification)) {
String[] parts = specification.split("\\*");
if (parts.length >= 2) {
return new BigDecimal(parts[0].trim());
}
}
} catch (Exception e) {
log.warn("获取规格厚度失败, itemType: {}, itemId: {}", itemType, itemId, e);
}
return null;
}
/**
* 获取库位及其所有子库位的ID列表
* 逻辑参考 WmsActualWarehouseServiceImpl.queryList 中的拆分处理逻辑:
@@ -1333,6 +1474,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
bo.setActualWarehouseId(null);
}
// 3. 插入钢卷数据
calculateTheoretical(bo);
WmsMaterialCoil add = BeanUtil.toBean(bo, WmsMaterialCoil.class);
if(bo.getDataType() != null && bo.getDataType() == 10){
add.setDataType(10);
@@ -1548,6 +1690,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
}
}
validateCoilWeight(bo.getGrossWeight(), bo.getNetWeight());
calculateTheoretical(bo);
// 直接更新钢卷属性
WmsMaterialCoil updateCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
validEntityBeforeSave(updateCoil);
@@ -1641,6 +1784,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
baseMapper.update(null, updateWrapper);
// 2. 创建新记录
calculateTheoretical(bo);
WmsMaterialCoil newCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
newCoil.setCoilId(null); // 清空ID让数据库自动生成新ID
newCoil.setDataType(1); // 设置为当前数据
@@ -1888,6 +2032,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
? newCoilBo.getActualWarehouseId() : null;
validateActualWarehouseForAssign(newCoilBo.getActualWarehouseId(), ignoreOccupiedId);
}
calculateTheoretical(newCoilBo);
WmsMaterialCoil newCoil = BeanUtil.toBean(newCoilBo, WmsMaterialCoil.class);
newCoil.setCoilId(null);
newCoil.setDataType(1);
@@ -2016,6 +2161,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
if (bo.getActualWarehouseId() != null) {
validateActualWarehouseForAssign(bo.getActualWarehouseId(), null);
}
calculateTheoretical(bo);
WmsMaterialCoil newCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
newCoil.setCoilId(null);
newCoil.setDataType(1);
@@ -4854,8 +5000,27 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
if (childCoilBo.getActualWarehouseId() != null) {
validateActualWarehouseForAssign(childCoilBo.getActualWarehouseId(), null);
}
if (pendingAction.getActionType() != 501 && childCoilBo.getActionType() != null) {
// 校验子卷净重不超过母卷
if (childCoilBo.getNetWeight() != null && parentCoil.getNetWeight() != null) {
if (childCoilBo.getNetWeight().compareTo(parentCoil.getNetWeight()) > 0) {
throw new RuntimeException("子卷净重[" + childCoilBo.getNetWeight() + "]不能超过母卷净重[" + parentCoil.getNetWeight() + "]");
}
}
// 校验子卷规格厚度不超过母卷
String childItemType = StringUtils.isNotBlank(childCoilBo.getItemType()) ? childCoilBo.getItemType() : parentCoil.getItemType();
Long childItemId = childCoilBo.getItemId() != null ? childCoilBo.getItemId() : parentCoil.getItemId();
BigDecimal parentSpecThickness = getSpecThickness(parentCoil.getItemType(), parentCoil.getItemId());
BigDecimal childSpecThickness = getSpecThickness(childItemType, childItemId);
if (parentSpecThickness != null && childSpecThickness != null) {
if (childSpecThickness.compareTo(parentSpecThickness) > 0) {
throw new RuntimeException("子卷规格厚度[" + childSpecThickness + "]不能超过母卷规格厚度[" + parentSpecThickness + "]");
}
}
}
// 5. 创建子钢卷(参考普通分卷逻辑,但不设置母卷为历史数据)
calculateTheoretical(childCoilBo);
WmsMaterialCoil childCoil = BeanUtil.toBean(childCoilBo, WmsMaterialCoil.class);
childCoil.setCoilId(null);
childCoil.setDataType(1); // 当前数据
@@ -4971,6 +5136,19 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
.map(WmsMaterialCoil::getCurrentCoilNo)
.collect(Collectors.toList());
if (pendingAction.getActionType() != 501) {
// 校验所有子卷总重不超过母卷净重
if (parentCoil.getNetWeight() != null) {
BigDecimal totalChildWeight = childCoils.stream()
.map(WmsMaterialCoil::getNetWeight)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (totalChildWeight.compareTo(parentCoil.getNetWeight()) > 0) {
throw new RuntimeException("所有子卷总重[" + totalChildWeight + "]不能超过母卷净重[" + parentCoil.getNetWeight() + "]");
}
}
}
// 如果母卷即将成为历史卷 证明已经加工完成则释放库位
if (parentCoil.getActualWarehouseId() != null){
updateActualWarehouseEnableStatus(parentCoil.getActualWarehouseId(), null);

View File

@@ -42,6 +42,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="transferType" column="transfer_type"/>
<result property="specId" column="spec_id"/>
<result property="versionId" column="version_id"/>
<result property="theoreticalThickness" column="theoretical_thickness"/>
<result property="theoreticalLength" column="theoretical_length"/>
<result property="chromePlateCoilNo" column="chrome_plate_coil_no"/>
</resultMap>
@@ -79,87 +82,91 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
GROUP BY w.warehouse_id, w.warehouse_name
ORDER BY w.warehouse_id
</select>
<select id="selectVoPagePlus" resultType="com.klp.domain.vo.WmsMaterialCoilVo">
SELECT
mc.coil_id,
mc.parent_coil_id,
mc.export_time,
mc.export_by,
mc.enter_coil_no,
mc.current_coil_no,
mc.supplier_coil_no,
mc.data_type,
mc.material_type,
mc.next_warehouse_id,
mc.qrcode_record_id,
mc.team,
mc.has_merge_split,
mc.parent_coil_nos,
mc.item_type,
mc.item_id,
mc.gross_weight,
mc.net_weight,
mc.status,
mc.remark,
mc.warehouse_id,
mc.actual_warehouse_id,
mc.del_flag,
mc.create_time,
mc.update_time,
mc.create_by,
mc.update_by,
mc.quality_status,
mc.trimming_requirement,
mc.packaging_requirement,
mc.packing_status,
mc.sale_id AS saleId,
mc.length,
mc.actual_length,
mc.actual_width,
mc.actual_thickness,
mc.production_start_time,
mc.production_end_time,
mc.production_duration,
mc.coil_surface_treatment,
mc.reserved_width,
mc.coating_type,
mc.temper_grade,
mc.business_purpose,
mc.is_related_to_order,
mc.exclusive_status,
mc.transfer_type,
mc.sale_name AS saleName,
w.warehouse_name AS warehouseName,
nw.warehouse_name AS nextWarehouseName,
aw.actual_warehouse_name AS actualWarehouseName,
CASE WHEN mc.item_type = 'raw_material' THEN rm.specification
WHEN mc.item_type = 'product' THEN p.specification
ELSE NULL END AS specification,
CASE WHEN mc.item_type = 'raw_material' THEN rm.material
WHEN mc.item_type = 'product' THEN p.material
ELSE NULL END AS material,
CASE WHEN mc.item_type = 'raw_material' THEN rm.manufacturer
WHEN mc.item_type = 'product' THEN p.manufacturer
ELSE NULL END AS manufacturer,
CASE WHEN mc.item_type = 'raw_material' THEN rm.surface_treatment_desc
WHEN mc.item_type = 'product' THEN p.surface_treatment_desc
ELSE NULL END AS surfaceTreatmentDesc,
CASE WHEN mc.item_type = 'raw_material' THEN rm.zinc_layer
WHEN mc.item_type = 'product' THEN p.zinc_layer
ELSE NULL END AS zincLayer,
-- 物品名称和编号(用于兼容)
CASE
WHEN mc.item_type = 'raw_material' THEN rm.raw_material_name
WHEN mc.item_type = 'product' THEN p.product_name
ELSE NULL
END as itemName,
CASE
WHEN mc.item_type = 'raw_material' THEN rm.raw_material_code
WHEN mc.item_type = 'product' THEN p.product_code
ELSE NULL
END as itemCode,
-- 异常数量统计
COALESCE(ca.abnormal_count, 0) AS abnormalCount
<!-- ==================== 公共 SQL 片段 ==================== -->
<sql id="baseColumns">
mc.coil_id,
mc.parent_coil_id,
mc.export_time,
mc.export_by,
mc.enter_coil_no,
mc.current_coil_no,
mc.supplier_coil_no,
mc.data_type,
mc.material_type,
mc.next_warehouse_id,
mc.qrcode_record_id,
mc.team,
mc.has_merge_split,
mc.parent_coil_nos,
mc.item_type,
mc.item_id,
mc.gross_weight,
mc.net_weight,
mc.status,
mc.remark,
mc.warehouse_id,
mc.actual_warehouse_id,
mc.del_flag,
mc.create_time,
mc.update_time,
mc.create_by,
mc.update_by,
mc.quality_status,
mc.trimming_requirement,
mc.packaging_requirement,
mc.packing_status,
mc.sale_id AS saleId,
mc.length,
mc.actual_length,
mc.actual_width,
mc.actual_thickness,
mc.production_start_time,
mc.production_end_time,
mc.production_duration,
mc.coil_surface_treatment,
mc.reserved_width,
mc.coating_type,
mc.temper_grade,
mc.business_purpose,
mc.is_related_to_order,
mc.exclusive_status,
mc.transfer_type,
mc.theoretical_thickness,
mc.theoretical_length,
mc.chrome_plate_coil_no,
mc.sale_name AS saleName,
w.warehouse_name AS warehouseName,
nw.warehouse_name AS nextWarehouseName,
aw.actual_warehouse_name AS actualWarehouseName,
CASE WHEN mc.item_type = 'raw_material' THEN rm.specification
WHEN mc.item_type = 'product' THEN p.specification
ELSE NULL END AS specification,
CASE WHEN mc.item_type = 'raw_material' THEN rm.material
WHEN mc.item_type = 'product' THEN p.material
ELSE NULL END AS material,
CASE WHEN mc.item_type = 'raw_material' THEN rm.manufacturer
WHEN mc.item_type = 'product' THEN p.manufacturer
ELSE NULL END AS manufacturer,
CASE WHEN mc.item_type = 'raw_material' THEN rm.surface_treatment_desc
WHEN mc.item_type = 'product' THEN p.surface_treatment_desc
ELSE NULL END AS surfaceTreatmentDesc,
CASE WHEN mc.item_type = 'raw_material' THEN rm.zinc_layer
WHEN mc.item_type = 'product' THEN p.zinc_layer
ELSE NULL END AS zincLayer,
CASE
WHEN mc.item_type = 'raw_material' THEN rm.raw_material_name
WHEN mc.item_type = 'product' THEN p.product_name
ELSE NULL
END as itemName,
CASE
WHEN mc.item_type = 'raw_material' THEN rm.raw_material_code
WHEN mc.item_type = 'product' THEN p.product_code
ELSE NULL
END as itemCode,
COALESCE(ca.abnormal_count, 0) AS abnormalCount
</sql>
<sql id="baseFrom">
FROM wms_material_coil mc
LEFT JOIN wms_warehouse w ON mc.warehouse_id = w.warehouse_id
LEFT JOIN wms_warehouse nw ON mc.next_warehouse_id = nw.warehouse_id
@@ -172,111 +179,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE del_flag = 0
GROUP BY coil_id
) ca ON mc.coil_id = ca.coil_id
</sql>
<!-- ==================== 各场景查询 ==================== -->
<select id="selectVoPagePlus" resultType="com.klp.domain.vo.WmsMaterialCoilVo">
SELECT <include refid="baseColumns"/>
<include refid="baseFrom"/>
${ew.customSqlSegment}
</select>
<!-- orderBy=true 专用包含库位排序辅助字段aw_sort_key/aw_layer_key/aw_id_key以及父库位 join -->
<select id="selectVoPagePlusOrderBy" resultType="com.klp.domain.vo.WmsMaterialCoilVo">
SELECT
mc.coil_id,
mc.parent_coil_id,
mc.export_time,
mc.export_by,
mc.enter_coil_no,
mc.current_coil_no,
mc.supplier_coil_no,
mc.data_type,
mc.material_type,
mc.next_warehouse_id,
mc.qrcode_record_id,
mc.team,
mc.has_merge_split,
mc.parent_coil_nos,
mc.item_type,
mc.item_id,
mc.gross_weight,
mc.net_weight,
mc.status,
mc.remark,
mc.warehouse_id,
mc.actual_warehouse_id,
mc.del_flag,
mc.create_time,
mc.update_time,
mc.create_by,
mc.update_by,
mc.quality_status,
mc.trimming_requirement,
mc.packaging_requirement,
mc.packing_status,
mc.sale_id AS saleId,
mc.length,
mc.actual_length,
mc.actual_width,
mc.actual_thickness,
mc.production_start_time,
mc.production_end_time,
mc.production_duration,
mc.reserved_width,
mc.coating_type,
mc.temper_grade,
mc.business_purpose,
mc.is_related_to_order,
mc.exclusive_status,
mc.transfer_type,
mc.sale_name AS saleName,
su.nick_name AS saleNickName,
w.warehouse_name AS warehouseName,
nw.warehouse_name AS nextWarehouseName,
aw.actual_warehouse_name AS actualWarehouseName,
CASE WHEN mc.item_type = 'raw_material' THEN rm.specification
WHEN mc.item_type = 'product' THEN p.specification
ELSE NULL END AS specification,
CASE WHEN mc.item_type = 'raw_material' THEN rm.material
WHEN mc.item_type = 'product' THEN p.material
ELSE NULL END AS material,
CASE WHEN mc.item_type = 'raw_material' THEN rm.manufacturer
WHEN mc.item_type = 'product' THEN p.manufacturer
ELSE NULL END AS manufacturer,
CASE WHEN mc.item_type = 'raw_material' THEN rm.surface_treatment_desc
WHEN mc.item_type = 'product' THEN p.surface_treatment_desc
ELSE NULL END AS surfaceTreatmentDesc,
CASE WHEN mc.item_type = 'raw_material' THEN rm.zinc_layer
WHEN mc.item_type = 'product' THEN p.zinc_layer
ELSE NULL END AS zincLayer,
-- 物品名称和编号(用于兼容)
CASE
WHEN mc.item_type = 'raw_material' THEN rm.raw_material_name
WHEN mc.item_type = 'product' THEN p.product_name
ELSE NULL
END as itemName,
CASE
WHEN mc.item_type = 'raw_material' THEN rm.raw_material_code
WHEN mc.item_type = 'product' THEN p.product_code
ELSE NULL
END as itemCode,
-- 库位排序辅助字段(用于全局交错排序,避免在 ORDER BY 中写复杂表达式触发 MP 注入拦截)
<include refid="baseColumns"/>,
COALESCE(CASE WHEN aw.actual_warehouse_type = 4 THEN awp.sort_no ELSE aw.sort_no END, 0) AS aw_sort_key,
CASE WHEN aw.actual_warehouse_type = 4 THEN CAST(SUBSTRING_INDEX(aw.actual_warehouse_code, '-', -1) AS UNSIGNED) ELSE 0 END AS aw_layer_key,
COALESCE(aw.actual_warehouse_id, 0) AS aw_id_key,
-- 异常数量统计
COALESCE(ca.abnormal_count, 0) AS abnormalCount
FROM wms_material_coil mc
LEFT JOIN wms_warehouse w ON mc.warehouse_id = w.warehouse_id
LEFT JOIN wms_warehouse nw ON mc.next_warehouse_id = nw.warehouse_id
LEFT JOIN wms_actual_warehouse aw ON mc.actual_warehouse_id = aw.actual_warehouse_id
LEFT JOIN wms_actual_warehouse awp ON aw.parent_id = awp.actual_warehouse_id
LEFT JOIN sys_user su ON mc.sale_id = su.user_id
LEFT JOIN wms_raw_material rm ON mc.item_type = 'raw_material' AND mc.item_id = rm.raw_material_id
LEFT JOIN wms_product p ON mc.item_type = 'product' AND mc.item_id = p.product_id
LEFT JOIN (
SELECT coil_id, COUNT(*) AS abnormal_count
FROM wms_coil_abnormal
WHERE del_flag = 0
GROUP BY coil_id
) ca ON mc.coil_id = ca.coil_id
${ew.customSqlSegment}
COALESCE(aw.actual_warehouse_id, 0) AS aw_id_key
<include refid="baseFrom"/>
LEFT JOIN wms_actual_warehouse awp ON aw.parent_id = awp.actual_warehouse_id
${ew.customSqlSegment}
</select>
<!-- orderByPlanDesc=true 专用:包含发货计划 join支持按计划创建时间倒序排序 -->
<select id="selectVoPagePlusPlanOrder" resultType="com.klp.domain.vo.WmsMaterialCoilVo">
SELECT <include refid="baseColumns"/>
<include refid="baseFrom"/>
LEFT JOIN wms_delivery_waybill_detail d ON d.coil_id = mc.coil_id AND d.del_flag = 0
LEFT JOIN wms_delivery_waybill wb ON wb.waybill_id = d.waybill_id AND wb.del_flag = 0
LEFT JOIN wms_delivery_plan pl ON pl.plan_id = wb.plan_id AND pl.del_flag = 0
${ew.customSqlSegment}
</select>
@@ -554,6 +486,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
mc.sale_name AS saleName,
-- 调拨类型
mc.transfer_type AS transferType,
-- 理论厚度
mc.theoretical_thickness AS theoreticalThickness,
-- 理论长度
mc.theoretical_length AS theoreticalLength,
-- 镀铬卷号
mc.chrome_plate_coil_no AS chromePlateCoilNo,
-- 库存状态(中文显示)
CASE
WHEN mc.status = 0 THEN '在库'
@@ -641,6 +579,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
mc.packing_status AS packingStatus,
mc.sale_name AS saleName,
mc.transfer_type AS transferType,
mc.theoretical_thickness AS theoreticalThickness,
mc.theoretical_length AS theoreticalLength,
mc.chrome_plate_coil_no AS chromePlateCoilNo,
CASE
WHEN mc.status = 0 THEN '在库'
WHEN mc.status = 1 THEN '已发货'
@@ -759,6 +700,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
mc.packing_status AS packingStatus,
mc.sale_name AS saleName,
mc.transfer_type AS transferType,
mc.theoretical_thickness AS theoreticalThickness,
mc.theoretical_length AS theoreticalLength,
mc.chrome_plate_coil_no AS chromePlateCoilNo,
CASE
WHEN mc.status = 0 THEN '在库'
WHEN mc.status = 1 THEN '已发货'