生产环节优化
This commit is contained in:
67
.agents/skills/karpathy-guidelines/SKILL.md
Normal file
67
.agents/skills/karpathy-guidelines/SKILL.md
Normal 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.
|
||||
79
jeecg-boot/db/mes-xsl-mixing-production-plan-menu.sql
Normal file
79
jeecg-boot/db/mes-xsl-mixing-production-plan-menu.sql
Normal 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';
|
||||
78
jeecg-boot/db/mes-xsl-raw-material-demand-plan-menu.sql
Normal file
78
jeecg-boot/db/mes-xsl-raw-material-demand-plan-menu.sql
Normal 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';
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> {}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("对应物料的混炼示方不存在,请先生成混炼示方!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 '机台ID(mes_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密炼生产计划维护';
|
||||
@@ -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='原材料需求计划明细';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<MesXslMixingProductionPlanList />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MesXslMixingProductionPlanList from '../../xslmes/mesXslMixingProductionPlan/MesXslMixingProductionPlanList.vue';
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<MesXslRawMaterialDemandPlanList />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MesXslRawMaterialDemandPlanList from '../../xslmes/mesXslRawMaterialDemandPlan/MesXslRawMaterialDemandPlanList.vue';
|
||||
</script>
|
||||
@@ -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' });
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
@@ -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' },
|
||||
};
|
||||
@@ -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
11
skills-lock.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user