小料需求计划

This commit is contained in:
2026-06-29 13:33:13 +08:00
parent 816af5df6e
commit 15cc5c2879
5 changed files with 331 additions and 13 deletions

View File

@@ -95,18 +95,23 @@ public class MesXslRawMaterialDemandPlanController {
private String buildGroupedSql(
MesXslRawMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String planDateExpr = "DATE(t.plan_date)";
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String materialIdExpr = "COALESCE(NULLIF(TRIM(t.material_id),''), '')";
String erpCodeExpr = "COALESCE(NULLIF(TRIM(t.erp_code),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
sql.append(planDateExpr).append(" AS planDate, ");
if (groupedByMachine) {
sql.append(machineExpr).append(" AS machineName, ");
} else {
sql.append("'' AS machineName, ");
}
sql.append(erpCodeExpr)
sql.append(materialIdExpr)
.append(" AS materialId, ")
.append(erpCodeExpr)
.append(" AS erpCode, ")
.append(rawMaterialExpr)
.append(" AS rawMaterialName, ")
@@ -128,12 +133,16 @@ public class MesXslRawMaterialDemandPlanController {
sql.append("AND ").append(machineExpr).append(" LIKE ? ");
params.add("%" + query.getMachineName().trim() + "%");
}
if (query != null && query.getPlanDate() != null) {
sql.append("AND ").append(planDateExpr).append(" = ? ");
params.add(new java.sql.Date(query.getPlanDate().getTime()));
}
sql.append("GROUP BY ");
sql.append("GROUP BY ").append(planDateExpr).append(", ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
sql.append(erpCodeExpr).append(", ").append(rawMaterialExpr);
sql.append(materialIdExpr).append(", ").append(erpCodeExpr).append(", ").append(rawMaterialExpr);
return sql.toString();
}
@@ -146,7 +155,9 @@ public class MesXslRawMaterialDemandPlanController {
int seq = 1;
while (rs.next()) {
MesXslRawMaterialDemandPlanSummary row = new MesXslRawMaterialDemandPlanSummary();
row.setPlanDate(rs.getDate("planDate"));
row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : "");
row.setMaterialId(trim(rs.getString("materialId")));
row.setErpCode(trim(rs.getString("erpCode")));
row.setRawMaterialName(trim(rs.getString("rawMaterialName")));
row.setDemandWeight(rs.getBigDecimal("demandWeight"));
@@ -162,14 +173,20 @@ public class MesXslRawMaterialDemandPlanController {
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName, erpCode";
return " ORDER BY planDate DESC, machineName, rawMaterialName, erpCode";
}
return " ORDER BY rawMaterialName, erpCode";
return " ORDER BY planDate DESC, rawMaterialName, erpCode";
}
private String buildRowId(MesXslRawMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getErpCode()) + "_" + seq;
return String.valueOf(row.getPlanDate())
+ "_"
+ machine
+ "_"
+ StringUtils.defaultString(row.getMaterialId())
+ "_"
+ seq;
}
private String trim(String value) {

View File

@@ -3,8 +3,10 @@ package org.jeecg.modules.xslmes.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 原材料需求计划汇总(按原材料或按机台+原材料)
@@ -22,6 +24,14 @@ public class MesXslRawMaterialDemandPlanSummary implements Serializable {
@Schema(description = "机台")
private String machineName;
@Excel(name = "计划日期", width = 14, format = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "计划日期")
private Date planDate;
@Schema(description = "物料ID(密炼物料信息ID)")
private String materialId;
@Excel(name = "ERP编号", width = 18)
@Schema(description = "ERP编号")
private String erpCode;

View File

@@ -2,8 +2,10 @@ package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.math.BigDecimal;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -18,19 +20,24 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.mes.material.entity.MesMaterial;
import org.jeecg.modules.mes.material.entity.MesMixerMaterial;
import org.jeecg.modules.mes.material.mapper.MesMaterialMapper;
import org.jeecg.modules.mes.material.mapper.MesMixerMaterialMapper;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger;
import org.jeecg.modules.xslmes.entity.MesXslFinalBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslMasterBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpec;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecMaterial;
import org.jeecg.modules.xslmes.mapper.MesXslEquipmentLedgerMapper;
import org.jeecg.modules.xslmes.mapper.MesXslFinalBatchPlanMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMasterBatchPlanMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingProductionPlanMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMaterialMapper;
import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -45,19 +52,28 @@ public class MesXslMixingProductionPlanServiceImpl
private final MesXslMasterBatchPlanMapper masterBatchPlanMapper;
private final MesXslFinalBatchPlanMapper finalBatchPlanMapper;
private final MesXslMixingSpecMapper mixingSpecMapper;
private final MesXslMixingSpecMaterialMapper mixingSpecMaterialMapper;
private final MesMaterialMapper mesMaterialMapper;
private final MesMixerMaterialMapper mesMixerMaterialMapper;
private final JdbcTemplate jdbcTemplate;
public MesXslMixingProductionPlanServiceImpl(
MesXslEquipmentLedgerMapper equipmentLedgerMapper,
MesXslMasterBatchPlanMapper masterBatchPlanMapper,
MesXslFinalBatchPlanMapper finalBatchPlanMapper,
MesXslMixingSpecMapper mixingSpecMapper,
MesMaterialMapper mesMaterialMapper) {
MesXslMixingSpecMaterialMapper mixingSpecMaterialMapper,
MesMaterialMapper mesMaterialMapper,
MesMixerMaterialMapper mesMixerMaterialMapper,
JdbcTemplate jdbcTemplate) {
this.equipmentLedgerMapper = equipmentLedgerMapper;
this.masterBatchPlanMapper = masterBatchPlanMapper;
this.finalBatchPlanMapper = finalBatchPlanMapper;
this.mixingSpecMapper = mixingSpecMapper;
this.mixingSpecMaterialMapper = mixingSpecMaterialMapper;
this.mesMaterialMapper = mesMaterialMapper;
this.mesMixerMaterialMapper = mesMixerMaterialMapper;
this.jdbcTemplate = jdbcTemplate;
}
@Override
@@ -82,6 +98,211 @@ public class MesXslMixingProductionPlanServiceImpl
if (!saveList.isEmpty()) {
this.saveBatch(saveList, 200);
}
rebuildRawMaterialDemandPlan(saveList);
}
private void rebuildRawMaterialDemandPlan(List<MesXslMixingProductionPlan> savedPlans) {
jdbcTemplate.update("DELETE FROM mes_xsl_raw_material_demand_plan");
if (CollectionUtils.isEmpty(savedPlans)) {
return;
}
List<MesXslMixingProductionPlan> validPlans =
savedPlans.stream()
.filter(Objects::nonNull)
.filter(p -> StringUtils.isNotBlank(p.getFormulaName()))
.filter(p -> p.getPlanDate() != null)
.filter(p -> p.getPlanCount() != null && p.getPlanCount() > 0)
.collect(Collectors.toList());
if (validPlans.isEmpty()) {
return;
}
List<String> formulaNames =
validPlans.stream()
.map(MesXslMixingProductionPlan::getFormulaName)
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (formulaNames.isEmpty()) {
return;
}
List<MesXslMixingSpec> specs =
mixingSpecMapper.selectList(
new LambdaQueryWrapper<MesXslMixingSpec>()
.in(MesXslMixingSpec::getSpecName, formulaNames)
.and(
w ->
w.eq(MesXslMixingSpec::getDelFlag, CommonConstant.DEL_FLAG_0)
.or()
.isNull(MesXslMixingSpec::getDelFlag)));
if (specs.isEmpty()) {
return;
}
Map<String, String> specIdByName = new HashMap<>();
for (MesXslMixingSpec spec : specs) {
if (spec == null || StringUtils.isBlank(spec.getSpecName()) || StringUtils.isBlank(spec.getId())) {
continue;
}
specIdByName.putIfAbsent(spec.getSpecName().trim(), spec.getId());
}
if (specIdByName.isEmpty()) {
return;
}
List<MesXslMixingSpecMaterial> details =
mixingSpecMaterialMapper.selectList(
new LambdaQueryWrapper<MesXslMixingSpecMaterial>()
.in(MesXslMixingSpecMaterial::getMixingSpecId, specIdByName.values())
.ne(MesXslMixingSpecMaterial::getMaterialKind, "胶料"));
if (details.isEmpty()) {
return;
}
Map<String, List<MesXslMixingSpecMaterial>> detailBySpecId =
details.stream()
.filter(Objects::nonNull)
.filter(d -> StringUtils.isNotBlank(d.getMixingSpecId()))
.collect(Collectors.groupingBy(MesXslMixingSpecMaterial::getMixingSpecId));
if (detailBySpecId.isEmpty()) {
return;
}
List<String> mixerMaterialIds =
details.stream()
.map(MesXslMixingSpecMaterial::getMixerMaterialId)
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
Map<String, MesMixerMaterial> mixerMaterialMap = new HashMap<>();
if (!mixerMaterialIds.isEmpty()) {
List<MesMixerMaterial> mixerMaterials =
mesMixerMaterialMapper.selectList(
new LambdaQueryWrapper<MesMixerMaterial>().in(MesMixerMaterial::getId, mixerMaterialIds));
for (MesMixerMaterial mixerMaterial : mixerMaterials) {
if (mixerMaterial != null && StringUtils.isNotBlank(mixerMaterial.getId())) {
mixerMaterialMap.put(mixerMaterial.getId(), mixerMaterial);
}
}
}
Map<String, RawDemandAggregate> aggregateMap = new HashMap<>();
for (MesXslMixingProductionPlan plan : validPlans) {
String specId = specIdByName.get(StringUtils.trimToEmpty(plan.getFormulaName()));
if (StringUtils.isBlank(specId)) {
continue;
}
List<MesXslMixingSpecMaterial> specDetailList = detailBySpecId.get(specId);
if (CollectionUtils.isEmpty(specDetailList)) {
continue;
}
BigDecimal planCount = BigDecimal.valueOf(plan.getPlanCount());
for (MesXslMixingSpecMaterial detail : specDetailList) {
if (detail == null || detail.getUnitWeight() == null) {
continue;
}
if ("胶料".equals(StringUtils.trimToEmpty(detail.getMaterialKind()))) {
continue;
}
MesMixerMaterial mixerMaterial =
mixerMaterialMap.get(StringUtils.trimToNull(detail.getMixerMaterialId()));
String erpCode =
StringUtils.defaultIfBlank(
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getErpCode()),
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getMaterialCode()));
String rawMaterialName =
StringUtils.defaultIfBlank(
StringUtils.trimToNull(detail.getMixerMaterialName()),
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getMaterialName()));
String mixerMaterialId =
StringUtils.defaultIfBlank(
StringUtils.trimToNull(detail.getMixerMaterialId()),
mixerMaterial == null ? null : StringUtils.trimToNull(mixerMaterial.getId()));
if (StringUtils.isBlank(rawMaterialName)) {
continue;
}
BigDecimal demandWeight = detail.getUnitWeight().multiply(planCount);
String planDateKey = plan.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString();
String key =
String.join(
"|",
planDateKey,
StringUtils.defaultString(mixerMaterialId),
StringUtils.defaultString(plan.getMachineId()),
StringUtils.defaultString(plan.getMachineName()),
StringUtils.defaultString(erpCode),
rawMaterialName);
RawDemandAggregate aggregate =
aggregateMap.computeIfAbsent(
key,
k ->
new RawDemandAggregate(
plan.getPlanDate(),
StringUtils.trimToNull(mixerMaterialId),
StringUtils.trimToNull(plan.getMachineId()),
StringUtils.trimToNull(plan.getMachineName()),
StringUtils.defaultString(erpCode),
rawMaterialName));
aggregate.demandWeight = aggregate.demandWeight.add(demandWeight);
}
}
if (aggregateMap.isEmpty()) {
return;
}
List<Object[]> batchArgs = new ArrayList<>();
Date now = new Date();
for (RawDemandAggregate aggregate : aggregateMap.values()) {
if (aggregate.demandWeight.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
batchArgs.add(
new Object[] {
IdWorker.getIdStr(),
aggregate.machineId,
aggregate.machineName,
aggregate.materialId,
StringUtils.defaultString(aggregate.erpCode),
aggregate.rawMaterialName,
aggregate.demandWeight,
aggregate.demandWeight,
BigDecimal.ZERO,
aggregate.planDate,
now,
now,
CommonConstant.DEL_FLAG_0
});
}
if (batchArgs.isEmpty()) {
return;
}
jdbcTemplate.batchUpdate(
"INSERT INTO mes_xsl_raw_material_demand_plan "
+ "(id, machine_id, machine_name, material_id, erp_code, raw_material_name, demand_weight, standard_weight, actual_weight, plan_date, create_time, update_time, del_flag) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
batchArgs);
}
private static class RawDemandAggregate {
private final Date planDate;
private final String materialId;
private final String machineId;
private final String machineName;
private final String erpCode;
private final String rawMaterialName;
private BigDecimal demandWeight = BigDecimal.ZERO;
private RawDemandAggregate(
Date planDate,
String materialId,
String machineId,
String machineName,
String erpCode,
String rawMaterialName) {
this.planDate = planDate;
this.materialId = materialId;
this.machineId = machineId;
this.machineName = machineName;
this.erpCode = erpCode;
this.rawMaterialName = rawMaterialName;
}
}
private boolean hasBusinessData(MesXslMixingProductionPlan row) {

View File

@@ -0,0 +1,67 @@
-- 原材料需求计划补充物料ID和计划日期用于按日期+物料汇总
SET @ddl_sql = (
SELECT IFNULL(
CONCAT(
'ALTER TABLE `mes_xsl_raw_material_demand_plan` ',
GROUP_CONCAT(stmt SEPARATOR ', ')
),
'SELECT 1'
)
FROM (
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND COLUMN_NAME = 'material_id'
)
THEN 'ADD COLUMN `material_id` varchar(32) DEFAULT NULL COMMENT ''物料ID(密炼物料信息ID)'' AFTER `machine_name`'
ELSE NULL
END AS stmt
UNION ALL
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND COLUMN_NAME = 'plan_date'
)
THEN 'ADD COLUMN `plan_date` date DEFAULT NULL COMMENT ''计划日期'' AFTER `actual_weight`'
ELSE NULL
END AS stmt
UNION ALL
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND INDEX_NAME = 'idx_raw_material_demand_material'
)
THEN 'ADD KEY `idx_raw_material_demand_material` (`material_id`)'
ELSE NULL
END AS stmt
UNION ALL
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'mes_xsl_raw_material_demand_plan'
AND INDEX_NAME = 'idx_raw_material_demand_date'
)
THEN 'ADD KEY `idx_raw_material_demand_date` (`plan_date`)'
ELSE NULL
END AS stmt
) t
WHERE t.stmt IS NOT NULL
);
PREPARE stmt FROM @ddl_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@@ -1,6 +1,7 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
export const baseColumns: BasicColumn[] = [
{ title: '计划日期', align: 'center', dataIndex: 'planDate', width: 130 },
{ title: 'ERP编号', align: 'center', dataIndex: 'erpCode', width: 180 },
{ title: '原材料名称', align: 'center', dataIndex: 'rawMaterialName', width: 220, ellipsis: true },
{ title: '需求重量', align: 'center', dataIndex: 'demandWeight', width: 140 },
@@ -14,15 +15,17 @@ export const machineColumns: BasicColumn[] = [
];
export const searchFormSchema: FormSchema[] = [
{ label: '计划日期', field: 'planDate', component: 'DatePicker', colProps: { span: 6 } },
{ label: 'ERP编号', field: 'erpCode', component: 'JInput', colProps: { span: 6 } },
{ label: '原材料名称', field: 'rawMaterialName', component: 'JInput', colProps: { span: 6 } },
];
export const superQuerySchema = {
machineName: { title: '机台', order: 0, view: 'text' },
erpCode: { title: 'ERP编号', order: 1, view: 'text' },
rawMaterialName: { title: '原材料名称', order: 2, view: 'text' },
demandWeight: { title: '需求重量', order: 3, view: 'number' },
standardWeight: { title: '标准重量', order: 4, view: 'number' },
actualWeight: { title: '实际重量', order: 5, view: 'number' },
planDate: { title: '计划日期', order: 0, view: 'date' },
machineName: { title: '机台', order: 1, view: 'text' },
erpCode: { title: 'ERP编号', order: 2, view: 'text' },
rawMaterialName: { title: '原材料名称', order: 3, view: 'text' },
demandWeight: { title: '需求重量', order: 4, view: 'number' },
standardWeight: { title: '标准重量', order: 5, view: 'number' },
actualWeight: { title: '实际重量', order: 6, view: 'number' },
};