生产环节优化

This commit is contained in:
2026-06-11 10:06:26 +08:00
parent b9be88ae3f
commit 3431cc6b17
32 changed files with 2237 additions and 52 deletions

View File

@@ -0,0 +1,67 @@
---
name: karpathy-guidelines
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
license: MIT
---
# Karpathy Guidelines
Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

View File

@@ -0,0 +1,79 @@
-- 密炼生产计划维护 菜单与权限挂载到 MES密炼工程
SET NAMES utf8mb4;
SET @mixer_parent_id = (
SELECT id
FROM sys_permission
WHERE name = 'MES密炼工程' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
);
SET @mixer_parent_id = IFNULL(@mixer_parent_id, (
SELECT id
FROM sys_permission
WHERE url = '/mes' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
));
SET @mixer_parent_id = IFNULL(@mixer_parent_id, '1860000000000000001');
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1860000000000000301',@mixer_parent_id,'密炼生产计划维护','/mes/mixingproductionplaninfo','mes/mixingproductionplaninfo/index','MesXslMixingProductionPlanList',1,NULL,'1',90,0,'ant-design:calendar-outlined',1,1,1,0,0,'密炼生产计划维护',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),
`name`=VALUES(`name`),
`url`=VALUES(`url`),
`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),
`menu_type`=VALUES(`menu_type`),
`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),
`is_leaf`=VALUES(`is_leaf`),
`keep_alive`=VALUES(`keep_alive`),
`icon`=VALUES(`icon`),
`status`=VALUES(`status`),
`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1860000000000000302','1860000000000000301','查询',2,'xslmes:mes_xsl_mixing_production_plan:list','1',1,'1',0,'admin',NOW()),
('1860000000000000303','1860000000000000301','整表保存',2,'xslmes:mes_xsl_mixing_production_plan:saveAll','1',2,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),
`perms`=VALUES(`perms`),
`sort_no`=VALUES(`sort_no`),
`status`=VALUES(`status`),
`del_flag`=VALUES(`del_flag`);
-- admin 角色授权
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
FROM sys_permission p
WHERE p.id IN (
'1860000000000000301',
'1860000000000000302', '1860000000000000303'
)
AND NOT EXISTS (
SELECT 1
FROM sys_role_permission rp
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b'
AND rp.permission_id = p.id
);
-- 强制修复确保菜单路由与组件路径正确
UPDATE sys_permission
SET
parent_id = @mixer_parent_id,
url = '/mes/mixingproductionplaninfo',
component = 'mes/mixingproductionplaninfo/index',
component_name = 'MesXslMixingProductionPlanList',
menu_type = 1,
is_route = 1,
is_leaf = 1,
hidden = 0,
status = '1',
del_flag = 0
WHERE id = '1860000000000000301';

View File

@@ -0,0 +1,78 @@
-- 原材料需求计划 菜单与权限挂载到 MES密炼工程兼容 MES管理
SET NAMES utf8mb4;
SET @raw_parent_id = (
SELECT id
FROM sys_permission
WHERE name = 'MES密炼工程' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
);
SET @raw_parent_id = IFNULL(@raw_parent_id, (
SELECT id
FROM sys_permission
WHERE url = '/mes' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
));
SET @raw_parent_id = IFNULL(@raw_parent_id, '1860000000000000001');
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1900000000000000710',@raw_parent_id,'原材料需求计划','/mes/rawmaterialdemandplan','mes/rawmaterialdemandplan/index','MesXslRawMaterialDemandPlanList',1,NULL,'1',95,0,'ant-design:ordered-list-outlined',1,1,1,0,0,'原材料需求计划',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),
`name`=VALUES(`name`),
`url`=VALUES(`url`),
`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),
`menu_type`=VALUES(`menu_type`),
`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),
`is_leaf`=VALUES(`is_leaf`),
`keep_alive`=VALUES(`keep_alive`),
`icon`=VALUES(`icon`),
`status`=VALUES(`status`),
`hidden`=VALUES(`hidden`),
`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1900000000000000711','1900000000000000710','导出',2,'xslmes:mes_xsl_raw_material_demand_plan:exportXls','1',1,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),
`perms`=VALUES(`perms`),
`sort_no`=VALUES(`sort_no`),
`status`=VALUES(`status`),
`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
FROM sys_permission p
WHERE p.id IN ('1900000000000000710', '1900000000000000711')
AND NOT EXISTS (
SELECT 1
FROM sys_role_permission rp
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b'
AND rp.permission_id = p.id
);
-- 强制修复 ID + 名称 + URL 三重兜底确保能显示
UPDATE sys_permission
SET
parent_id = @raw_parent_id,
url = '/mes/rawmaterialdemandplan',
component = 'mes/rawmaterialdemandplan/index',
component_name = 'MesXslRawMaterialDemandPlanList',
menu_type = 1,
is_route = 1,
is_leaf = 1,
hidden = 0,
status = '1',
del_flag = 0,
redirect = NULL
WHERE id = '1900000000000000710'
OR name = '原材料需求计划'
OR url = '/mes/rawmaterialdemandplan';

View File

@@ -0,0 +1,74 @@
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.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanSaveAllVO;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "密炼生产计划维护")
@RestController
@RequestMapping("/xslmes/mesXslMixingProductionPlan")
public class MesXslMixingProductionPlanController
extends JeecgController<MesXslMixingProductionPlan, IMesXslMixingProductionPlanService> {
private final IMesXslMixingProductionPlanService mixingProductionPlanService;
public MesXslMixingProductionPlanController(
IMesXslMixingProductionPlanService mixingProductionPlanService) {
this.mixingProductionPlanService = mixingProductionPlanService;
}
@Operation(summary = "密炼生产计划维护-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslMixingProductionPlan>> queryPageList(
MesXslMixingProductionPlan model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "20") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslMixingProductionPlan> queryWrapper =
QueryGenerator.initQueryWrapper(model, req.getParameterMap());
queryWrapper.orderByAsc("sort_no").orderByAsc("create_time");
IPage<MesXslMixingProductionPlan> pageList =
mixingProductionPlanService.page(new Page<>(pageNo, pageSize), queryWrapper);
return Result.OK(pageList);
}
@AutoLog(value = "密炼生产计划维护-整表保存")
@Operation(summary = "密炼生产计划维护-整表保存")
@RequiresPermissions("xslmes:mes_xsl_mixing_production_plan:saveAll")
@PostMapping("/saveAll")
public Result<String> saveAll(@RequestBody MesXslMixingProductionPlanSaveAllVO req) {
mixingProductionPlanService.saveAllRows(req == null ? null : req.getRows());
return Result.OK("保存成功");
}
@Operation(summary = "密炼生产计划维护-班次生产订单候选分页")
@GetMapping("/orderOptionPage")
public Result<IPage<MesXslMixingProductionPlanOrderOptionVO>> orderOptionPage(
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(name = "keyword", required = false) String keyword,
@RequestParam(name = "machineId", required = false) String machineId,
@RequestParam(name = "machineName", required = false) String machineName) {
return Result.OK(
mixingProductionPlanService.queryOrderOptions(
pageNo, pageSize, keyword, machineId, machineName));
}
}

View File

@@ -7,6 +7,7 @@ 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 org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
@@ -97,9 +98,19 @@ public class MesXslProductionOrderController
@Operation(summary = "生产订单-拆分生成母胶计划")
@RequiresPermissions("xslmes:mes_xsl_production_order:split")
@PostMapping("/split")
public Result<MesXslMasterBatchPlan> split(@RequestParam(name = "id", required = true) String id) {
MesXslMasterBatchPlan plan = mesXslProductionOrderService.splitToMasterBatchPlan(id);
return Result.OK("拆分成功", plan);
public Result<List<MesXslMasterBatchPlan>> split(@RequestParam(name = "id", required = true) String id) {
List<MesXslMasterBatchPlan> plans = mesXslProductionOrderService.splitToMasterBatchPlan(id);
return Result.OK("拆分成功", plans);
}
@AutoLog(value = "生产订单-批量拆分生成计划")
@Operation(summary = "生产订单-批量拆分生成计划")
@RequiresPermissions("xslmes:mes_xsl_production_order:split")
@PostMapping("/splitBatch")
public Result<Integer> splitBatch(@RequestParam(name = "ids", required = true) String ids) {
List<String> idList = Arrays.asList(ids.split(","));
int count = mesXslProductionOrderService.splitToMasterBatchPlanBatch(idList);
return Result.OK("批量拆分成功", count);
}
@RequiresPermissions("xslmes:mes_xsl_production_order:exportXls")

View File

@@ -0,0 +1,178 @@
package org.jeecg.modules.xslmes.controller;
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.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialDemandPlanSummary;
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.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;
/**
* 原材料需求计划(汇总查询)
*/
@Tag(name = "原材料需求计划")
@RestController
@RequestMapping("/xslmes/mesXslRawMaterialDemandPlan")
public class MesXslRawMaterialDemandPlanController {
@Autowired private JdbcTemplate jdbcTemplate;
@Operation(summary = "原材料需求计划-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslRawMaterialDemandPlanSummary>> queryPageList(
MesXslRawMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params);
String countSql = "SELECT COUNT(1) FROM (" + groupedSql + ") t";
Long total = jdbcTemplate.queryForObject(countSql, Long.class, params.toArray());
long totalCount = total == null ? 0L : total;
int offset = Math.max((pageNo - 1) * pageSize, 0);
String pageSql = groupedSql + buildOrderBy(groupedByMachine) + " LIMIT ? OFFSET ?";
List<Object> pageParams = new ArrayList<>(params);
pageParams.add(pageSize);
pageParams.add(offset);
List<MesXslRawMaterialDemandPlanSummary> rows = queryRows(pageSql, pageParams, groupedByMachine);
Page<MesXslRawMaterialDemandPlanSummary> page = new Page<>(pageNo, pageSize, totalCount);
page.setRecords(rows);
return Result.OK(page);
}
@RequiresPermissions("xslmes:mes_xsl_raw_material_demand_plan:exportXls")
@RequestMapping("/exportXls")
public ModelAndView exportXls(
HttpServletRequest request,
MesXslRawMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params) + buildOrderBy(groupedByMachine);
List<MesXslRawMaterialDemandPlanSummary> exportList = queryRows(groupedSql, params, groupedByMachine);
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "原材料需求计划");
mv.addObject(NormalExcelConstants.CLASS, MesXslRawMaterialDemandPlanSummary.class);
mv.addObject(
NormalExcelConstants.PARAMS,
new ExportParams(
"原材料需求计划",
"导出人:" + (sysUser == null ? "admin" : sysUser.getRealname()),
"原材料需求计划",
ExcelType.XSSF));
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;
}
private String buildGroupedSql(
MesXslRawMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String erpCodeExpr = "COALESCE(NULLIF(TRIM(t.erp_code),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
if (groupedByMachine) {
sql.append(machineExpr).append(" AS machineName, ");
} else {
sql.append("'' AS machineName, ");
}
sql.append(erpCodeExpr)
.append(" AS erpCode, ")
.append(rawMaterialExpr)
.append(" AS rawMaterialName, ")
.append("SUM(COALESCE(t.demand_weight,0)) AS demandWeight, ")
.append("SUM(COALESCE(t.standard_weight,0)) AS standardWeight, ")
.append("SUM(COALESCE(t.actual_weight,0)) AS actualWeight ")
.append("FROM mes_xsl_raw_material_demand_plan t ")
.append("WHERE (t.del_flag = 0 OR t.del_flag IS NULL) ");
if (query != null && StringUtils.isNotBlank(query.getErpCode())) {
sql.append("AND ").append(erpCodeExpr).append(" LIKE ? ");
params.add("%" + query.getErpCode().trim() + "%");
}
if (query != null && StringUtils.isNotBlank(query.getRawMaterialName())) {
sql.append("AND ").append(rawMaterialExpr).append(" LIKE ? ");
params.add("%" + query.getRawMaterialName().trim() + "%");
}
if (groupedByMachine && query != null && StringUtils.isNotBlank(query.getMachineName())) {
sql.append("AND ").append(machineExpr).append(" LIKE ? ");
params.add("%" + query.getMachineName().trim() + "%");
}
sql.append("GROUP BY ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
sql.append(erpCodeExpr).append(", ").append(rawMaterialExpr);
return sql.toString();
}
private List<MesXslRawMaterialDemandPlanSummary> queryRows(
String sql, List<Object> params, boolean groupedByMachine) {
return jdbcTemplate.query(
sql,
rs -> {
List<MesXslRawMaterialDemandPlanSummary> list = new ArrayList<>();
int seq = 1;
while (rs.next()) {
MesXslRawMaterialDemandPlanSummary row = new MesXslRawMaterialDemandPlanSummary();
row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : "");
row.setErpCode(trim(rs.getString("erpCode")));
row.setRawMaterialName(trim(rs.getString("rawMaterialName")));
row.setDemandWeight(rs.getBigDecimal("demandWeight"));
row.setStandardWeight(rs.getBigDecimal("standardWeight"));
row.setActualWeight(rs.getBigDecimal("actualWeight"));
row.setId(buildRowId(row, groupedByMachine, seq++));
list.add(row);
}
return list;
},
params.toArray());
}
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName, erpCode";
}
return " ORDER BY 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;
}
private String trim(String value) {
return value == null ? "" : value.trim();
}
}

View File

@@ -0,0 +1,132 @@
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.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 密炼生产计划维护
*/
@Data
@TableName("mes_xsl_mixing_production_plan")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "密炼生产计划维护")
public class MesXslMixingProductionPlan implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Excel(name = "排序号", width = 10)
@Schema(description = "排序号")
private Integer sortNo;
@Excel(name = "机台", width = 20, dictTable = "mes_xsl_equipment_ledger", dicText = "equipment_name", dicCode = "id")
@Dict(dictTable = "mes_xsl_equipment_ledger", dicText = "equipment_name", dicCode = "id")
@Schema(description = "机台ID")
private String machineId;
@Excel(name = "机台", width = 20)
@Schema(description = "机台名称")
private String machineName;
@Schema(description = "早班计划ID")
private String morningPlanId;
@Schema(description = "早班计划类型 M母胶/F终胶")
private String morningPlanType;
@Excel(name = "早班生产订单", width = 20)
private String morningOrderNo;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "早班订单日期")
private Date morningOrderDate;
@Excel(name = "早班配方名称", width = 20)
private String morningFormulaName;
@Excel(name = "早班计划重量", width = 15)
private BigDecimal morningPlanWeight;
@Excel(name = "早班计划车数", width = 12)
private Integer morningPlannedCarCount;
@Excel(name = "早班已排产车数", width = 12)
private Integer morningScheduledCarCount;
@Excel(name = "早班完成车数", width = 12)
private Integer morningFinishedCarCount;
@Excel(name = "早班计划", width = 12)
private Integer morningPlanCount;
@Excel(name = "早班备注", width = 20)
private String morningRemark;
@Schema(description = "中班计划ID")
private String noonPlanId;
@Schema(description = "中班计划类型 M母胶/F终胶")
private String noonPlanType;
@Excel(name = "中班生产订单", width = 20)
private String noonOrderNo;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "中班订单日期")
private Date noonOrderDate;
@Excel(name = "中班配方名称", width = 20)
private String noonFormulaName;
@Excel(name = "中班计划重量", width = 15)
private BigDecimal noonPlanWeight;
@Excel(name = "中班计划车数", width = 12)
private Integer noonPlannedCarCount;
@Excel(name = "中班已排产车数", width = 12)
private Integer noonScheduledCarCount;
@Excel(name = "中班完成车数", width = 12)
private Integer noonFinishedCarCount;
@Excel(name = "中班计划", width = 12)
private Integer noonPlanCount;
@Excel(name = "中班备注", width = 20)
private String noonRemark;
@Schema(description = "晚班计划ID")
private String nightPlanId;
@Schema(description = "晚班计划类型 M母胶/F终胶")
private String nightPlanType;
@Excel(name = "晚班生产订单", width = 20)
private String nightOrderNo;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "晚班订单日期")
private Date nightOrderDate;
@Excel(name = "晚班配方名称", width = 20)
private String nightFormulaName;
@Excel(name = "晚班计划重量", width = 15)
private BigDecimal nightPlanWeight;
@Excel(name = "晚班计划车数", width = 12)
private Integer nightPlannedCarCount;
@Excel(name = "晚班已排产车数", width = 12)
private Integer nightScheduledCarCount;
@Excel(name = "晚班完成车数", width = 12)
private Integer nightFinishedCarCount;
@Excel(name = "晚班计划", width = 12)
private Integer nightPlanCount;
@Excel(name = "晚班备注", width = 20)
private String nightRemark;
private Integer tenantId;
private String sysOrgCode;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
}

View File

@@ -0,0 +1,44 @@
package org.jeecg.modules.xslmes.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
/**
* 原材料需求计划汇总(按原材料或按机台+原材料)
*/
@Data
@Schema(description = "原材料需求计划汇总")
public class MesXslRawMaterialDemandPlanSummary implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private String id;
@Excel(name = "机台", width = 20)
@Schema(description = "机台")
private String machineName;
@Excel(name = "ERP编号", width = 18)
@Schema(description = "ERP编号")
private String erpCode;
@Excel(name = "原材料名称", width = 24)
@Schema(description = "原材料名称")
private String rawMaterialName;
@Excel(name = "需求重量", width = 15)
@Schema(description = "需求重量")
private BigDecimal demandWeight;
@Excel(name = "标准重量", width = 15)
@Schema(description = "标准重量")
private BigDecimal standardWeight;
@Excel(name = "实际重量", width = 15)
@Schema(description = "实际重量")
private BigDecimal actualWeight;
}

View File

@@ -0,0 +1,6 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
public interface MesXslMixingProductionPlanMapper extends BaseMapper<MesXslMixingProductionPlan> {}

View File

@@ -1,10 +1,13 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import org.jeecg.modules.xslmes.entity.MesXslMasterBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslProductionOrder;
public interface IMesXslMasterBatchPlanService extends IService<MesXslMasterBatchPlan> {
List<MesXslMasterBatchPlan> generateBatchFromProductionOrder(MesXslProductionOrder productionOrder);
MesXslMasterBatchPlan generateFromProductionOrder(MesXslProductionOrder productionOrder);
}

View File

@@ -0,0 +1,14 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
public interface IMesXslMixingProductionPlanService extends IService<MesXslMixingProductionPlan> {
void saveAllRows(List<MesXslMixingProductionPlan> rows);
IPage<MesXslMixingProductionPlanOrderOptionVO> queryOrderOptions(
Integer pageNo, Integer pageSize, String keyword, String machineId, String machineName);
}

View File

@@ -1,10 +1,13 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import org.jeecg.modules.xslmes.entity.MesXslMasterBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslProductionOrder;
public interface IMesXslProductionOrderService extends IService<MesXslProductionOrder> {
MesXslMasterBatchPlan splitToMasterBatchPlan(String id);
List<MesXslMasterBatchPlan> splitToMasterBatchPlan(String id);
int splitToMasterBatchPlanBatch(List<String> ids);
}

View File

@@ -4,6 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.mes.material.entity.MesMaterial;
@@ -33,7 +37,7 @@ public class MesXslFinalBatchPlanServiceImpl
return exists;
}
MesMaterial finalMaterial = resolveFinalMaterial(productionOrder.getMaterialCode());
MesMaterial finalMaterial = resolveFinalMaterial(productionOrder);
if (finalMaterial == null) {
throw new JeecgBootException("未找到对应终胶物料请确认MES物料编码");
}
@@ -60,16 +64,67 @@ public class MesXslFinalBatchPlanServiceImpl
return plan;
}
private MesMaterial resolveFinalMaterial(String mesMaterialCode) {
if (StringUtils.isBlank(mesMaterialCode)) {
private MesMaterial resolveFinalMaterial(MesXslProductionOrder productionOrder) {
for (String seedCode : resolveSplitSeedCodes(productionOrder)) {
for (String candidateCode : buildFinalCandidates(seedCode)) {
MesMaterial matched = resolveMaterialByBaseCode(candidateCode);
if (matched != null) {
MesMaterial resolved = new MesMaterial();
resolved.setMaterialCode(candidateCode);
resolved.setMaterialName(StringUtils.defaultIfBlank(matched.getMaterialName(), candidateCode));
return resolved;
}
}
}
return null;
}
private MesMaterial resolveMaterialByBaseCode(String baseCode) {
if (StringUtils.isBlank(baseCode)) {
return null;
}
MesMaterial exact =
mesMaterialMapper.selectOne(
new LambdaQueryWrapper<MesMaterial>()
.eq(MesMaterial::getMaterialCode, baseCode)
.last("LIMIT 1"));
if (exact != null) {
return exact;
}
return mesMaterialMapper.selectOne(
new LambdaQueryWrapper<MesMaterial>()
.eq(MesMaterial::getMaterialCode, mesMaterialCode.trim())
.likeRight(MesMaterial::getMaterialCode, baseCode)
.last("LIMIT 1"));
}
private List<String> resolveSplitSeedCodes(MesXslProductionOrder productionOrder) {
Set<String> seeds = new LinkedHashSet<>();
if (productionOrder != null) {
if (StringUtils.isNotBlank(productionOrder.getMesMaterialName())) {
seeds.add(productionOrder.getMesMaterialName().trim());
}
if (StringUtils.isNotBlank(productionOrder.getMaterialCode())) {
seeds.add(productionOrder.getMaterialCode().trim());
}
}
return new ArrayList<>(seeds);
}
private List<String> buildFinalCandidates(String code) {
List<String> candidates = new ArrayList<>(2);
if (StringUtils.isBlank(code)) {
return candidates;
}
String trimmed = code.trim();
if (trimmed.length() > 0 && (trimmed.startsWith("F") || trimmed.startsWith("f"))) {
candidates.add(trimmed);
return candidates;
}
candidates.add("F" + trimmed);
candidates.add(trimmed);
return candidates;
}
private String buildOrderSerialNo(MesXslProductionOrder productionOrder) {
String orderNo = StringUtils.defaultIfBlank(productionOrder.getProductionOrderNo(), productionOrder.getId());
return orderNo + "-F-" + System.currentTimeMillis();

View File

@@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.mes.material.entity.MesMaterial;
@@ -25,28 +27,55 @@ public class MesXslMasterBatchPlanServiceImpl
@Autowired private MesMaterialMapper mesMaterialMapper;
@Override
public MesXslMasterBatchPlan generateFromProductionOrder(MesXslProductionOrder productionOrder) {
public List<MesXslMasterBatchPlan> generateBatchFromProductionOrder(MesXslProductionOrder productionOrder) {
if (productionOrder == null || StringUtils.isBlank(productionOrder.getId())) {
throw new JeecgBootException("生产订单不存在,无法拆分");
}
MesMaterial motherMaterial = resolveMotherMaterial(productionOrder.getMaterialCode());
if (motherMaterial == null) {
throw new JeecgBootException("未找到对应母胶物料优先B1其次B2");
int totalSegments = normalizeSegmentCount(productionOrder.getProcessSegmentCount());
int motherSegments = Math.max(totalSegments - 1, 0);
if (motherSegments <= 0) {
return new ArrayList<>();
}
MesXslMasterBatchPlan exists =
this.getOne(new LambdaQueryWrapper<MesXslMasterBatchPlan>().eq(MesXslMasterBatchPlan::getSourceOrderId, productionOrder.getId()));
if (exists != null) {
return exists;
List<MesXslMasterBatchPlan> existingPlans =
this.list(
new LambdaQueryWrapper<MesXslMasterBatchPlan>()
.eq(MesXslMasterBatchPlan::getSourceOrderId, productionOrder.getId())
.orderByAsc(MesXslMasterBatchPlan::getCreateTime)
.orderByAsc(MesXslMasterBatchPlan::getId));
List<MesXslMasterBatchPlan> result = new ArrayList<>();
for (int stageIndex = 1; stageIndex <= motherSegments; stageIndex++) {
MesMaterial motherMaterial = resolveMotherMaterialByStage(productionOrder, stageIndex);
if (motherMaterial == null) {
throw new JeecgBootException("未找到对应母胶物料(第" + stageIndex + "编码前缀B" + stageIndex + "");
}
MesXslMasterBatchPlan matched = findExistingByMaterialCode(existingPlans, motherMaterial.getMaterialCode());
if (matched != null) {
result.add(matched);
continue;
}
MesXslMasterBatchPlan plan = buildMotherPlan(productionOrder, motherMaterial, stageIndex);
this.save(plan);
result.add(plan);
existingPlans.add(plan);
}
return result;
}
@Override
public MesXslMasterBatchPlan generateFromProductionOrder(MesXslProductionOrder productionOrder) {
List<MesXslMasterBatchPlan> plans = generateBatchFromProductionOrder(productionOrder);
return plans.isEmpty() ? null : plans.get(0);
}
private MesXslMasterBatchPlan buildMotherPlan(
MesXslProductionOrder productionOrder, MesMaterial motherMaterial, int stageIndex) {
BigDecimal planWeight = productionOrder.getPlanQty() == null ? BigDecimal.ZERO : productionOrder.getPlanQty();
BigDecimal perCarWeight = BigDecimal.ZERO;
int planCarCount = calcPlanCarCount(planWeight, perCarWeight);
MesXslMasterBatchPlan plan = new MesXslMasterBatchPlan();
plan.setSourceOrderId(productionOrder.getId());
plan.setOrderSerialNo(buildOrderSerialNo(productionOrder));
plan.setOrderSerialNo(buildOrderSerialNo(productionOrder, stageIndex));
plan.setOrderNo(productionOrder.getProductionOrderNo());
plan.setProductionSegmentCount(productionOrder.getProcessSegmentCount());
plan.setOrderDate(productionOrder.getOrderDate());
@@ -58,45 +87,93 @@ public class MesXslMasterBatchPlanServiceImpl
plan.setScheduledCarCount(0);
plan.setFinishedCarCount(0);
plan.setStatus(0);
this.save(plan);
return plan;
}
private MesMaterial resolveMotherMaterial(String mesMaterialCode) {
if (StringUtils.isBlank(mesMaterialCode)) {
private MesXslMasterBatchPlan findExistingByMaterialCode(
List<MesXslMasterBatchPlan> existingPlans, String materialCode) {
if (StringUtils.isBlank(materialCode) || existingPlans == null || existingPlans.isEmpty()) {
return null;
}
String code = mesMaterialCode.trim();
List<String> candidates = buildMotherCandidates(code);
for (String c : candidates) {
MesMaterial found =
mesMaterialMapper.selectOne(
new LambdaQueryWrapper<MesMaterial>()
.eq(MesMaterial::getMaterialCode, c)
.last("LIMIT 1"));
if (found != null) {
return found;
for (MesXslMasterBatchPlan plan : existingPlans) {
if (plan != null && materialCode.equalsIgnoreCase(StringUtils.trimToEmpty(plan.getMaterialCode()))) {
return plan;
}
}
return null;
}
private List<String> buildMotherCandidates(String code) {
List<String> list = new ArrayList<>(2);
private MesMaterial resolveMotherMaterialByStage(MesXslProductionOrder productionOrder, int stageIndex) {
for (String seedCode : resolveSplitSeedCodes(productionOrder)) {
List<String> candidates = buildMotherCandidates(seedCode, stageIndex);
for (String candidateCode : candidates) {
MesMaterial matched = resolveMaterialByBaseCode(candidateCode);
if (matched != null) {
MesMaterial resolved = new MesMaterial();
resolved.setMaterialCode(candidateCode);
resolved.setMaterialName(StringUtils.defaultIfBlank(matched.getMaterialName(), candidateCode));
return resolved;
}
}
}
return null;
}
private MesMaterial resolveMaterialByBaseCode(String baseCode) {
if (StringUtils.isBlank(baseCode)) {
return null;
}
MesMaterial exact =
mesMaterialMapper.selectOne(
new LambdaQueryWrapper<MesMaterial>()
.eq(MesMaterial::getMaterialCode, baseCode)
.last("LIMIT 1"));
if (exact != null) {
return exact;
}
return mesMaterialMapper.selectOne(
new LambdaQueryWrapper<MesMaterial>()
.likeRight(MesMaterial::getMaterialCode, baseCode)
.last("LIMIT 1"));
}
private List<String> resolveSplitSeedCodes(MesXslProductionOrder productionOrder) {
Set<String> seeds = new LinkedHashSet<>();
if (productionOrder != null) {
if (StringUtils.isNotBlank(productionOrder.getMesMaterialName())) {
seeds.add(productionOrder.getMesMaterialName().trim());
}
if (StringUtils.isNotBlank(productionOrder.getMaterialCode())) {
seeds.add(productionOrder.getMaterialCode().trim());
}
}
return new ArrayList<>(seeds);
}
private List<String> buildMotherCandidates(String code, int stageIndex) {
List<String> list = new ArrayList<>(1);
if (StringUtils.isBlank(code) || stageIndex <= 0) {
return list;
}
if (code.length() > 1 && (code.startsWith("F") || code.startsWith("f"))) {
String suffix = code.substring(1);
list.add("B1" + suffix);
list.add("B2" + suffix);
list.add("B" + stageIndex + suffix);
} else {
list.add("B1" + code);
list.add("B2" + code);
list.add("B" + stageIndex + code);
}
return list;
}
private String buildOrderSerialNo(MesXslProductionOrder productionOrder) {
private int normalizeSegmentCount(Integer segmentCount) {
if (segmentCount == null || segmentCount <= 0) {
return 1;
}
return segmentCount;
}
private String buildOrderSerialNo(MesXslProductionOrder productionOrder, int stageIndex) {
String orderNo = StringUtils.defaultIfBlank(productionOrder.getProductionOrderNo(), productionOrder.getId());
return orderNo + "-" + System.currentTimeMillis();
return orderNo + "-B" + stageIndex + "-" + System.currentTimeMillis();
}
private int calcPlanCarCount(BigDecimal planWeight, BigDecimal perCarWeight) {

View File

@@ -0,0 +1,416 @@
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.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
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.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.service.IMesXslMixingProductionPlanService;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@Service
public class MesXslMixingProductionPlanServiceImpl
extends ServiceImpl<MesXslMixingProductionPlanMapper, MesXslMixingProductionPlan>
implements IMesXslMixingProductionPlanService {
private final MesXslEquipmentLedgerMapper equipmentLedgerMapper;
private final MesXslMasterBatchPlanMapper masterBatchPlanMapper;
private final MesXslFinalBatchPlanMapper finalBatchPlanMapper;
private final MesXslMixingSpecMapper mixingSpecMapper;
public MesXslMixingProductionPlanServiceImpl(
MesXslEquipmentLedgerMapper equipmentLedgerMapper,
MesXslMasterBatchPlanMapper masterBatchPlanMapper,
MesXslFinalBatchPlanMapper finalBatchPlanMapper,
MesXslMixingSpecMapper mixingSpecMapper) {
this.equipmentLedgerMapper = equipmentLedgerMapper;
this.masterBatchPlanMapper = masterBatchPlanMapper;
this.finalBatchPlanMapper = finalBatchPlanMapper;
this.mixingSpecMapper = mixingSpecMapper;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveAllRows(List<MesXslMixingProductionPlan> rows) {
this.remove(new LambdaQueryWrapper<>());
if (CollectionUtils.isEmpty(rows)) {
return;
}
Map<String, String> machineNameCache = new HashMap<>();
List<MesXslMixingProductionPlan> saveList = new ArrayList<>();
int sort = 1;
for (MesXslMixingProductionPlan row : rows) {
if (row == null || !hasBusinessData(row)) {
continue;
}
normalizeRow(row, machineNameCache);
row.setSortNo(sort++);
saveList.add(row);
}
if (!saveList.isEmpty()) {
this.saveBatch(saveList, 200);
}
}
private boolean hasBusinessData(MesXslMixingProductionPlan row) {
return StringUtils.isNotBlank(row.getMachineId())
|| StringUtils.isNotBlank(row.getMorningPlanId())
|| StringUtils.isNotBlank(row.getNoonPlanId())
|| StringUtils.isNotBlank(row.getNightPlanId())
|| StringUtils.isNotBlank(row.getMorningRemark())
|| StringUtils.isNotBlank(row.getNoonRemark())
|| StringUtils.isNotBlank(row.getNightRemark())
|| row.getMorningPlanCount() != null
|| row.getNoonPlanCount() != null
|| row.getNightPlanCount() != null;
}
private void normalizeRow(
MesXslMixingProductionPlan row, Map<String, String> machineNameCache) {
row.setId(null);
row.setMachineId(StringUtils.trimToNull(row.getMachineId()));
row.setMachineName(resolveMachineName(row.getMachineId(), machineNameCache));
fillShiftFromPlan(row, "morning");
fillShiftFromPlan(row, "noon");
fillShiftFromPlan(row, "night");
Date now = new Date();
if (row.getCreateTime() == null) {
row.setCreateTime(now);
}
row.setUpdateTime(now);
if (row.getDelFlag() == null) {
row.setDelFlag(CommonConstant.DEL_FLAG_0);
}
}
private String resolveMachineName(String machineId, Map<String, String> cache) {
if (StringUtils.isBlank(machineId)) {
return null;
}
return cache.computeIfAbsent(
machineId,
id -> {
MesXslEquipmentLedger ledger = equipmentLedgerMapper.selectById(id);
return ledger == null ? null : ledger.getEquipmentName();
});
}
private void fillShiftFromPlan(MesXslMixingProductionPlan row, String shift) {
String planId = getPlanId(row, shift);
String planType = StringUtils.upperCase(StringUtils.trimToEmpty(getPlanType(row, shift)));
if (StringUtils.isBlank(planId)) {
clearShiftPlanFields(row, shift);
return;
}
if ("M".equals(planType)) {
MesXslMasterBatchPlan plan = masterBatchPlanMapper.selectById(planId);
if (plan == null) {
clearShiftPlanFields(row, shift);
return;
}
setShiftFromMasterPlan(row, shift, plan);
return;
}
MesXslFinalBatchPlan finalPlan = finalBatchPlanMapper.selectById(planId);
if (finalPlan == null) {
clearShiftPlanFields(row, shift);
return;
}
setShiftFromFinalPlan(row, shift, finalPlan);
}
private String getPlanId(MesXslMixingProductionPlan row, String shift) {
return switch (shift) {
case "morning" -> StringUtils.trimToNull(row.getMorningPlanId());
case "noon" -> StringUtils.trimToNull(row.getNoonPlanId());
default -> StringUtils.trimToNull(row.getNightPlanId());
};
}
private String getPlanType(MesXslMixingProductionPlan row, String shift) {
return switch (shift) {
case "morning" -> row.getMorningPlanType();
case "noon" -> row.getNoonPlanType();
default -> row.getNightPlanType();
};
}
private void setShiftFromMasterPlan(
MesXslMixingProductionPlan row, String shift, MesXslMasterBatchPlan plan) {
setShiftPlanType(row, shift, "M");
setShiftOrderNo(row, shift, plan.getOrderNo());
setShiftOrderDate(row, shift, plan.getOrderDate());
setShiftFormulaName(
row,
shift,
resolveFormulaNameByMachineAndMaterial(
row.getMachineId(), row.getMachineName(), plan.getMaterialCode(), plan.getMesMaterialName()));
setShiftPlanWeight(row, shift, plan.getPlanWeight());
setShiftPlannedCarCount(row, shift, plan.getPlannedCarCount());
setShiftScheduledCarCount(row, shift, plan.getScheduledCarCount());
setShiftFinishedCarCount(row, shift, plan.getFinishedCarCount());
}
private void setShiftFromFinalPlan(
MesXslMixingProductionPlan row, String shift, MesXslFinalBatchPlan plan) {
setShiftPlanType(row, shift, "F");
setShiftOrderNo(row, shift, plan.getOrderNo());
setShiftOrderDate(row, shift, plan.getOrderDate());
setShiftFormulaName(
row,
shift,
resolveFormulaNameByMachineAndMaterial(
row.getMachineId(), row.getMachineName(), plan.getMaterialCode(), plan.getMesMaterialName()));
setShiftPlanWeight(row, shift, plan.getPlanWeight());
setShiftPlannedCarCount(row, shift, plan.getPlannedCarCount());
setShiftScheduledCarCount(row, shift, plan.getScheduledCarCount());
setShiftFinishedCarCount(row, shift, plan.getFinishedCarCount());
}
private void clearShiftPlanFields(MesXslMixingProductionPlan row, String shift) {
setShiftPlanType(row, shift, null);
setShiftOrderNo(row, shift, null);
setShiftOrderDate(row, shift, null);
setShiftFormulaName(row, shift, null);
setShiftPlanWeight(row, shift, null);
setShiftPlannedCarCount(row, shift, null);
setShiftScheduledCarCount(row, shift, null);
setShiftFinishedCarCount(row, shift, null);
}
private void setShiftPlanType(MesXslMixingProductionPlan row, String shift, String value) {
switch (shift) {
case "morning" -> row.setMorningPlanType(value);
case "noon" -> row.setNoonPlanType(value);
default -> row.setNightPlanType(value);
}
}
private void setShiftOrderNo(MesXslMixingProductionPlan row, String shift, String value) {
switch (shift) {
case "morning" -> row.setMorningOrderNo(value);
case "noon" -> row.setNoonOrderNo(value);
default -> row.setNightOrderNo(value);
}
}
private void setShiftOrderDate(MesXslMixingProductionPlan row, String shift, Date value) {
switch (shift) {
case "morning" -> row.setMorningOrderDate(value);
case "noon" -> row.setNoonOrderDate(value);
default -> row.setNightOrderDate(value);
}
}
private void setShiftFormulaName(MesXslMixingProductionPlan row, String shift, String value) {
switch (shift) {
case "morning" -> row.setMorningFormulaName(value);
case "noon" -> row.setNoonFormulaName(value);
default -> row.setNightFormulaName(value);
}
}
private void setShiftPlanWeight(
MesXslMixingProductionPlan row, String shift, java.math.BigDecimal value) {
switch (shift) {
case "morning" -> row.setMorningPlanWeight(value);
case "noon" -> row.setNoonPlanWeight(value);
default -> row.setNightPlanWeight(value);
}
}
private void setShiftPlannedCarCount(MesXslMixingProductionPlan row, String shift, Integer value) {
switch (shift) {
case "morning" -> row.setMorningPlannedCarCount(value);
case "noon" -> row.setNoonPlannedCarCount(value);
default -> row.setNightPlannedCarCount(value);
}
}
private void setShiftScheduledCarCount(MesXslMixingProductionPlan row, String shift, Integer value) {
switch (shift) {
case "morning" -> row.setMorningScheduledCarCount(value);
case "noon" -> row.setNoonScheduledCarCount(value);
default -> row.setNightScheduledCarCount(value);
}
}
private void setShiftFinishedCarCount(MesXslMixingProductionPlan row, String shift, Integer value) {
switch (shift) {
case "morning" -> row.setMorningFinishedCarCount(value);
case "noon" -> row.setNoonFinishedCarCount(value);
default -> row.setNightFinishedCarCount(value);
}
}
@Override
public IPage<MesXslMixingProductionPlanOrderOptionVO> queryOrderOptions(
Integer pageNo, Integer pageSize, String keyword, String machineId, String machineName) {
String machineIdTrimmed = StringUtils.trimToNull(machineId);
String machineNameTrimmed = StringUtils.trimToNull(machineName);
List<MesXslMixingProductionPlanOrderOptionVO> all = new ArrayList<>();
LambdaQueryWrapper<MesXslMasterBatchPlan> masterWrapper =
new LambdaQueryWrapper<MesXslMasterBatchPlan>()
.and(w -> w.eq(MesXslMasterBatchPlan::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesXslMasterBatchPlan::getDelFlag));
LambdaQueryWrapper<MesXslFinalBatchPlan> finalWrapper =
new LambdaQueryWrapper<MesXslFinalBatchPlan>()
.and(w -> w.eq(MesXslFinalBatchPlan::getDelFlag, CommonConstant.DEL_FLAG_0).or().isNull(MesXslFinalBatchPlan::getDelFlag));
if (StringUtils.isNotBlank(keyword)) {
masterWrapper.and(
w ->
w.like(MesXslMasterBatchPlan::getOrderNo, keyword)
.or()
.like(MesXslMasterBatchPlan::getMesMaterialName, keyword));
finalWrapper.and(
w ->
w.like(MesXslFinalBatchPlan::getOrderNo, keyword)
.or()
.like(MesXslFinalBatchPlan::getMesMaterialName, keyword));
}
List<MesXslMasterBatchPlan> masters = masterBatchPlanMapper.selectList(masterWrapper);
for (MesXslMasterBatchPlan m : masters) {
MesXslMixingProductionPlanOrderOptionVO vo = new MesXslMixingProductionPlanOrderOptionVO();
vo.setPlanId(m.getId());
vo.setPlanType("M");
vo.setOrderNo(m.getOrderNo());
vo.setOrderDate(m.getOrderDate());
vo.setFormulaName(
resolveFormulaNameByMachineAndMaterial(
machineIdTrimmed, machineNameTrimmed, m.getMaterialCode(), m.getMesMaterialName()));
vo.setPlanWeight(m.getPlanWeight());
vo.setPlannedCarCount(m.getPlannedCarCount());
vo.setScheduledCarCount(m.getScheduledCarCount());
vo.setFinishedCarCount(m.getFinishedCarCount());
all.add(vo);
}
List<MesXslFinalBatchPlan> finals = finalBatchPlanMapper.selectList(finalWrapper);
for (MesXslFinalBatchPlan f : finals) {
MesXslMixingProductionPlanOrderOptionVO vo = new MesXslMixingProductionPlanOrderOptionVO();
vo.setPlanId(f.getId());
vo.setPlanType("F");
vo.setOrderNo(f.getOrderNo());
vo.setOrderDate(f.getOrderDate());
vo.setFormulaName(
resolveFormulaNameByMachineAndMaterial(
machineIdTrimmed, machineNameTrimmed, f.getMaterialCode(), f.getMesMaterialName()));
vo.setPlanWeight(f.getPlanWeight());
vo.setPlannedCarCount(f.getPlannedCarCount());
vo.setScheduledCarCount(f.getScheduledCarCount());
vo.setFinishedCarCount(f.getFinishedCarCount());
all.add(vo);
}
all.sort(
Comparator.comparing(
MesXslMixingProductionPlanOrderOptionVO::getOrderDate,
Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(
MesXslMixingProductionPlanOrderOptionVO::getOrderNo,
Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(
MesXslMixingProductionPlanOrderOptionVO::getPlanId,
Comparator.nullsLast(Comparator.reverseOrder())));
long current = pageNo == null || pageNo < 1 ? 1 : pageNo;
long size = pageSize == null || pageSize < 1 ? 20 : pageSize;
int from = (int) ((current - 1) * size);
int to = (int) Math.min(from + size, all.size());
List<MesXslMixingProductionPlanOrderOptionVO> pageRecords =
from >= all.size() ? Collections.emptyList() : all.subList(from, to);
Page<MesXslMixingProductionPlanOrderOptionVO> page = new Page<>(current, size);
page.setTotal(all.size());
page.setRecords(
pageRecords.stream().filter(Objects::nonNull).collect(Collectors.toList()));
return page;
}
private String resolveFormulaNameByMachineAndMaterial(
String machineId, String machineName, String materialCode, String fallbackName) {
String code = StringUtils.trimToEmpty(materialCode);
if (StringUtils.isBlank(code)) {
return StringUtils.defaultString(fallbackName);
}
String targetSpec = buildTargetSpecName(code);
LambdaQueryWrapper<MesXslMixingSpec> exactWrapper =
new LambdaQueryWrapper<MesXslMixingSpec>()
.eq(MesXslMixingSpec::getSpecName, targetSpec)
.and(
w ->
w.eq(MesXslMixingSpec::getDelFlag, CommonConstant.DEL_FLAG_0)
.or()
.isNull(MesXslMixingSpec::getDelFlag))
.last("LIMIT 1");
appendMachineCondition(exactWrapper, machineId, machineName);
MesXslMixingSpec exact = mixingSpecMapper.selectOne(exactWrapper);
if (exact != null && StringUtils.isNotBlank(exact.getSpecName())) {
return exact.getSpecName().trim();
}
LambdaQueryWrapper<MesXslMixingSpec> prefixWrapper =
new LambdaQueryWrapper<MesXslMixingSpec>()
.likeRight(MesXslMixingSpec::getSpecName, code)
.and(
w ->
w.eq(MesXslMixingSpec::getDelFlag, CommonConstant.DEL_FLAG_0)
.or()
.isNull(MesXslMixingSpec::getDelFlag))
.orderByAsc(MesXslMixingSpec::getSpecName)
.last("LIMIT 1");
appendMachineCondition(prefixWrapper, machineId, machineName);
MesXslMixingSpec prefix = mixingSpecMapper.selectOne(prefixWrapper);
if (prefix != null && StringUtils.isNotBlank(prefix.getSpecName())) {
return prefix.getSpecName().trim();
}
return StringUtils.defaultIfBlank(fallbackName, targetSpec);
}
private String buildTargetSpecName(String materialCode) {
String code = StringUtils.trimToEmpty(materialCode).toUpperCase();
if (code.matches(".*SA\\d{2}$")) {
return code;
}
return code + "SA01";
}
private void appendMachineCondition(
LambdaQueryWrapper<MesXslMixingSpec> wrapper, String machineId, String machineName) {
String machineIdTrimmed = StringUtils.trimToNull(machineId);
String machineNameTrimmed = StringUtils.trimToNull(machineName);
if (machineIdTrimmed != null && machineNameTrimmed != null) {
wrapper.and(
w ->
w.eq(MesXslMixingSpec::getMachineId, machineIdTrimmed)
.or()
.eq(MesXslMixingSpec::getMachineName, machineNameTrimmed));
return;
}
if (machineIdTrimmed != null) {
wrapper.eq(MesXslMixingSpec::getMachineId, machineIdTrimmed);
return;
}
if (machineNameTrimmed != null) {
wrapper.eq(MesXslMixingSpec::getMachineName, machineNameTrimmed);
}
}
}

View File

@@ -1,11 +1,17 @@
package org.jeecg.modules.xslmes.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.xslmes.entity.MesXslFinalBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpec;
import org.jeecg.modules.xslmes.service.IMesXslFinalBatchPlanService;
import org.jeecg.modules.xslmes.entity.MesXslMasterBatchPlan;
import org.jeecg.modules.xslmes.entity.MesXslProductionOrder;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMapper;
import org.jeecg.modules.xslmes.mapper.MesXslProductionOrderMapper;
import org.jeecg.modules.xslmes.service.IMesXslMasterBatchPlanService;
import org.jeecg.modules.xslmes.service.IMesXslProductionOrderService;
@@ -20,10 +26,11 @@ public class MesXslProductionOrderServiceImpl
@Autowired private IMesXslMasterBatchPlanService masterBatchPlanService;
@Autowired private IMesXslFinalBatchPlanService finalBatchPlanService;
@Autowired private MesXslMixingSpecMapper mixingSpecMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public MesXslMasterBatchPlan splitToMasterBatchPlan(String id) {
public List<MesXslMasterBatchPlan> splitToMasterBatchPlan(String id) {
if (StringUtils.isBlank(id)) {
throw new JeecgBootException("生产订单ID不能为空");
}
@@ -34,12 +41,67 @@ public class MesXslProductionOrderServiceImpl
if (order.getSplitStatus() != null && order.getSplitStatus() == 1) {
throw new JeecgBootException("该生产订单已拆分,无需重复操作");
}
MesXslMasterBatchPlan plan = masterBatchPlanService.generateFromProductionOrder(order);
finalBatchPlanService.generateFromProductionOrder(order);
List<MesXslMasterBatchPlan> plans = masterBatchPlanService.generateBatchFromProductionOrder(order);
MesXslFinalBatchPlan finalPlan = finalBatchPlanService.generateFromProductionOrder(order);
validateMixingSpecExists(plans, finalPlan);
MesXslProductionOrder update = new MesXslProductionOrder();
update.setId(order.getId());
update.setSplitStatus(1);
this.updateById(update);
return plan;
return plans;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int splitToMasterBatchPlanBatch(List<String> ids) {
if (ids == null || ids.isEmpty()) {
throw new JeecgBootException("请至少选择一条生产订单");
}
int successCount = 0;
for (String id : ids) {
if (StringUtils.isBlank(id)) {
continue;
}
splitToMasterBatchPlan(id.trim());
successCount++;
}
return successCount;
}
private void validateMixingSpecExists(
List<MesXslMasterBatchPlan> masterPlans, MesXslFinalBatchPlan finalPlan) {
List<String> requiredSpecs = new ArrayList<>();
if (masterPlans != null) {
for (MesXslMasterBatchPlan plan : masterPlans) {
if (plan != null && StringUtils.isNotBlank(plan.getMaterialCode())) {
requiredSpecs.add(plan.getMaterialCode().trim() + "SA01");
}
}
}
if (finalPlan != null && StringUtils.isNotBlank(finalPlan.getMaterialCode())) {
requiredSpecs.add(finalPlan.getMaterialCode().trim() + "SA01");
}
if (requiredSpecs.isEmpty()) {
return;
}
List<String> missingSpecs = new ArrayList<>();
for (String specName : requiredSpecs) {
MesXslMixingSpec found =
mixingSpecMapper.selectOne(
new LambdaQueryWrapper<MesXslMixingSpec>()
.eq(MesXslMixingSpec::getSpecName, specName)
.and(
w ->
w.eq(MesXslMixingSpec::getDelFlag, 0)
.or()
.isNull(MesXslMixingSpec::getDelFlag))
.last("LIMIT 1"));
if (found == null) {
missingSpecs.add(specName);
}
}
if (!missingSpecs.isEmpty()) {
throw new JeecgBootException("对应物料的混炼示方不存在,请先生成混炼示方!");
}
}
}

View File

@@ -0,0 +1,41 @@
package org.jeecg.modules.xslmes.vo;
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 org.springframework.format.annotation.DateTimeFormat;
@Data
@Schema(description = "密炼生产计划-班次生产订单候选")
public class MesXslMixingProductionPlanOrderOptionVO {
@Schema(description = "计划ID")
private String planId;
@Schema(description = "计划类型 M母胶/F终胶")
private String planType;
@Schema(description = "订单编号")
private String orderNo;
@Schema(description = "订单日期")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date orderDate;
@Schema(description = "胶料名称")
private String formulaName;
@Schema(description = "计划重量")
private BigDecimal planWeight;
@Schema(description = "计划车数")
private Integer plannedCarCount;
@Schema(description = "已排产车数")
private Integer scheduledCarCount;
@Schema(description = "完成车数")
private Integer finishedCarCount;
}

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.vo;
import java.util.List;
import lombok.Data;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
@Data
public class MesXslMixingProductionPlanSaveAllVO {
private List<MesXslMixingProductionPlan> rows;
}

View File

@@ -0,0 +1,57 @@
-- 密炼生产计划维护//晚班
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `mes_xsl_mixing_production_plan` (
`id` varchar(32) NOT NULL COMMENT '主键',
`sort_no` int DEFAULT NULL COMMENT '排序号',
`machine_id` varchar(32) DEFAULT NULL COMMENT '机台IDmes_xsl_equipment_ledger.id',
`machine_name` varchar(128) DEFAULT NULL COMMENT '机台名称冗余',
`morning_plan_id` varchar(32) DEFAULT NULL COMMENT '早班计划ID母胶/终胶计划',
`morning_plan_type` varchar(2) DEFAULT NULL COMMENT '早班计划类型M母胶/F终胶',
`morning_order_no` varchar(64) DEFAULT NULL COMMENT '早班生产订单',
`morning_order_date` date DEFAULT NULL COMMENT '早班订单日期',
`morning_formula_name` varchar(128) DEFAULT NULL COMMENT '早班配方名称',
`morning_plan_weight` decimal(18,6) DEFAULT NULL COMMENT '早班计划重量',
`morning_planned_car_count` int DEFAULT NULL COMMENT '早班计划车数',
`morning_scheduled_car_count` int DEFAULT NULL COMMENT '早班已排产车数',
`morning_finished_car_count` int DEFAULT NULL COMMENT '早班完成车数',
`morning_plan_count` int DEFAULT NULL COMMENT '早班计划',
`morning_remark` varchar(500) DEFAULT NULL COMMENT '早班备注',
`noon_plan_id` varchar(32) DEFAULT NULL COMMENT '中班计划ID母胶/终胶计划',
`noon_plan_type` varchar(2) DEFAULT NULL COMMENT '中班计划类型M母胶/F终胶',
`noon_order_no` varchar(64) DEFAULT NULL COMMENT '中班生产订单',
`noon_order_date` date DEFAULT NULL COMMENT '中班订单日期',
`noon_formula_name` varchar(128) DEFAULT NULL COMMENT '中班配方名称',
`noon_plan_weight` decimal(18,6) DEFAULT NULL COMMENT '中班计划重量',
`noon_planned_car_count` int DEFAULT NULL COMMENT '中班计划车数',
`noon_scheduled_car_count` int DEFAULT NULL COMMENT '中班已排产车数',
`noon_finished_car_count` int DEFAULT NULL COMMENT '中班完成车数',
`noon_plan_count` int DEFAULT NULL COMMENT '中班计划',
`noon_remark` varchar(500) DEFAULT NULL COMMENT '中班备注',
`night_plan_id` varchar(32) DEFAULT NULL COMMENT '晚班计划ID母胶/终胶计划',
`night_plan_type` varchar(2) DEFAULT NULL COMMENT '晚班计划类型M母胶/F终胶',
`night_order_no` varchar(64) DEFAULT NULL COMMENT '晚班生产订单',
`night_order_date` date DEFAULT NULL COMMENT '晚班订单日期',
`night_formula_name` varchar(128) DEFAULT NULL COMMENT '晚班配方名称',
`night_plan_weight` decimal(18,6) DEFAULT NULL COMMENT '晚班计划重量',
`night_planned_car_count` int DEFAULT NULL COMMENT '晚班计划车数',
`night_scheduled_car_count` int DEFAULT NULL COMMENT '晚班已排产车数',
`night_finished_car_count` int DEFAULT NULL COMMENT '晚班完成车数',
`night_plan_count` int DEFAULT NULL COMMENT '晚班计划',
`night_remark` varchar(500) DEFAULT NULL COMMENT '晚班备注',
`tenant_id` int DEFAULT NULL COMMENT '租户',
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '部门编码',
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int DEFAULT '0' COMMENT '删除标记0正常1删除',
PRIMARY KEY (`id`),
KEY `idx_mxmp_machine` (`machine_id`),
KEY `idx_mxmp_sort` (`sort_no`),
KEY `idx_mxmp_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES密炼生产计划维护';

View File

@@ -0,0 +1,23 @@
-- 原材料需求计划明细表
CREATE TABLE IF NOT EXISTS `mes_xsl_raw_material_demand_plan` (
`id` varchar(32) NOT NULL COMMENT '主键',
`machine_id` varchar(32) DEFAULT NULL COMMENT '机台ID',
`machine_name` varchar(64) DEFAULT NULL COMMENT '机台名称',
`erp_code` varchar(64) NOT NULL COMMENT 'ERP编号',
`raw_material_name` varchar(128) NOT NULL COMMENT '原材料名称',
`demand_weight` decimal(18,6) DEFAULT '0.000000' COMMENT '需求重量',
`standard_weight` decimal(18,6) DEFAULT '0.000000' COMMENT '标准重量',
`actual_weight` decimal(18,6) DEFAULT '0.000000' COMMENT '实际重量',
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '所属部门',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` tinyint(1) DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
PRIMARY KEY (`id`),
KEY `idx_raw_material_demand_machine` (`machine_name`),
KEY `idx_raw_material_demand_erp` (`erp_code`),
KEY `idx_raw_material_demand_name` (`raw_material_name`),
KEY `idx_raw_material_demand_del` (`del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='原材料需求计划明细';

View File

@@ -0,0 +1,141 @@
package org.jeecg.modules.xslmes.test;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.jeecg.JeecgSystemApplication;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.mes.material.entity.MesMaterial;
import org.jeecg.modules.mes.material.mapper.MesMaterialMapper;
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.MesXslProductionOrder;
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.MesXslProductionOrderMapper;
import org.jeecg.modules.xslmes.service.IMesXslProductionOrderService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = JeecgSystemApplication.class)
@Transactional
class MesXslProductionOrderSplitIntegrationTest {
@Resource private IMesXslProductionOrderService productionOrderService;
@Resource private MesXslProductionOrderMapper productionOrderMapper;
@Resource private MesXslMasterBatchPlanMapper masterBatchPlanMapper;
@Resource private MesXslFinalBatchPlanMapper finalBatchPlanMapper;
@Resource private MesMaterialMapper materialMapper;
@Resource private MesXslMixingSpecMapper mixingSpecMapper;
@Test
void splitShouldGenerateB1B2AndFWhenSegmentCountIs3() {
String base = "UT" + System.currentTimeMillis();
MesXslProductionOrder order = createOrder(base, 3);
createMaterial("B1" + base + "SA01", "B1-" + base);
createMaterial("B2" + base + "SA01", "B2-" + base);
createMaterial("F" + base + "SA01", "F-" + base);
createMixingSpec("B1" + base + "SA01");
createMixingSpec("B2" + base + "SA01");
createMixingSpec("F" + base + "SA01");
List<MesXslMasterBatchPlan> masters = productionOrderService.splitToMasterBatchPlan(order.getId());
Assertions.assertEquals(2, masters.size(), "段数=3时应生成2条母胶计划");
List<MesXslMasterBatchPlan> dbMasters =
masterBatchPlanMapper.selectList(
new LambdaQueryWrapper<MesXslMasterBatchPlan>()
.eq(MesXslMasterBatchPlan::getSourceOrderId, order.getId())
.orderByAsc(MesXslMasterBatchPlan::getMaterialCode));
Assertions.assertEquals(2, dbMasters.size());
Assertions.assertEquals("B1" + base, dbMasters.get(0).getMaterialCode());
Assertions.assertEquals("B2" + base, dbMasters.get(1).getMaterialCode());
MesXslFinalBatchPlan finalPlan =
finalBatchPlanMapper.selectOne(
new LambdaQueryWrapper<MesXslFinalBatchPlan>()
.eq(MesXslFinalBatchPlan::getSourceOrderId, order.getId())
.last("LIMIT 1"));
Assertions.assertNotNull(finalPlan, "应生成1条终胶计划");
Assertions.assertEquals("F" + base, finalPlan.getMaterialCode());
MesXslProductionOrder updated = productionOrderMapper.selectById(order.getId());
Assertions.assertEquals(1, updated.getSplitStatus());
}
@Test
void splitShouldFailWhenAnyMixingSpecMissing() {
String base = "UT" + (System.currentTimeMillis() + 7);
MesXslProductionOrder order = createOrder(base, 3);
createMaterial("B1" + base + "SA01", "B1-" + base);
createMaterial("B2" + base + "SA01", "B2-" + base);
createMaterial("F" + base + "SA01", "F-" + base);
// 故意缺少 FxxxSA01
createMixingSpec("B1" + base + "SA01");
createMixingSpec("B2" + base + "SA01");
JeecgBootException ex =
Assertions.assertThrows(
JeecgBootException.class,
() -> productionOrderService.splitToMasterBatchPlan(order.getId()));
Assertions.assertEquals("对应物料的混炼示方不存在,请先生成混炼示方!", ex.getMessage());
List<MesXslMasterBatchPlan> dbMasters =
masterBatchPlanMapper.selectList(
new LambdaQueryWrapper<MesXslMasterBatchPlan>()
.eq(MesXslMasterBatchPlan::getSourceOrderId, order.getId()));
Assertions.assertTrue(dbMasters.isEmpty(), "失败后母胶计划应回滚");
MesXslFinalBatchPlan finalPlan =
finalBatchPlanMapper.selectOne(
new LambdaQueryWrapper<MesXslFinalBatchPlan>()
.eq(MesXslFinalBatchPlan::getSourceOrderId, order.getId())
.last("LIMIT 1"));
Assertions.assertNull(finalPlan, "失败后终胶计划应回滚");
MesXslProductionOrder unchanged = productionOrderMapper.selectById(order.getId());
Assertions.assertTrue(
unchanged.getSplitStatus() == null || unchanged.getSplitStatus() == 0, "失败后拆分状态不应置为已拆分");
}
private MesXslProductionOrder createOrder(String baseCode, int segmentCount) {
MesXslProductionOrder order = new MesXslProductionOrder();
order.setProductionOrderNo("PO-" + baseCode);
order.setOrderDate(new Date());
order.setProcessSegmentCount(segmentCount);
order.setMesMaterialName(baseCode);
order.setMaterialCode(baseCode);
order.setPlanQty(new BigDecimal("100.00"));
order.setSplitStatus(0);
order.setDelFlag(0);
productionOrderMapper.insert(order);
return order;
}
private void createMaterial(String code, String name) {
MesMaterial material = new MesMaterial();
material.setMaterialCode(code);
material.setMaterialName(name);
material.setDelFlag(0);
materialMapper.insert(material);
}
private void createMixingSpec(String specName) {
MesXslMixingSpec spec = new MesXslMixingSpec();
spec.setSpecName(specName);
spec.setDelFlag(0);
mixingSpecMapper.insert(spec);
}
}

View File

@@ -0,0 +1,7 @@
<template>
<MesXslMixingProductionPlanList />
</template>
<script lang="ts" setup>
import MesXslMixingProductionPlanList from '../../xslmes/mesXslMixingProductionPlan/MesXslMixingProductionPlanList.vue';
</script>

View File

@@ -0,0 +1,7 @@
<template>
<MesXslRawMaterialDemandPlanList />
</template>
<script lang="ts" setup>
import MesXslRawMaterialDemandPlanList from '../../xslmes/mesXslRawMaterialDemandPlan/MesXslRawMaterialDemandPlanList.vue';
</script>

View File

@@ -0,0 +1,13 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/xslmes/mesXslMixingProductionPlan/list',
saveAll = '/xslmes/mesXslMixingProductionPlan/saveAll',
orderOptionPage = '/xslmes/mesXslMixingProductionPlan/orderOptionPage',
}
export const list = (params) => defHttp.get({ url: Api.list, params });
export const saveAll = (params) => defHttp.post({ url: Api.saveAll, params });
export const orderOptionPage = (params) => defHttp.get({ url: Api.orderOptionPage, params }, { successMessageMode: 'none' });

View File

@@ -0,0 +1,339 @@
<template>
<div class="mixing-plan-page">
<div class="mixing-plan-toolbar">
<a-button type="primary" preIcon="ant-design:save-outlined" :loading="saving" @click="handleSaveAll">保存</a-button>
</div>
<a-table
:columns="columns"
:data-source="rows"
:pagination="false"
:scroll="{ x: 1560 }"
row-key="_rowKey"
size="small"
bordered
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.dataIndex === 'machineName'">
<a-input
:value="record.machineName"
readonly
placeholder="点击选择机台"
class="picker-input"
@click="openMachinePicker(index)"
/>
</template>
<template
v-else-if="
column.dataIndex === 'morningOrderNo' ||
column.dataIndex === 'noonOrderNo' ||
column.dataIndex === 'nightOrderNo'
"
>
<a-input
:value="record[column.dataIndex]"
readonly
placeholder="点击选择生产订单计划"
class="picker-input"
@click="openOrderPicker(index, shiftByOrderField(column.dataIndex as string))"
/>
</template>
<template
v-else-if="
column.dataIndex === 'morningFormulaName' ||
column.dataIndex === 'noonFormulaName' ||
column.dataIndex === 'nightFormulaName'
"
>
<a-input :value="record[column.dataIndex]" readonly />
</template>
<template
v-else-if="
column.dataIndex === 'morningPlanCount' ||
column.dataIndex === 'noonPlanCount' ||
column.dataIndex === 'nightPlanCount'
"
>
<a-input-number
:value="record[column.dataIndex]"
:min="0"
:precision="0"
:controls="false"
style="width: 100%"
@change="(v) => updateCell(index, column.dataIndex as string, v)"
/>
</template>
<template
v-else-if="
column.dataIndex === 'morningRemark' || column.dataIndex === 'noonRemark' || column.dataIndex === 'nightRemark'
"
>
<a-input :value="record[column.dataIndex]" @change="(e) => updateCell(index, column.dataIndex as string, e?.target?.value)" />
</template>
<template v-else-if="column.key === 'action'">
<a-button type="link" size="small" danger @click="removeRow(index)">删行</a-button>
</template>
<template v-else-if="column.key === 'insert'">
<a-button type="text" size="small" class="insert-plus-btn" title="新增" @click="insertBelow(index)">+</a-button>
</template>
</template>
</a-table>
<MesXslEquipmentLedgerSelectModal @register="registerEquipmentModal" @select="onEquipmentSelect" />
<MesXslMixingPlanOrderSelectModal @register="registerOrderModal" @select="onOrderSelect" />
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { buildUUID } from '/@/utils/uuid';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
import MesXslMixingPlanOrderSelectModal from './components/MesXslMixingPlanOrderSelectModal.vue';
import { list, saveAll } from './MesXslMixingProductionPlan.api';
const { createMessage } = useMessage();
const saving = ref(false);
const rows = ref<Recordable[]>([]);
const pickerContext = ref<{ rowIndex: number; shift?: 'morning' | 'noon' | 'night' } | null>(null);
const [registerEquipmentModal, { openModal: openEquipmentModal }] = useModal();
const [registerOrderModal, { openModal: openOrderModal }] = useModal();
const columns = computed(() => [
{ title: '新增', key: 'insert', width: 64, fixed: 'left', align: 'center' },
{ title: '机台', dataIndex: 'machineName', width: 108, fixed: 'left', align: 'center' },
{
title: '早班',
align: 'center',
children: [
{ title: '生产订单', dataIndex: 'morningOrderNo', width: 120, align: 'center' },
{ title: '配方名称', dataIndex: 'morningFormulaName', width: 110, align: 'center' },
{ title: '计划', dataIndex: 'morningPlanCount', width: 72, align: 'center' },
{ title: '备注', dataIndex: 'morningRemark', width: 98, align: 'center' },
],
},
{
title: '中班',
align: 'center',
children: [
{ title: '生产订单', dataIndex: 'noonOrderNo', width: 120, align: 'center' },
{ title: '配方名称', dataIndex: 'noonFormulaName', width: 110, align: 'center' },
{ title: '计划', dataIndex: 'noonPlanCount', width: 72, align: 'center' },
{ title: '备注', dataIndex: 'noonRemark', width: 98, align: 'center' },
],
},
{
title: '晚班',
align: 'center',
children: [
{ title: '生产订单', dataIndex: 'nightOrderNo', width: 120, align: 'center' },
{ title: '配方名称', dataIndex: 'nightFormulaName', width: 110, align: 'center' },
{ title: '计划', dataIndex: 'nightPlanCount', width: 72, align: 'center' },
{ title: '备注', dataIndex: 'nightRemark', width: 98, align: 'center' },
],
},
{ title: '删除', key: 'action', width: 64, fixed: 'right', align: 'center' },
]);
function createEmptyRow(): Recordable {
return {
_rowKey: buildUUID(),
id: '',
machineId: '',
machineName: '',
morningPlanId: '',
morningPlanType: '',
morningOrderNo: '',
morningOrderDate: '',
morningFormulaName: '',
morningPlanWeight: null,
morningPlannedCarCount: null,
morningScheduledCarCount: null,
morningFinishedCarCount: null,
morningPlanCount: null,
morningRemark: '',
noonPlanId: '',
noonPlanType: '',
noonOrderNo: '',
noonOrderDate: '',
noonFormulaName: '',
noonPlanWeight: null,
noonPlannedCarCount: null,
noonScheduledCarCount: null,
noonFinishedCarCount: null,
noonPlanCount: null,
noonRemark: '',
nightPlanId: '',
nightPlanType: '',
nightOrderNo: '',
nightOrderDate: '',
nightFormulaName: '',
nightPlanWeight: null,
nightPlannedCarCount: null,
nightScheduledCarCount: null,
nightFinishedCarCount: null,
nightPlanCount: null,
nightRemark: '',
};
}
function shiftByOrderField(field: string): 'morning' | 'noon' | 'night' {
if (field.startsWith('morning')) return 'morning';
if (field.startsWith('noon')) return 'noon';
return 'night';
}
function updateCell(index: number, field: string, value: any) {
const row = rows.value[index];
if (!row) return;
row[field] = value;
}
function createBlankRows(count = 12) {
return Array.from({ length: count }, () => createEmptyRow());
}
function insertBelow(index: number) {
rows.value.splice(index + 1, 0, createEmptyRow());
}
function removeRow(index: number) {
rows.value.splice(index, 1);
if (!rows.value.length) {
rows.value = createBlankRows();
}
}
function openMachinePicker(rowIndex: number) {
pickerContext.value = { rowIndex };
const row = rows.value[rowIndex];
openEquipmentModal(true, { equipmentLedgerId: row?.machineId || '' });
}
function onEquipmentSelect(payload: Recordable) {
const ctx = pickerContext.value;
if (!ctx) return;
const row = rows.value[ctx.rowIndex];
if (!row) return;
row.machineId = payload?.equipmentLedgerId || '';
row.machineName = payload?.equipmentName || '';
pickerContext.value = null;
}
function openOrderPicker(rowIndex: number, shift: 'morning' | 'noon' | 'night') {
pickerContext.value = { rowIndex, shift };
const row = rows.value[rowIndex];
openOrderModal(true, {
planId: row?.[`${shift}PlanId`] || '',
machineId: row?.machineId || '',
machineName: row?.machineName || '',
});
}
function onOrderSelect(payload: Recordable | null) {
const ctx = pickerContext.value;
if (!ctx || !ctx.shift) return;
const row = rows.value[ctx.rowIndex];
if (!row) return;
const shift = ctx.shift;
row[`${shift}PlanId`] = payload?.planId || '';
row[`${shift}PlanType`] = payload?.planType || '';
row[`${shift}OrderNo`] = payload?.orderNo || '';
row[`${shift}OrderDate`] = payload?.orderDate || '';
row[`${shift}FormulaName`] = payload?.formulaName || '';
row[`${shift}PlanWeight`] = payload?.planWeight ?? null;
row[`${shift}PlannedCarCount`] = payload?.plannedCarCount ?? null;
row[`${shift}ScheduledCarCount`] = payload?.scheduledCarCount ?? null;
row[`${shift}FinishedCarCount`] = payload?.finishedCarCount ?? null;
pickerContext.value = null;
}
async function loadRows() {
const res = await list({ pageNo: 1, pageSize: 500 });
const records = (res?.records || res?.result?.records || []) as Recordable[];
rows.value = (records || []).map((item) => ({ ...createEmptyRow(), ...item, _rowKey: item.id || buildUUID() }));
if (!rows.value.length) {
rows.value = createBlankRows();
}
}
async function handleSaveAll() {
saving.value = true;
try {
const payload = rows.value.map((r) => {
const { _rowKey, ...rest } = r;
return rest;
});
await saveAll({ rows: payload });
createMessage.success('保存成功');
await loadRows();
} finally {
saving.value = false;
}
}
loadRows();
</script>
<style scoped>
.mixing-plan-page {
padding: 8px;
background: #fff;
}
.mixing-plan-toolbar {
margin-bottom: 12px;
display: flex;
gap: 8px;
}
.picker-input {
cursor: pointer;
}
:deep(.ant-table-thead > tr > th) {
text-align: center !important;
padding: 4px 6px !important;
font-size: 12px;
}
:deep(.ant-table-tbody > tr > td) {
padding: 2px 3px !important;
}
:deep(.ant-input),
:deep(.ant-input-number),
:deep(.ant-input-number-input) {
font-size: 12px;
}
:deep(.ant-input),
:deep(.ant-input-number) {
width: 100%;
min-width: 0;
}
:deep(.ant-input-number-input) {
padding: 0 4px;
}
.insert-plus-btn {
color: #52c41a;
font-size: 18px;
font-weight: 700;
line-height: 1;
padding: 0 4px;
}
.insert-plus-btn:hover,
.insert-plus-btn:focus {
color: #73d13d;
}
</style>

View File

@@ -0,0 +1,89 @@
<template>
<BasicModal v-bind="$attrs" title="选择生产订单计划" :width="1100" @register="registerModal" @ok="handleOk">
<BasicTable @register="registerTable" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable } from '/@/components/Table';
import { orderOptionPage } from '../MesXslMixingProductionPlan.api';
const emit = defineEmits(['register', 'select']);
const selectedRow = ref<Recordable | null>(null);
const machineId = ref('');
const machineName = ref('');
const [registerTable, { reload, getSelectRowKeys, getSelectRows, setSelectedRowKeys, clearSelectedRowKeys }] = useTable({
api: orderOptionPage,
columns: [
{ title: '类型', dataIndex: 'planType', width: 70, customRender: ({ text }) => (text === 'M' ? '母胶' : '终胶') },
{ title: '订单编号', dataIndex: 'orderNo', width: 150 },
{ title: '订单日期', dataIndex: 'orderDate', width: 120 },
{ title: '胶料名称', dataIndex: 'formulaName', width: 170 },
{ title: '计划重量', dataIndex: 'planWeight', width: 120 },
{ title: '计划车数', dataIndex: 'plannedCarCount', width: 100 },
{ title: '已排产车数', dataIndex: 'scheduledCarCount', width: 110 },
{ title: '完成车数', dataIndex: 'finishedCarCount', width: 100 },
],
rowKey: 'planId',
useSearchForm: true,
formConfig: {
labelWidth: 80,
schemas: [{ label: '关键字', field: 'keyword', component: 'Input', colProps: { span: 10 } }],
},
pagination: { pageSize: 10 },
canResize: false,
showIndexColumn: false,
immediate: true,
beforeFetch: (params) => {
return Object.assign(params, {
machineId: machineId.value || undefined,
machineName: machineName.value || undefined,
});
},
rowSelection: {
type: 'radio',
columnWidth: 48,
onChange: (_keys, rows) => {
selectedRow.value = rows?.[0] ?? null;
},
},
clickToRowSelect: true,
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
selectedRow.value = null;
clearSelectedRowKeys?.();
setModalProps({ confirmLoading: false });
machineId.value = data?.machineId || '';
machineName.value = data?.machineName || '';
if (data?.planId) {
setSelectedRowKeys?.([data.planId]);
}
reload();
});
function handleOk() {
const row = selectedRow.value || ((getSelectRows?.() || []) as Recordable[])[0];
if (!row?.planId) {
emit('select', null);
closeModal();
return;
}
emit('select', {
planId: row.planId,
planType: row.planType,
orderNo: row.orderNo || '',
orderDate: row.orderDate || '',
formulaName: row.formulaName || '',
planWeight: row.planWeight ?? null,
plannedCarCount: row.plannedCarCount ?? null,
scheduledCarCount: row.scheduledCarCount ?? null,
finishedCarCount: row.finishedCarCount ?? null,
});
closeModal();
}
</script>

View File

@@ -8,6 +8,7 @@ enum Api {
deleteBatch = '/xslmes/mesXslProductionOrder/deleteBatch',
queryById = '/xslmes/mesXslProductionOrder/queryById',
split = '/xslmes/mesXslProductionOrder/split',
splitBatch = '/xslmes/mesXslProductionOrder/splitBatch',
exportXls = '/xslmes/mesXslProductionOrder/exportXls',
}
@@ -26,4 +27,7 @@ export const queryById = (params) => defHttp.get({ url: Api.queryById, params })
export const splitToMasterBatchPlan = (params, handleSuccess) =>
defHttp.post({ url: Api.split, params }, { joinParamsToUrl: true }).then(() => handleSuccess());
export const splitToMasterBatchPlanBatch = (params, handleSuccess) =>
defHttp.post({ url: Api.splitBatch, params }, { joinParamsToUrl: true }).then(() => handleSuccess());
export const getExportUrl = Api.exportXls;

View File

@@ -11,6 +11,15 @@
>
导出
</a-button>
<a-button
type="primary"
v-auth="'xslmes:mes_xsl_production_order:split'"
preIcon="ant-design:split-cells-outlined"
:disabled="selectedRowKeys.length === 0"
@click="handleBatchSplit"
>
拆分
</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
@@ -32,11 +41,14 @@
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import { useMessage } from '/@/hooks/web/useMessage';
import { Modal } from 'ant-design-vue';
import MesXslProductionOrderModal from './modules/MesXslProductionOrderModal.vue';
import { columns, searchFormSchema } from './MesXslProductionOrder.data';
import { batchDelete, deleteOne, getExportUrl, list, splitToMasterBatchPlan } from './MesXslProductionOrder.api';
import { batchDelete, deleteOne, getExportUrl, list, splitToMasterBatchPlanBatch } from './MesXslProductionOrder.api';
const [registerModal, { openModal }] = useModal();
const { createMessage } = useMessage();
const { tableContext, onExportXls } = useListPage({
tableProps: {
title: '生产订单',
@@ -65,8 +77,25 @@
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value.join(',') }, reload);
}
async function handleSplit(record) {
await splitToMasterBatchPlan({ id: record.id }, reload);
async function handleBatchSplit() {
if (!selectedRowKeys.value.length) {
createMessage.warning('请先勾选要拆分的生产订单');
return;
}
Modal.confirm({
title: '确认批量拆分',
content: `将拆分 ${selectedRowKeys.value.length} 条生产订单,是否继续?`,
okText: '确认',
cancelText: '取消',
onOk: async () => {
try {
await splitToMasterBatchPlanBatch({ ids: selectedRowKeys.value.join(',') }, reload);
createMessage.success('批量拆分成功');
} catch (e: any) {
createMessage.error(e?.message || '批量拆分失败');
}
},
});
}
function handleSuccess() {
reload();
@@ -74,12 +103,6 @@
function getTableAction(record) {
return [
{ label: '编辑', onClick: handleEdit.bind(null, record), auth: 'xslmes:mes_xsl_production_order:edit' },
{
label: '拆分',
onClick: handleSplit.bind(null, record),
auth: 'xslmes:mes_xsl_production_order:split',
ifShow: () => record.splitStatus !== 1,
},
];
}
function getDropDownAction(record) {

View File

@@ -0,0 +1,10 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/xslmes/mesXslRawMaterialDemandPlan/list',
exportXls = '/xslmes/mesXslRawMaterialDemandPlan/exportXls',
}
export const getExportUrl = Api.exportXls;
export const list = (params) => defHttp.get({ url: Api.list, params });

View File

@@ -0,0 +1,28 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
export const baseColumns: BasicColumn[] = [
{ title: 'ERP编号', align: 'center', dataIndex: 'erpCode', width: 180 },
{ title: '原材料名称', align: 'center', dataIndex: 'rawMaterialName', width: 220, ellipsis: true },
{ title: '需求重量', align: 'center', dataIndex: 'demandWeight', width: 140 },
{ title: '标准重量', align: 'center', dataIndex: 'standardWeight', width: 140 },
{ title: '实际重量', align: 'center', dataIndex: 'actualWeight', width: 140 },
];
export const machineColumns: BasicColumn[] = [
{ title: '机台', align: 'center', dataIndex: 'machineName', width: 160 },
...baseColumns,
];
export const searchFormSchema: FormSchema[] = [
{ 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' },
};

View File

@@ -0,0 +1,83 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #tableTitle>
<a-checkbox v-model:checked="groupByMachine" @change="onGroupByMachineChange">按机台统计</a-checkbox>
<a-button
style="margin-left: 8px"
type="primary"
v-auth="'xslmes:mes_xsl_raw_material_demand_plan:exportXls'"
preIcon="ant-design:export-outlined"
@click="onExportXls"
>
导出
</a-button>
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
</template>
</BasicTable>
</div>
</template>
<script lang="ts" name="xslmes-mesXslRawMaterialDemandPlan" setup>
import { onMounted, reactive, ref } from 'vue';
import { BasicTable } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import {
baseColumns,
machineColumns,
searchFormSchema,
superQuerySchema,
} from './MesXslRawMaterialDemandPlan.data';
import { list, getExportUrl } from './MesXslRawMaterialDemandPlan.api';
const queryParam = reactive<any>({ groupByMachine: 0 });
const groupByMachine = ref(false);
const { tableContext, onExportXls } = useListPage({
tableProps: {
title: '原材料需求计划',
api: list,
columns: baseColumns,
canResize: true,
formConfig: {
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true,
},
beforeFetch: (params) => {
return Object.assign(params, queryParam, {
groupByMachine: groupByMachine.value ? 1 : 0,
});
},
},
exportConfig: {
name: '原材料需求计划',
url: getExportUrl,
params: queryParam,
},
});
const [registerTable, { reload, setColumns }] = tableContext;
const superQueryConfig = reactive(superQuerySchema);
onMounted(() => {
applyColumns();
});
function applyColumns() {
setColumns(groupByMachine.value ? machineColumns : baseColumns);
}
function onGroupByMachineChange() {
queryParam.groupByMachine = groupByMachine.value ? 1 : 0;
applyColumns();
reload();
}
function handleSuperQuery(params) {
Object.keys(params).forEach((k) => {
queryParam[k] = params[k];
});
reload();
}
</script>

11
skills-lock.json Normal file
View File

@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"karpathy-guidelines": {
"source": "multica-ai/andrej-karpathy-skills",
"sourceType": "github",
"skillPath": "skills/karpathy-guidelines/SKILL.md",
"computedHash": "0ec1641120accbb458780e0bd19a50a4182f2b97863e90851b91307f29802c9f"
}
}
}