工序投料管理

This commit is contained in:
z 2026-05-27 08:25:25 +08:00
parent 203cc6e1bc
commit 37fff9c937
20 changed files with 2583 additions and 27 deletions

View File

@ -0,0 +1,886 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute;
import cn.hutool.core.util.ObjectUtil;
import com.ningxia.yunxi.chemmes.framework.common.pojo.CommonResult;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.ningxia.yunxi.chemmes.framework.security.core.util.SecurityFrameworkUtils;
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo.*;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.headno.HeadNoDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.machine.MachineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.machmat.MachMatDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.machmatdetail.MachMatDetailDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.material.MaterialDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.plan.PlanDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.planline.PlanLineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.planmachine.PlanMachineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.proline.ProLineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.rawstoragelog.RawStorageLogDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.shiftresult.ShiftResultDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.usermachine.UserMachineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.usermachine.UserMachineDetailDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.usermachine.UserMachineDetailMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.matfeed.MatFeedDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.matfeeddetail.MatFeedDetailDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.headno.HeadNoMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.machine.MachineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.machmat.MachMatMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.machmatdetail.MachMatDetailMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.material.MaterialMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.matfeed.MatFeedMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.matfeeddetail.MatFeedDetailMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.plan.PlanMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.planline.PlanLineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.planmachine.PlanMachineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.proline.ProLineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.rawstoragelog.RawStorageLogMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.shiftresult.ShiftResultMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.techproc.TechProcMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.usermachine.UserMachineMapper;
import com.ningxia.yunxi.chemmes.module.system.dal.dataobject.dict.DictDataDO;
import com.ningxia.yunxi.chemmes.module.system.dal.dataobject.user.AdminUserDO;
import com.ningxia.yunxi.chemmes.module.system.dal.mysql.dict.DictDataMapper;
import com.ningxia.yunxi.chemmes.module.system.dal.mysql.user.AdminUserMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import static com.ningxia.yunxi.chemmes.framework.common.pojo.CommonResult.success;
/**
* 投料执行管理 Controller
* 功能物料配置(表2) + 投料信息(表3) + 弹窗库存确认
* 参考磨机执行(MillExecute)的页面结构
*/
@Tag(name = "管理后台 - 投料执行")
@RestController
@RequestMapping("/tpo/mat-feed-execute")
@Validated
public class MatFeedExecuteController {
@Resource
private UserMachineMapper userMachineMapper;
@Resource
private UserMachineDetailMapper userMachineDetailMapper;
@Resource
private ShiftResultMapper shiftResultMapper;
@Resource
private PlanMapper planMapper;
@Resource
private PlanMachineMapper planMachineMapper;
@Resource
private MachineMapper machineMapper;
@Resource
private ProLineMapper proLineMapper;
@Resource
private PlanLineMapper planLineMapper;
@Resource
private HeadNoMapper headNoMapper;
@Resource
private MatFeedMapper matFeedMapper;
@Resource
private MatFeedDetailMapper matFeedDetailMapper;
@Resource
private MachMatMapper machMatMapper;
@Resource
private MachMatDetailMapper machMatDetailMapper;
@Resource
private MaterialMapper materialMapper;
@Resource
private DictDataMapper dictDataMapper;
@Resource
private TechProcMapper techProcMapper;
@Resource
private RawStorageLogMapper rawStorageLogMapper;
@Resource
private AdminUserMapper adminUserMapper;
// ==================== 1界面初始化 ====================
@GetMapping("/init")
@Operation(summary = "界面初始化 - 获取当前用户的产线、机台、班次信息")
public CommonResult<MatFeedExecuteInitVO> init() {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String userId = String.valueOf(loginUserId);
UserMachineDO userMachine = userMachineMapper.selectByUserId(userId);
List<UserMachineDetailDO> detailList = Collections.emptyList();
if (userMachine != null) {
detailList = userMachineDetailMapper.selectListByUserMachId(userMachine.getId());
if (detailList == null) {
detailList = Collections.emptyList();
}
}
List<MatFeedExecuteInitVO.LineOption> lineList = detailList.stream()
.filter(d -> d.getLineId() != null && d.getProLineName() != null)
.collect(Collectors.collectingAndThen(
Collectors.toMap(
UserMachineDetailDO::getLineId,
d -> MatFeedExecuteInitVO.LineOption.builder()
.lineId(d.getLineId())
.lineName(d.getProLineName())
.build(),
(a, b) -> a
),
m -> new ArrayList<>(m.values())
));
List<MatFeedExecuteInitVO.MachineOption> machineList = detailList.stream()
.filter(d -> d.getMachineId() != null && d.getMachineName() != null)
.map(d -> MatFeedExecuteInitVO.MachineOption.builder()
.machineId(d.getMachineId())
.machineName(d.getMachineName())
.lineId(d.getLineId())
.build())
.collect(Collectors.toList());
LocalDate today = LocalDate.now();
LocalDateTime nowDateTime = LocalDateTime.now();
ShiftResultDO currentShift = shiftResultMapper.selectCurrent(nowDateTime);
MatFeedExecuteInitVO vo = new MatFeedExecuteInitVO();
vo.setLineList(lineList);
vo.setMachineList(machineList);
if (currentShift != null) {
vo.setShiftCd(currentShift.getClassRate());
vo.setGroupCd(currentShift.getClassGroup());
vo.setDefaultDate(today);
}
return success(vo);
}
// ==================== 2生产计划查询(表1) ====================
// 与磨机执行的表1查询逻辑一致
@GetMapping("/plan-list")
@Operation(summary = "查询生产计划列表 - 表1")
public CommonResult<List<MatFeedExecutePlanVO>> getPlanList(
@RequestParam("machineId") Integer machineId,
@RequestParam("lineId") Integer lineId,
@RequestParam("proDate") String proDate,
@RequestParam("shiftCd") String shiftCd,
@RequestParam(value = "groupCd", required = false) String groupCd) {
List<MatFeedExecutePlanVO> result = new ArrayList<>();
LocalDate date = LocalDate.parse(proDate);
// ===== Step 1: 查询 tpo_head_no获取 plan_id 列表和抬头状态 =====
// WHERE pro_date = :proDate AND shift_cd = :shiftCd AND machine_id = :machineId
LambdaQueryWrapperX<HeadNoDO> headQuery = new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getProDate, date)
.eq(HeadNoDO::getShiftCd, shiftCd)
.eq(HeadNoDO::getMachineId, machineId);
if (groupCd != null && !groupCd.isEmpty()) {
headQuery.eq(HeadNoDO::getGroupCd, groupCd);
}
List<HeadNoDO> headNoList = headNoMapper.selectList(headQuery);
if (headNoList.isEmpty()) {
return success(result);
}
// Map: planId -> HeadNoDO用于获取 t3.pro_status 即机台计划状态
Map<Integer, HeadNoDO> headNoMap = new LinkedHashMap<>();
List<Integer> planIds = new ArrayList<>();
for (HeadNoDO h : headNoList) {
if (h.getPlanId() != null && headNoMap.putIfAbsent(h.getPlanId(), h) == null) {
planIds.add(h.getPlanId());
}
}
if (planIds.isEmpty()) {
return success(result);
}
// ===== Step 2: 查询 tpl_plan 主表 =====
List<PlanDO> plans = planMapper.selectBatchIds(planIds);
Map<Integer, PlanDO> planMap = plans.stream()
.collect(Collectors.toMap(PlanDO::getId, p -> p, (a, b) -> a));
// ===== Step 3: 查询 tpl_plan_line (JOIN tpl_plan.id = tpl_plan_line.pro_id) =====
LambdaQueryWrapperX<PlanLineDO> lineQuery = new LambdaQueryWrapperX<PlanLineDO>()
.in(PlanLineDO::getProId, planIds);
if (lineId != null) {
lineQuery.eq(PlanLineDO::getLineId, lineId);
}
List<PlanLineDO> planLines = planLineMapper.selectList(lineQuery);
// Map: planId -> PlanLineDO
Map<Integer, PlanLineDO> planLineByPlanIdMap = planLines.stream()
.collect(Collectors.toMap(PlanLineDO::getProId, l -> l, (a, b) -> a));
// ===== Step 4: 查询 tpl_plan_machine (LEFT JOIN) =====
// ON tpl_plan_line.id = tpl_plan_machine.pro_order_line_id
// AND tpl_plan_machine.machine_id = :machineId
// AND tpl_plan.pro_no = tpl_plan_machine.pro_no
Set<Integer> planLineIds = planLines.stream()
.map(PlanLineDO::getId).collect(Collectors.toSet());
Map<Integer, PlanMachineDO> planMachineByPlanIdMap = new HashMap<>();
if (!planLineIds.isEmpty()) {
List<PlanMachineDO> planMachines = planMachineMapper.selectList(
new LambdaQueryWrapperX<PlanMachineDO>()
.in(PlanMachineDO::getProOrderLineId, planLineIds)
.eq(PlanMachineDO::getMachineId, machineId)
);
// 构建映射: planLineId -> PlanLineDO
Map<Integer, PlanLineDO> lineByIdMap = planLines.stream()
.collect(Collectors.toMap(PlanLineDO::getId, l -> l, (a, b) -> a));
for (PlanMachineDO pm : planMachines) {
PlanLineDO matchedLine = lineByIdMap.get(pm.getProOrderLineId());
if (matchedLine != null) {
// 校验 pro_no 匹配条件: t.pro_no = t2.pro_no
PlanDO matchedPlan = planMap.get(matchedLine.getProId());
if (matchedPlan != null && matchedPlan.getProNo() != null
&& matchedPlan.getProNo().equals(pm.getProNo())) {
planMachineByPlanIdMap.put(matchedLine.getProId(), pm);
}
}
}
}
// ===== Step 5: 查询工艺流程名称 =====
Map<Integer, String> techProcMap = techProcMapper.selectList(
new LambdaQueryWrapperX<com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.techproc.TechProcDO>()
).stream().collect(Collectors.toMap(
com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.techproc.TechProcDO::getId,
com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.techproc.TechProcDO::getTechProc,
(a, b) -> a
));
// ===== Step 6: 构建结果 =====
for (Integer planId : planIds) {
PlanDO plan = planMap.get(planId);
if (plan == null) continue;
HeadNoDO headNo = headNoMap.get(planId);
PlanMachineDO pm = planMachineByPlanIdMap.get(planId);
PlanLineDO planLine = planLineByPlanIdMap.get(planId);
// 机台计划状态: coalesce(t3.pro_status, '未执行')
String machineProStatus = (headNo != null && headNo.getProStatus() != null)
? getProStatusName(headNo.getProStatus()) : "未执行";
// 计划状态: coalesce(t2.pro_status, t.plan_status)
String planStatus;
if (pm != null && pm.getProStatus() != null) {
planStatus = getProStatusName(pm.getProStatus());
} else {
planStatus = getProStatusName(plan.getPlanStatus());
}
// 是否正在执行(用于前端底色浅蓝色)
boolean isExecuting = "执行中".equals(machineProStatus);
String techProcName = plan.getProcessFlow() != null
? techProcMap.getOrDefault(Integer.parseInt(plan.getProcessFlow()), plan.getTechProc())
: plan.getTechProc();
MatFeedExecutePlanVO vo = MatFeedExecutePlanVO.builder()
.id(plan.getId())
.planNo(plan.getProNo())
.machineProStatus(machineProStatus)
.planStatus(planStatus)
.materialName(plan.getMaterialName())
.spec(plan.getSpec())
.planQty(plan.getPlanQty())
.completeQty(plan.getCompleteQty())
.planBgDate(plan.getPlanBgDate())
.planEndDate(plan.getPlanEndDate())
.techProc(techProcName)
.proDate(plan.getProDate())
.machineCd(pm != null ? pm.getMachineCd() : null)
.machineName(pm != null ? pm.getMachineName() : null)
.machineId(pm != null ? pm.getMachineId() : null)
.lineName(planLine != null ? planLine.getLineName()
: (pm != null ? pm.getLineName() : null))
.lineId(planLine != null ? planLine.getLineId()
: (pm != null ? pm.getLineId() : null))
.isExecuting(isExecuting)
.build();
result.add(vo);
}
return success(result);
}
private String getProStatusName(String proStatus) {
DictDataDO dictData = dictDataMapper.selectByDictTypeAndValue("plan_status", proStatus);
return dictData != null ? dictData.getLabel() : proStatus;
}
// ==================== 3物料配置查询(表2) ====================
@GetMapping("/mat-config")
@Operation(summary = "查询物料配置 - 表2投入类物料")
public CommonResult<List<MatFeedExecuteMatConfigVO>> getMatConfig(
@RequestParam(value = "planNo", required = false) String planNo,
@RequestParam("proDate") String proDate,
@RequestParam("shiftCd") String shiftCd,
@RequestParam("machineId") Integer machineId) {
List<MatFeedExecuteMatConfigVO> result = new ArrayList<>();
LocalDate date = LocalDate.parse(proDate);
// ===== Step 1: 查询当前正在执行的计划 =====
List<HeadNoDO> executingHeads = headNoMapper.selectList(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getMachineId, machineId)
.eq(HeadNoDO::getProStatus, "2")
);
// 如果没有执行中的计划物料配置返回空
if (executingHeads.isEmpty()) {
return success(result);
}
// ===== Step 2: tpo_head_no, tpo_mat_feed 查询已投料数据 =====
// SELECT t.* FROM tpo_head_no t, tpo_mat_feed t1
// WHERE t.id = t1.head_id AND t.pro_date = proDate AND t.shift_cd = shiftCd
// AND t.plan_no = planNo AND t.machine_id = machineId
if (planNo != null && !planNo.isEmpty()) {
List<HeadNoDO> matchedHeads = headNoMapper.selectList(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getProDate, date)
.eq(HeadNoDO::getShiftCd, shiftCd)
.eq(HeadNoDO::getPlanNo, planNo)
.eq(HeadNoDO::getMachineId, machineId)
);
if (!matchedHeads.isEmpty()) {
for (HeadNoDO headNo : matchedHeads) {
List<MatFeedDO> feedList = matFeedMapper.selectList(
new LambdaQueryWrapperX<MatFeedDO>()
.eq(MatFeedDO::getHeadId, headNo.getId())
);
for (MatFeedDO feed : feedList) {
MatFeedExecuteMatConfigVO vo = MatFeedExecuteMatConfigVO.builder()
.id(feed.getId())
.materialId(feed.getMaterialId())
.materialCode(feed.getMaterialCode())
.materialName(feed.getMaterialName())
.matTypeRaw(feed.getMatType())
.matType(convertMatTypeName(feed.getMatType()))
.spec(feed.getSpec())
.unit(feed.getUnit())
.atoQty(feed.getAtoQty() != null ? feed.getAtoQty() :null)
.confirmQty(feed.getConfirmQty())
.dataSource(1)
.build();
result.add(vo);
}
}
}
}
// ===== Step 3: 如果步骤2没有数据从机台投料配置表查询 =====
// SELECT t1.material_name, t1.material_code, t1.mat_type, 其它字段null
// FROM tba_mach_mat t, tba_mach_mat_detail t1
// WHERE t.id = t1.mach_mat_id AND t.enabled_status = 1 AND t1.is_input_mtrl = '1'
if (result.isEmpty()) {
List<MachMatDO> machMats = machMatMapper.selectList(
new LambdaQueryWrapperX<MachMatDO>()
.eq(MachMatDO::getMachineId, machineId)
.eq(MachMatDO::getEnabledStatus, 0)
);
for (MachMatDO mm : machMats) {
List<MachMatDetailDO> details = machMatDetailMapper.selectList(
new LambdaQueryWrapperX<MachMatDetailDO>()
.eq(MachMatDetailDO::getMachMatId, mm.getId())
.eq(MachMatDetailDO::getIsInputMtrl, "1")
);
for (MachMatDetailDO md : details) {
// 根据物料id查询物料表获取规格型号
MaterialDO material = md.getMaterialId() != null
? materialMapper.selectById(md.getMaterialId()) : null;
MatFeedExecuteMatConfigVO vo = MatFeedExecuteMatConfigVO.builder()
.id(md.getId())
.materialId(md.getMaterialId())
.materialCode(md.getMaterialCode())
.materialName(md.getMaterialName())
.matTypeRaw(md.getMatType())
.matType(convertMatTypeName(md.getMatType()))
.spec(material != null ? material.getSpec() : null)
.unit(md.getUnit())
.atoQty(null)
.confirmQty(null)
.dataSource(2)
.build();
result.add(vo);
}
}
}
// 按物料编码升序排列
result.sort(Comparator.comparing(MatFeedExecuteMatConfigVO::getMaterialCode,
Comparator.nullsLast(String::compareTo)));
return success(result);
}
// ==================== 4投料信息查询(表3) ====================
@GetMapping("/feed-info")
@Operation(summary = "查询投料信息 - 表3")
public CommonResult<List<MatFeedExecuteFeedInfoVO>> getFeedInfo(
@RequestParam(value = "planNo", required = false) String planNo,
@RequestParam("proDate") String proDate,
@RequestParam("shiftCd") String shiftCd,
@RequestParam("machineId") Integer machineId) {
List<MatFeedExecuteFeedInfoVO> result = new ArrayList<>();
LocalDate date = LocalDate.parse(proDate);
// 通过日期+班次+机台+planNo查询抬头单
HeadNoDO headNo = headNoMapper.selectOne(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getMachineId, machineId)
.eq(HeadNoDO::getProDate, date)
.eq(HeadNoDO::getShiftCd, shiftCd)
.eqIfPresent(HeadNoDO::getPlanNo, planNo)
);
if (headNo == null) {
return success(result);
}
// 查询投料主表
List<MatFeedDO> feedList = matFeedMapper.selectList(
new LambdaQueryWrapperX<MatFeedDO>()
.eq(MatFeedDO::getHeadId, headNo.getId())
);
for (MatFeedDO feed : feedList) {
// 查询投料明细
List<MatFeedDetailDO> details = matFeedDetailMapper.selectList(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.eq(MatFeedDetailDO::getFeedId, feed.getId())
);
for (MatFeedDetailDO detail : details) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
MatFeedExecuteFeedInfoVO vo = MatFeedExecuteFeedInfoVO.builder()
.id(detail.getId())
.materialId(detail.getMaterialId())
.materialName(detail.getMaterialName())
.matTypeRaw(detail.getMatType())
.matType(convertMatTypeName(detail.getMatType()))
.confirmQty(detail.getConfirmQty())
.feedQty(detail.getFeedQty())
.lotNo(detail.getLotNo())
.spec(detail.getSpec())
.storeHouseName(detail.getStoreHouseName())
.storeAreaName(detail.getStoreAreaName())
.storeHouseId(detail.getStoreHouseId())
.storeAreaId(detail.getStoreAreaId())
.materialCode(detail.getMaterialCode())
.planNo(feed.getPlanNo())
.feedEmpName(detail.getFeedEmpName())
.feedTime(detail.getFeedTime() != null ? detail.getFeedTime().format(dtf) : null)
.feedNo(detail.getFeedNo())
.build();
result.add(vo);
}
}
return success(result);
}
// ==================== 5弹窗-库存信息查询 ====================
@GetMapping("/stock-info")
@Operation(summary = "弹窗-查询库存信息(根据物料配置列表查原材料的库存)")
public CommonResult<List<MatFeedExecuteStockVO>> getStockInfo(
@RequestParam("machineId") Integer machineId,
@RequestParam("proDate") String proDate,
@RequestParam("shiftCd") String shiftCd,
@RequestParam("planNo") String planNo) {
List<MatFeedExecuteStockVO> result = new ArrayList<>();
LocalDate date = LocalDate.parse(proDate);
// 先获取当前物料配置列表(确认数量来源)
List<MatFeedExecuteMatConfigVO> matConfigList = this.getMatConfig(planNo, proDate, shiftCd, machineId)
.getData();
if (matConfigList == null || matConfigList.isEmpty()) {
return success(result);
}
// 构建物料ID->确认数量的映射
Map<Integer, BigDecimal> confirmQtyMap = new LinkedHashMap<>();
for (MatFeedExecuteMatConfigVO config : matConfigList) {
confirmQtyMap.put(config.getMaterialId(), config.getConfirmQty() != null ?
config.getConfirmQty() : BigDecimal.ZERO);
}
int rowNum = 0;
// 对每个物料配置 twm_pro_storage_inventory 查询原材料库存
for (MatFeedExecuteMatConfigVO config : matConfigList) {
rowNum++;
BigDecimal confirmQty = confirmQtyMap.getOrDefault(config.getMaterialId(), BigDecimal.ZERO);
// TODO: 从存货台账(twm_pro_storage_inventory)或原料库存表查询该物料的库存记录
// 这里先返回基础信息后续可根据实际库存表结构调整查询逻辑
MatFeedExecuteStockVO stock = MatFeedExecuteStockVO.builder()
.id(rowNum)
.rowNum(rowNum)
.materialName(config.getMaterialName())
.matType(config.getMatType())
.matTypeRaw(config.getMatTypeRaw())
.spec(config.getSpec())
.lotNo("")
.stockQty(BigDecimal.ZERO) // 需要从实际库存表查询
.feedQty(confirmQty) // 默认填入确认数量可手动修改
.storeHouseName("") // 需要从实际库存表查询
.storeAreaName("") // 需要从实际库存表查询
.materialCode(config.getMaterialCode())
.earliestInDate(null) // 需要从实际库存表查询
.inventBillNo("")
.materialId(config.getMaterialId())
.confirmQty(confirmQty)
.build();
result.add(stock);
}
return success(result);
}
// ==================== 6物料配置确认(表2确认) ====================
@PostMapping("/confirm-mat-config")
@Operation(summary = "物料配置确认 - 写入tpo_mat_feed")
@Transactional(rollbackFor = Exception.class)
public CommonResult<Boolean> confirmMatConfig(@Valid @RequestBody MatFeedExecuteConfirmReqVO reqVO) {
// (1) 校验产线/机台
if (reqVO.getLineId() == null || reqVO.getMachineId() == null) {
return CommonResult.error(400, "产线、机台不能为空,请确认!");
}
// (2) 校验日期/班次
if (reqVO.getProDate() == null || reqVO.getProDate().isEmpty()
|| reqVO.getShiftCd() == null || reqVO.getShiftCd().isEmpty()) {
return CommonResult.error(400, "生产日期或班次不能为空,请确认!");
}
LocalDate proDate = LocalDate.parse(reqVO.getProDate());
Integer machineId = reqVO.getMachineId();
String planNo = reqVO.getPlanNo();
// (3) 校验物料配置列表不为空
if (reqVO.getMatConfigItems() == null || reqVO.getMatConfigItems().isEmpty()) {
return CommonResult.error(400, "物料配置信息列表为空,请查询数据!");
}
// (4) 循环管控确认数量不能为空或0
for (MatFeedExecuteConfirmReqVO.MatConfigItem item : reqVO.getMatConfigItems()) {
if (item.getConfirmQty() == null || item.getConfirmQty().compareTo(BigDecimal.ZERO) <= 0) {
return CommonResult.error(400,
"该物料(" + item.getMaterialName() + ")投料数量等于0请确认");
}
}
// (5) 校验机台计划状态存在'执行中'
List<HeadNoDO> executingHeads = headNoMapper.selectList(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getMachineId, machineId)
.eq(HeadNoDO::getProStatus, "2")
);
if (executingHeads == null || executingHeads.isEmpty()) {
return CommonResult.error(400, "当前机台没有执行中的计划,请确认");
}
// (6) 通过机台编码+日期+班次+计划号查询抬头单
HeadNoDO headNo = headNoMapper.selectOne(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getProDate, proDate)
.eq(HeadNoDO::getShiftCd, reqVO.getShiftCd())
.eqIfPresent(HeadNoDO::getMachineCd, reqVO.getMachineCd())
.eq(HeadNoDO::getPlanNo, planNo)
);
if (headNo == null) {
return CommonResult.error(400, "未找到对应的抬头单记录,请确认");
}
// 通过head_no查询tpo_mat_feed和tpo_mat_feed_detail是否已有数据
List<MatFeedDO> existingFeeds = matFeedMapper.selectList(
new LambdaQueryWrapperX<MatFeedDO>()
.eq(MatFeedDO::getHeadId, headNo.getId())
);
List<MatFeedDetailDO> existingDetails = Collections.emptyList();
if (!existingFeeds.isEmpty()) {
List<Integer> feedIds = existingFeeds.stream()
.map(MatFeedDO::getId).collect(Collectors.toList());
existingDetails = matFeedDetailMapper.selectList(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.in(MatFeedDetailDO::getFeedId, feedIds)
);
}
boolean hasExistingData = !existingDetails.isEmpty();
if (hasExistingData) {
return CommonResult.error(400,
"当前计划(" + planNo + "),当前日期、班次已投料,不允许修改!");
}
// 先根据抬头单号删除 tpo_mat_feed tpo_mat_feed_detail
for (MatFeedDO feed : existingFeeds) {
matFeedDetailMapper.delete(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.eq(MatFeedDetailDO::getFeedId, feed.getId())
);
matFeedMapper.deleteById(feed.getId());
}
// ========== 所有校验通过开始写入 tpo_mat_feed ==========
for (MatFeedExecuteConfirmReqVO.MatConfigItem item : reqVO.getMatConfigItems()) {
MaterialDO material = item.getMaterialId() != null ?
materialMapper.selectById(item.getMaterialId()) : null;
MatFeedDO matFeed = MatFeedDO.builder()
.planId(headNo.getPlanId())
.planNo(planNo)
.headNo(headNo.getHeadNo())
.headId(headNo.getId())
.atoQty(item.getAtoQty())
.confirmQty(item.getConfirmQty())
.materialId(item.getMaterialId())
.materialCode(item.getMaterialCode())
.materialName(item.getMaterialName())
.unit(material != null ? material.getUnit() : item.getUnit())
.matType(material != null ? material.getMatType() : item.getMatTypeRaw())
.spec(material != null ? material.getSpec() : item.getSpec())
.build();
matFeedMapper.insert(matFeed);
}
return success(true);
}
// ==================== 7库存消耗(表3/弹窗确认) ====================
@PostMapping("/consume-stock")
@Operation(summary = "库存消耗确认 - 写入投料单号和出库流水")
@Transactional(rollbackFor = Exception.class)
public CommonResult<Boolean> consumeStock(@Valid @RequestBody MatFeedExecuteFeedReqVO reqVO) {
// (1) 校验产线/机台
if (reqVO.getLineId() == null || reqVO.getMachineId() == null) {
return CommonResult.error(400, "产线、机台不能为空,请确认!");
}
// (2) 校验日期/班次
if (reqVO.getProDate() == null || reqVO.getProDate().isEmpty()
|| reqVO.getShiftCd() == null || reqVO.getShiftCd().isEmpty()) {
return CommonResult.error(400, "生产日期或班次不能为空,请确认!");
}
LocalDate proDate = LocalDate.parse(reqVO.getProDate());
Integer machineId = reqVO.getMachineId();
String planNo = reqVO.getPlanNo();
// (3) 校验投料列表不为空
if (reqVO.getFeedItems() == null || reqVO.getFeedItems().isEmpty()) {
return CommonResult.error(400, "投料信息列表为空,请刷新数据!");
}
// (4) 循环校验投料数量 > 0
for (MatFeedExecuteFeedReqVO.FeedItem item : reqVO.getFeedItems()) {
if (item.getFeedQty() == null || item.getFeedQty().compareTo(BigDecimal.ZERO) <= 0) {
return CommonResult.error(400,
"该物料(" + item.getMaterialName() + ")投料数量等于0请确认");
}
}
// (5) 查询抬头单
HeadNoDO headNo = headNoMapper.selectOne(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getMachineId, machineId)
.eq(HeadNoDO::getProDate, proDate)
.eq(HeadNoDO::getShiftCd, reqVO.getShiftCd())
.eq(HeadNoDO::getPlanNo, planNo)
);
if (headNo == null) {
return CommonResult.error(400, "当前计划抬头单不存在,请确认");
}
// ========== 开始写入 ==========
LocalDateTime now = LocalDateTime.now();
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String nickname = "";
if (loginUserId != null) {
AdminUserDO user = adminUserMapper.selectById(loginUserId);
nickname = user != null ? user.getNickname() : "";
}
// 生成投料单号 F + 年月日 + 2位流水号
String feedNo = generateFeedNo();
// 更新/写入 tpo_mat_feed_detail 的投料信息
for (MatFeedExecuteFeedReqVO.FeedItem item : reqVO.getFeedItems()) {
MaterialDO material = item.getMaterialId() != null ?
materialMapper.selectById(item.getMaterialId()) : null;
String inventBillNo = generateInventBillNo();
// 查找对应的 mat_feed_detail 记录并更新
List<MatFeedDetailDO> existingDetails = matFeedDetailMapper.selectList(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.eq(MatFeedDetailDO::getHeadId, headNo.getId())
.eq(MatFeedDetailDO::getMaterialId, item.getMaterialId())
);
MatFeedDetailDO detailToUpdate;
if (!existingDetails.isEmpty()) {
// 更新现有记录
detailToUpdate = existingDetails.get(0);
detailToUpdate.setFeedNo(feedNo);
detailToUpdate.setFeedQty(item.getFeedQty());
detailToUpdate.setLotNo(item.getLotNo());
detailToUpdate.setStoreHouseId(item.getStoreHouseId());
detailToUpdate.setStoreHouseName(item.getStoreHouseName());
detailToUpdate.setStoreAreaId(item.getStoreAreaId());
detailToUpdate.setStoreAreaName(item.getStoreAreaName());
detailToUpdate.setFeedEmpId(loginUserId != null ? loginUserId.intValue() : null);
detailToUpdate.setFeedEmpName(nickname);
detailToUpdate.setFeedTime(now);
detailToUpdate.setInventBillNo(inventBillNo);
matFeedDetailMapper.updateById(detailToUpdate);
} else {
// 新增记录
detailToUpdate = MatFeedDetailDO.builder()
.feedNo(feedNo)
.feedId(null)
.materialId(item.getMaterialId())
.materialCode(material != null ? material.getMatCode() : item.getMaterialCode())
.materialName(item.getMaterialName())
.spec(material != null ? material.getSpec() : item.getSpec())
.unit(material != null ? material.getUnit() : null)
.matType(item.getMatType())
.feedQty(item.getFeedQty())
.lotNo(item.getLotNo())
.storeHouseId(item.getStoreHouseId())
.storeHouseName(item.getStoreHouseName())
.storeAreaId(item.getStoreAreaId())
.storeAreaName(item.getStoreAreaName())
.feedEmpId(loginUserId != null ? loginUserId.intValue() : null)
.feedEmpName(nickname)
.feedTime(now)
.headNo(headNo.getHeadNo())
.headId(headNo.getId())
.planId(headNo.getPlanId())
.planNo(planNo)
.confirmQty(item.getConfirmQty())
.inventBillNo(inventBillNo)
.build();
matFeedDetailMapper.insert(detailToUpdate);
}
// TODO: 写入出库流水 twm_raw_storage_log (operatorType='2', businessType='21')
}
return success(true);
}
/**
* 生成投料单号
* 格式: F + 年份(4位) + (2位) + (2位) + 2位流水号
*/
private String generateFeedNo() {
String ym = String.format("%tY%<tm%<td", LocalDate.now());
String maxFeedNo = matFeedDetailMapper.selectMaxFeedNo();
if (maxFeedNo == null || maxFeedNo.length() < 11
|| !ym.equals(maxFeedNo.substring(1, 9))) {
return "F" + ym + "01";
}
String prefix = maxFeedNo.substring(0, 9);
int seqNum = Integer.parseInt(maxFeedNo.substring(maxFeedNo.length() - 2)) + 1;
return prefix + String.format("%02d", seqNum);
}
/**
* 生成存货账单号
*/
private String generateInventBillNo() {
// TODO: 根据实际业务实现
return "CF" + String.format("%tY%<tm%<td", LocalDate.now()) + String.format("%02d",
new Random().nextInt(99) + 1);
}
// ==================== 8取消(表3取消) ====================
@PutMapping("/cancel-feed")
@Operation(summary = "投料信息取消")
@Transactional(rollbackFor = Exception.class)
public CommonResult<Boolean> cancelFeed(@Valid @RequestBody MatFeedExecuteCancelReqVO reqVO) {
// (1)(2) 基础校验
if (reqVO.getLineId() == null || reqVO.getMachineId() == null) {
return CommonResult.error(400, "产线、机台不能为空,请确认!");
}
if (reqVO.getProDate() == null || reqVO.getProDate().isEmpty()
|| reqVO.getShiftCd() == null || reqVO.getShiftCd().isEmpty()) {
return CommonResult.error(400, "生产日期或班次不能为空,请确认!");
}
String planNo = reqVO.getPlanNo();
Integer machineId = reqVO.getMachineId();
LocalDate proDate = LocalDate.parse(reqVO.getProDate());
// (3) 校验planNo
if (planNo == null || planNo.isEmpty()) {
return CommonResult.error(400, "选择执行计划为空,请选择!");
}
// (4) 查询抬头单
HeadNoDO headNo = headNoMapper.selectOne(
new LambdaQueryWrapperX<HeadNoDO>()
.eq(HeadNoDO::getMachineId, machineId)
.eq(HeadNoDO::getProDate, proDate)
.eq(HeadNoDO::getShiftCd, reqVO.getShiftCd())
.eq(HeadNoDO::getPlanNo, planNo)
);
if (headNo == null) {
return CommonResult.error(400, "当前计划不存在抬头单,请刷新界面");
}
// 删除 tpo_mat_feed tpo_mat_feed_detail
List<MatFeedDO> feedList = matFeedMapper.selectList(
new LambdaQueryWrapperX<MatFeedDO>()
.eq(MatFeedDO::getHeadId, headNo.getId())
);
for (MatFeedDO feed : feedList) {
matFeedDetailMapper.delete(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.eq(MatFeedDetailDO::getFeedId, feed.getId())
);
matFeedMapper.deleteById(feed.getId());
}
// 同时删除无feedId关联的detail记录兜底
matFeedDetailMapper.delete(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.eq(MatFeedDetailDO::getHeadId, headNo.getId())
.isNull(MatFeedDetailDO::getFeedId)
);
return success(true);
}
// ==================== 内部方法 ====================
/**
* 物料类型编码转名称
*/
private String convertMatTypeName(String matType) {
if (matType == null) return "";
switch (matType) {
case "1": return "原材料";
case "2": return "半成品";
case "3": return "成品";
case "4": return "联产品";
case "6": return "废料";
default: return matType;
}
}
}

View File

@ -0,0 +1,54 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 投料执行 - 取消(表3取消) Request VO
*/
@Schema(description = "管理后台 - 投料执行/投料取消请求VO")
@Data
public class MatFeedExecuteCancelReqVO {
@Schema(description = "产线ID", required = true)
@NotNull(message = "产线不能为空")
private Integer lineId;
@Schema(description = "产线名称")
private String lineName;
@Schema(description = "产线编码")
private String lineCd;
@Schema(description = "机台ID", required = true)
@NotNull(message = "机台不能为空")
private Integer machineId;
@Schema(description = "机台名称")
private String machineName;
@Schema(description = "机台编码")
private String machineCd;
@Schema(description = "生产日期", required = true)
@NotNull(message = "生产日期不能为空")
private String proDate;
@Schema(description = "班次", required = true)
@NotNull(message = "班次不能为空")
private String shiftCd;
@Schema(description = "班组")
private String groupCd;
@Schema(description = "生产计划号", required = true)
@NotNull(message = "生产计划号不能为空")
private String planNo;
@Schema(description = "生产计划ID")
private Integer planId;
}

View File

@ -0,0 +1,85 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.List;
/**
* 投料执行 - 确认(表2确认) Request VO
*/
@Schema(description = "管理后台 - 投料执行/物料配置确认请求VO")
@Data
public class MatFeedExecuteConfirmReqVO {
@Schema(description = "产线ID", required = true, example = "1")
@NotNull(message = "产线不能为空")
private Integer lineId;
@Schema(description = "产线名称", example = "1号线")
private String lineName;
@Schema(description = "产线编码")
private String lineCd;
@Schema(description = "机台ID", required = true, example = "1")
@NotNull(message = "机台不能为空")
private Integer machineId;
@Schema(description = "机台名称", example = "磨机01")
private String machineName;
@Schema(description = "机台编码")
private String machineCd;
@Schema(description = "生产日期", required = true, example = "2026-05-14")
@NotNull(message = "生产日期不能为空")
private String proDate;
@Schema(description = "班次", required = true, example = "")
@NotNull(message = "班次不能为空")
private String shiftCd;
@Schema(description = "班组")
private String groupCd;
@Schema(description = "生产计划号", required = true)
@NotNull(message = "生产计划号不能为空")
private String planNo;
@Schema(description = "生产计划ID")
private Integer planId;
@Schema(description = "物料配置明细列表表2所有行")
private List<MatConfigItem> matConfigItems;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "物料配置明细项")
public static class MatConfigItem {
@Schema(description = "ID")
private Integer id;
@Schema(description = "物料ID")
private Integer materialId;
@Schema(description = "物料名称")
private String materialName;
@Schema(description = "物料编码")
private String materialCode;
@Schema(description = "规格")
private String spec;
@Schema(description = "单位")
private String unit;
@Schema(description = "确认数量")
private BigDecimal confirmQty;
@Schema(description = "采集数量")
private BigDecimal atoQty;
@Schema(description = "数据来源 1=已投料数据 2=机台配置投入物料")
private Integer dataSource;
@Schema(description = "物料类型原始值")
private String matTypeRaw;
}
}

View File

@ -0,0 +1,72 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 投料执行 - 投料信息(表3) VO
*/
@Schema(description = "管理后台 - 投料执行/投料信息VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MatFeedExecuteFeedInfoVO {
@Schema(description = "ID")
private Integer id;
@Schema(description = "物料ID")
private Integer materialId;
@Schema(description = "物料名称")
private String materialName;
@Schema(description = "物料类型(原始值)")
private String matTypeRaw;
@Schema(description = "物料类型(显示名称)")
private String matType;
@Schema(description = "确认数量(来自物料配置的确认数量)")
private BigDecimal confirmQty;
@Schema(description = "投料数量")
private BigDecimal feedQty;
@Schema(description = "批次号")
private String lotNo;
@Schema(description = "规格型号")
private String spec;
@Schema(description = "仓库名称")
private String storeHouseName;
@Schema(description = "库区名称")
private String storeAreaName;
@Schema(description = "仓库ID")
private Integer storeHouseId;
@Schema(description = "库区ID")
private Integer storeAreaId;
@Schema(description = "物料编码")
private String materialCode;
@Schema(description = "计划号")
private String planNo;
@Schema(description = "投料人")
private String feedEmpName;
@Schema(description = "投料时间")
private String feedTime;
@Schema(description = "投料单号")
private String feedNo;
}

View File

@ -0,0 +1,91 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.NotNull;
/**
* 投料执行 - 库存消耗(表3库存消耗) Request VO
*/
@Schema(description = "管理后台 - 投料执行/库存消耗请求VO")
@Data
public class MatFeedExecuteFeedReqVO {
@Schema(description = "产线ID", required = true)
@NotNull(message = "产线不能为空")
private Integer lineId;
@Schema(description = "产线名称")
private String lineName;
@Schema(description = "产线编码")
private String lineCd;
@Schema(description = "机台ID", required = true)
@NotNull(message = "机台不能为空")
private Integer machineId;
@Schema(description = "机台名称")
private String machineName;
@Schema(description = "机台编码")
private String machineCd;
@Schema(description = "生产日期", required = true)
@NotNull(message = "生产日期不能为空")
private String proDate;
@Schema(description = "班次", required = true)
@NotNull(message = "班次不能为空")
private String shiftCd;
@Schema(description = "班组")
private String groupCd;
@Schema(description = "生产计划号", required = true)
@NotNull(message = "生产计划号不能为空")
private String planNo;
@Schema(description = "生产计划ID")
private Integer planId;
@Schema(description = "投料信息列表弹窗中表2-库存信息,投料数量手动填写)")
private java.util.List<FeedItem> feedItems;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "投料信息明细项")
public static class FeedItem {
@Schema(description = "ID库存记录或mat_feed_detail的id")
private Integer id;
@Schema(description = "物料ID")
private Integer materialId;
@Schema(description = "物料名称")
private String materialName;
@Schema(description = "物料类型")
private String matType;
@Schema(description = "规格")
private String spec;
@Schema(description = "批次号")
private String lotNo;
@Schema(description = "投料数量")
private java.math.BigDecimal feedQty;
@Schema(description = "仓库ID")
private Integer storeHouseId;
@Schema(description = "仓库名称")
private String storeHouseName;
@Schema(description = "库区ID")
private Integer storeAreaId;
@Schema(description = "库区名称")
private String storeAreaName;
@Schema(description = "物料编码")
private String materialCode;
@Schema(description = "存货账单号")
private String inventBillNo;
@Schema(description = "确认数量")
private java.math.BigDecimal confirmQty;
}
}

View File

@ -0,0 +1,56 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDate;
import java.util.List;
/**
* 投料执行 - 界面初始化 VO
*/
@Schema(description = "管理后台 - 投料执行/界面初始化VO")
@Data
public class MatFeedExecuteInitVO {
@Schema(description = "产线列表", example = "[]")
private List<LineOption> lineList;
@Schema(description = "机台列表", example = "[]")
private List<MachineOption> machineList;
@Schema(description = "默认生产日期", example = "2026-05-14")
private LocalDate defaultDate;
@Schema(description = "默认班次", example = "")
private String shiftCd;
@Schema(description = "默认班组", example = "A组")
private String groupCd;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "产线选项")
public static class LineOption {
@Schema(description = "产线ID")
private Integer lineId;
@Schema(description = "产线名称")
private String lineName;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "机台选项")
public static class MachineOption {
@Schema(description = "机台ID")
private Integer machineId;
@Schema(description = "机台名称")
private String machineName;
@Schema(description = "产线ID")
private Integer lineId;
}
}

View File

@ -0,0 +1,50 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
/**
* 投料执行 - 物料配置(表2) VO
*/
@Schema(description = "管理后台 - 投料执行/物料配置VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MatFeedExecuteMatConfigVO {
@Schema(description = "ID来自tba_mach_mat_detail")
private Integer id;
@Schema(description = "物料ID")
private Integer materialId;
@Schema(description = "物料编码")
private String materialCode;
@Schema(description = "物料名称")
private String materialName;
@Schema(description = "物料类型(原始值)")
private String matTypeRaw;
@Schema(description = "物料类型(显示名称)")
private String matType;
@Schema(description = "规格型号")
private String spec;
@Schema(description = "单位")
private String unit;
@Schema(description = "采集数量")
private BigDecimal atoQty;
@Schema(description = "确认数量")
private BigDecimal confirmQty;
@Schema(description = "数据来源 1=已投料数据 2=机台配置投入物料")
private Integer dataSource;
}

View File

@ -0,0 +1,71 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 投料执行 - 生产计划(表1) VO
*/
@Schema(description = "管理后台 - 投料执行/生产计划VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MatFeedExecutePlanVO {
@Schema(description = "计划ID")
private Integer id;
@Schema(description = "生产计划号")
private String planNo;
@Schema(description = "机台计划状态")
private String machineProStatus;
@Schema(description = "计划状态")
private String planStatus;
@Schema(description = "产品名称")
private String materialName;
@Schema(description = "规格型号")
private String spec;
@Schema(description = "计划数量")
private BigDecimal planQty;
@Schema(description = "完成数量")
private BigDecimal completeQty;
@Schema(description = "计划开始日期")
private LocalDate planBgDate;
@Schema(description = "计划结束日期")
private LocalDate planEndDate;
@Schema(description = "工艺流程")
private String techProc;
@Schema(description = "计划日期(生产日期)")
private LocalDate proDate;
@Schema(description = "机台编码")
private String machineCd;
@Schema(description = "机台名称")
private String machineName;
@Schema(description = "机台ID")
private Integer machineId;
@Schema(description = "产线名称")
private String lineName;
@Schema(description = "产线ID")
private Integer lineId;
@Schema(description = "是否正在执行(true=底色浅蓝色)")
private Boolean isExecuting;
}

View File

@ -0,0 +1,72 @@
package com.ningxia.yunxi.chemmes.module.biz.controller.admin.matfeedexecute.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 投料执行 - 弹窗库存信息 VO
*/
@Schema(description = "管理后台 - 投料执行/弹窗库存信息VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MatFeedExecuteStockVO {
@Schema(description = "ID")
private Integer id;
@Schema(description = "序号(前端展示用)")
private Integer rowNum;
@Schema(description = "物料名称")
private String materialName;
@Schema(description = "物料类型(显示名称)")
private String matType;
@Schema(description = "物料类型(原始值)")
private String matTypeRaw;
@Schema(description = "规格")
private String spec;
@Schema(description = "批次号")
private String lotNo;
@Schema(description = "库存数量")
private BigDecimal stockQty;
@Schema(description = "投料数量(可编辑)")
private BigDecimal feedQty;
@Schema(description = "仓库名称")
private String storeHouseName;
@Schema(description = "库区名称")
private String storeAreaName;
@Schema(description = "物料编码")
private String materialCode;
@Schema(description = "最早入库日期")
private LocalDate earliestInDate;
@Schema(description = "存货账单号")
private String inventBillNo;
@Schema(description = "物料ID")
private Integer materialId;
@Schema(description = "仓库ID")
private Integer storeHouseId;
@Schema(description = "库区ID")
private Integer storeAreaId;
@Schema(description = "确认数量来自表2")
private BigDecimal confirmQty;
}

View File

@ -17,6 +17,7 @@ import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.plan.PlanDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.planline.PlanLineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.planmachine.PlanMachineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.proline.ProLineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.rawstorageinventory.RawStorageInventoryDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.shiftresult.ShiftResultDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.usermachine.UserMachineDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.usermachine.UserMachineDetailDO;
@ -44,6 +45,7 @@ import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.plan.PlanMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.planline.PlanLineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.planmachine.PlanMachineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.proline.ProLineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.rawstorageinventory.RawStorageInventoryMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.shiftresult.ShiftResultMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.usermachine.UserMachineMapper;
import com.ningxia.yunxi.chemmes.module.biz.dal.mysql.techproc.TechProcMapper;
@ -149,6 +151,8 @@ public class MillExecuteController {
private MaterialMapper materialMapper;
@Resource
private AdminUserMapper adminUserMapper;
@Resource
private RawStorageInventoryMapper rawStorageInventoryMapper;
// ==================== 1界面初始化 ====================
@ -245,6 +249,7 @@ public class MillExecuteController {
@Operation(summary = "查询生产计划列表 - 表1")
public CommonResult<List<MillExecutePlanVO>> getPlanList(
@RequestParam("machineId") Integer machineId,
@RequestParam("lineId") Integer lineId,
@RequestParam("proDate") String proDate,
@RequestParam("shiftCd") String shiftCd,
@RequestParam(value = "groupCd", required = false) String groupCd
@ -271,11 +276,25 @@ public class MillExecuteController {
).stream().map(HeadNoDO::getPlanId).distinct().collect(Collectors.toList());
// 条件2: plan_status in ('1','2','4') 的计划ID已下发/执行中/暂停
List<Integer> statusPlanIds = planMapper.selectList(
new com.ningxia.yunxi.chemmes.framework.mybatis.core.query.LambdaQueryWrapperX<PlanDO>()
.in(PlanDO::getPlanStatus, Arrays.asList("1", "2", "4"))
.select(PlanDO::getId)
).stream().map(PlanDO::getId).collect(Collectors.toList());
// 关联 tpl_plan_line lineId 过滤
List<Integer> statusPlanIds = new ArrayList<>();
if (lineId != null) {
// 先查询指定 lineId tpl_plan_line 记录获取 pro_id 列表
List<Integer> linePlanIds = planLineMapper.selectList(
new LambdaQueryWrapperX<PlanLineDO>()
.eq(PlanLineDO::getLineId, lineId)
.select(PlanLineDO::getProId)
).stream().map(PlanLineDO::getProId).distinct().collect(Collectors.toList());
if (!linePlanIds.isEmpty()) {
statusPlanIds = planMapper.selectList(
new LambdaQueryWrapperX<PlanDO>()
.in(PlanDO::getId, linePlanIds)
.in(PlanDO::getPlanStatus, Arrays.asList("1", "2", "4"))
.select(PlanDO::getId)
).stream().map(PlanDO::getId).collect(Collectors.toList());
}
}
// 合并所有计划IDunion all语义
Set<Integer> allPlanIds = new LinkedHashSet<>();
@ -767,7 +786,7 @@ public class MillExecuteController {
return CommonResult.error(400, "该计划(" + planNo + ")不存在,请刷新数据!");
}
// (3) 校验计划状态是否为已下发(1)或暂停(4)
if (!"1".equals(plan.getPlanStatus()) && !"4".equals(plan.getPlanStatus())) {
if (!"1".equals(plan.getPlanStatus()) && !"4".equals(plan.getPlanStatus())&& !"2".equals(plan.getPlanStatus())) {
return CommonResult.error(400, "该计划(" + planNo + ")非下发或暂停状态,请确认!");
}
@ -1105,7 +1124,6 @@ public class MillExecuteController {
// (6) 判断是否存在投料或产出数据
String headNoStr = confirmHead.getHeadNo();
Long matOutCount = matOutMapper.countByHeadNo(headNoStr);
// TODO: 投料表(tpo_mat_feed)尚未创建待创建后补充投料数据检查
Long matFeedCount = matFeedMapper.countByHeadNo(headNoStr);
if ((matOutCount != null && matOutCount > 0) || matFeedCount > 0) {
return CommonResult.error(400,
@ -1237,6 +1255,9 @@ public class MillExecuteController {
// --- B2.5: 根据物料ID查询物料表获取物料信息 ---
MaterialDO material = materialMapper.selectById(item.getMaterialId());
String inventBillNo = generateInventBillNo();
// B3: 插入 tpo_mat_out_detail
MatOutDetailDO detail = MatOutDetailDO.builder()
.outNo(outNo)
@ -1257,12 +1278,12 @@ public class MillExecuteController {
.storeAreaName(wipConfig != null ? wipConfig.getStoreAreaName() : null)
.storeAreCd(wipConfig != null ? wipConfig.getStoreAreCd() : null)
.storeHouseCd(wipConfig != null ? wipConfig.getStoreHouseCd() : null)
.inventBillNo(inventBillNo)
.build();
matOutDetailMapper.insert(detail);
// --- C: 插入 twm_pro_storage_inventory (存货台账) ---
String inventBillNo = generateInventBillNo();
ProStorageInventoryDO inventory = ProStorageInventoryDO.builder()
RawStorageInventoryDO inventory = RawStorageInventoryDO.builder()
.storeHouseId(wipConfig != null ? wipConfig.getStoreHouseId() : null)
.storeAreaId(wipConfig != null ? wipConfig.getStoreAreaId() : null)
.storeHouseName(wipConfig != null ? wipConfig.getStoreHouseName() : null)
@ -1277,12 +1298,10 @@ public class MillExecuteController {
.lotNo(lotNo)
.yardQty(item.getOutQty())
.useQty(item.getOutQty())
.planId(currentHead.getPlanId())
.proNo(planNo)
.inventBillNo(inventBillNo)
.earStoreDate(LocalDate.now())
.build();
proStorageInventoryMapper.insert(inventory);
rawStorageInventoryMapper.insert(inventory);
// --- D: 插入 twm_raw_storage_log (出入库流水) ---
RawStorageLogDO storageLog = RawStorageLogDO.builder()
@ -1308,7 +1327,7 @@ public class MillExecuteController {
.relarionId(matOutId)
.relarionDetailId(detail.getId())
.operatorName(nickname)
// .inventBillNo(inventBillNo)
.inventBillNo(inventBillNo)
.build();
rawStorageLogMapper.insert(storageLog);
}
@ -1362,7 +1381,7 @@ public class MillExecuteController {
*/
private String generateInventBillNo() {
String ym = String.format("%tY%<tm%<td", LocalDate.now());
String maxBillNo = proStorageInventoryMapper.selectMaxInventBillNo();
String maxBillNo = rawStorageInventoryMapper.selectMaxBillNo();
if (maxBillNo == null || maxBillNo.length() < 10
|| !ym.equals(maxBillNo.substring(2, 10))) {
return "CH" + ym + "01";
@ -1480,9 +1499,9 @@ public class MillExecuteController {
for (MatOutDetailDO detail : detailList) {
if (detail.getInventBillNo() != null && !detail.getInventBillNo().isEmpty()) {
// 查询存货台账
ProStorageInventoryDO inventory = proStorageInventoryMapper.selectOne(
new LambdaQueryWrapperX<ProStorageInventoryDO>()
.eq(ProStorageInventoryDO::getInventBillNo, detail.getInventBillNo())
RawStorageInventoryDO inventory = rawStorageInventoryMapper.selectOne(
new LambdaQueryWrapperX<RawStorageInventoryDO>()
.eq(RawStorageInventoryDO::getInventBillNo, detail.getInventBillNo())
);
if (inventory != null) {
BigDecimal yardQty = inventory.getYardQty() != null ? inventory.getYardQty() : BigDecimal.ZERO;
@ -1624,7 +1643,7 @@ public class MillExecuteController {
case "2": return "半成品";
case "3": return "成品";
case "4": return "联产品";
case "6": return "回收";
case "6": return "";
default: return matType;
}
}

View File

@ -1,5 +1,6 @@
package com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.matfeed;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.dataobject.BaseDOWithoutLogic;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
@ -22,7 +23,7 @@ import com.ningxia.yunxi.chemmes.framework.mybatis.core.dataobject.BaseDO;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MatFeedDO extends BaseDO {
public class MatFeedDO extends BaseDOWithoutLogic {
/**
* 自增字段

View File

@ -158,7 +158,10 @@ public class RawStorageLogDO extends BaseDOWithoutLogic {
* 关联子单号id
*/
private Integer relarionDetailId;
private String inventBillNo;
}

View File

@ -51,4 +51,16 @@ public interface MatFeedDetailMapper extends BaseMapperX<MatFeedDetailDO> {
.orderByDesc(MatFeedDetailDO::getId));
}
/**
* 查询最大的投料单号
*/
default String selectMaxFeedNo() {
List<MatFeedDetailDO> list = selectList(
new LambdaQueryWrapperX<MatFeedDetailDO>()
.isNotNull(MatFeedDetailDO::getFeedNo)
.orderByDesc(MatFeedDetailDO::getFeedNo)
.last("LIMIT 1")
);
return (list != null && !list.isEmpty()) ? list.get(0).getFeedNo() : null;
}
}

View File

@ -4,6 +4,7 @@ import com.ningxia.yunxi.chemmes.framework.common.pojo.PageResult;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.mapper.BaseMapperX;
import com.ningxia.yunxi.chemmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.ningxia.yunxi.chemmes.module.biz.controller.admin.rawstorageinventory.vo.RawStorageInventoryPageReqVO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.prostorageinventory.ProStorageInventoryDO;
import com.ningxia.yunxi.chemmes.module.biz.dal.dataobject.rawstorageinventory.RawStorageInventoryDO;
import org.apache.ibatis.annotations.Mapper;

View File

@ -0,0 +1,249 @@
import request from '@/config/axios'
// ================== 初始化相关 ==================
export interface LineOption {
lineId: number
lineName: string
}
export interface MachineOption {
machineId: number
machineName: string
lineId?: number
}
export interface MatFeedExecuteInitVO {
lineList: LineOption[]
machineList: MachineOption[]
defaultDate: string
shiftCd: string
groupCd: string
}
// 初始化接口
export const getMatFeedExecuteInit = async (): Promise<MatFeedExecuteInitVO> => {
return await request.get({ url: '/tpo/mat-feed-execute/init' })
}
// ================== 生产计划表1相关 ==================
export interface MatFeedExecutePlanVO {
id: number
planNo: string
machineProStatus: string
planStatus: string
materialName: string
spec: string
planQty: number
completeQty: number
planBgDate: string
planEndDate: string
techProc: string
proDate: string
machineCd: string
machineName: string
machineId: number
lineName: string
lineId: number
lineCd: string
isExecuting: boolean
}
// 生产计划查询
export const getPlanList = async (params: {
machineId?: number
lineId?: number
proDate?: string
shiftCd?: string
groupCd?: string
}): Promise<MatFeedExecutePlanVO[]> => {
return await request.get({ url: '/tpo/mat-feed-execute/plan-list', params })
}
// ================== 物料配置表2相关 ==================
export interface MatFeedExecuteMatConfigVO {
id: number
materialId: number
materialCode: string
materialName: string
matTypeRaw: string
matType: string
spec: string
unit: string
atoQty: number
confirmQty: number | string
dataSource: number // 1=已投料数据 2=机台配置投入物料
}
// 物料配置查询
export const getMatConfig = async (params: {
machineId: number
planNo?: string
proDate: string
shiftCd: string
}): Promise<MatFeedExecuteMatConfigVO[]> => {
return await request.get({ url: '/tpo/mat-feed-execute/mat-config', params })
}
// ================== 投料信息表3相关 ==================
export interface MatFeedExecuteFeedInfoVO {
id: number
materialId: number
materialName: string
matTypeRaw: string
matType: string
confirmQty: number
feedQty: number
lotNo: string
spec: string
storeHouseName: string
storeAreaName: string
storeHouseId: number
storeAreaId: number
materialCode: string
planNo: string
feedEmpName: string
feedTime: string
feedNo: string
}
// 投料信息查询
export const getFeedInfo = async (params: {
machineId: number
planNo?: string
proDate: string
shiftCd: string
}): Promise<MatFeedExecuteFeedInfoVO[]> => {
return await request.get({ url: '/tpo/mat-feed-execute/feed-info', params })
}
// ================== 弹窗库存信息相关 ==================
export interface MatFeedExecuteStockVO {
id: number
rowNum: number
materialName: string
matType: string
matTypeRaw: string
spec: string
lotNo: string
stockQty: number
feedQty: number | string // 可编辑,手动修改
storeHouseName: string
storeAreaName: string
materialCode: string
earliestInDate: string
inventBillNo: string
materialId: number
storeHouseId: number
storeAreaId: number
confirmQty: number
}
// 弹窗库存信息查询
export const getStockInfo = async (params: {
machineId: number
proDate: string
shiftCd: string
planNo: string
}): Promise<MatFeedExecuteStockVO[]> => {
return await request.get({ url: '/tpo/mat-feed-execute/stock-info', params })
}
// ================== 物料配置确认接口 ==================
/** 物料配置确认 - 单条明细 */
export interface MatConfigItem {
id: number
materialId: number
materialName?: string
materialCode?: string
spec?: string
unit?: string
confirmQty: number | string
atoQty?: number
dataSource?: number
matTypeRaw?: string
}
/** 物料配置确认 Request VO */
export interface MatFeedConfirmReqVO {
lineId: number
lineName?: string
lineCd?: string
machineId: number
machineName?: string
machineCd?: string
proDate: string
shiftCd: string
groupCd?: string
planNo: string
planId?: number
matConfigItems: MatConfigItem[]
}
// 物料配置确认写入tpo_mat_feed + tpo_mat_feed_detail
export const confirmMatConfig = async (data: MatFeedConfirmReqVO): Promise<void> => {
return await request.post({ url: '/tpo/mat-feed-execute/confirm-mat-config', data })
}
// ================== 库存消耗(投料)接口 ==================
/** 库存消耗 - 单条明细 */
export interface FeedItem {
id: number
materialId: number
materialName?: string
matType?: string
spec?: string
lotNo?: string
feedQty: number | string
storeHouseId?: number
storeHouseName?: string
storeAreaId?: number
storeAreaName?: string
materialCode?: string
inventBillNo?: string
confirmQty?: number
}
/** 库存消耗 Request VO */
export interface MatFeedFeedReqVO {
lineId: number
lineName?: string
lineCd?: string
machineId: number
machineName?: string
machineCd?: string
proDate: string
shiftCd: string
groupCd?: string
planNo: string
planId?: number
feedItems: FeedItem[]
}
// 库存消耗确认(生成投料单号 + 写入出库流水)
export const consumeStock = async (data: MatFeedFeedReqVO): Promise<void> => {
return await request.post({ url: '/tpo/mat-feed-execute/consume-stock', data })
}
// ================== 取消接口 ==================
/** 取消 Request VO */
export interface MatFeedCancelReqVO {
lineId: number
lineName?: string
lineCd?: string
machineId: number
machineName?: string
machineCd?: string
proDate: string
shiftCd: string
groupCd?: string
planNo: string
planId?: number
}
// 投料取消
export const cancelFeed = async (data: MatFeedCancelReqVO): Promise<void> => {
return await request.put({ url: '/tpo/mat-feed-execute/cancel-feed', data })
}

View File

@ -59,6 +59,7 @@ export interface MillExecutePlanVO {
// 生产计划查询
export const getPlanList = async (params: {
machineId?: number
lineId?: number
proDate?: string
shiftCd?: string
groupCd?: string

View File

@ -19,7 +19,6 @@ declare module 'vue' {
ContentDetailWrap: typeof import('./../components/ContentDetailWrap/src/ContentDetailWrap.vue')['default']
ContentWrap: typeof import('./../components/ContentWrap/src/ContentWrap.vue')['default']
CopperModal: typeof import('./../components/Cropper/src/CopperModal.vue')['default']
copy: typeof import('./../views/biz/purreceipt copy/index.vue')['default']
CountTo: typeof import('./../components/CountTo/src/CountTo.vue')['default']
Crontab: typeof import('./../components/Crontab/src/Crontab.vue')['default']
Cropper: typeof import('./../components/Cropper/src/Cropper.vue')['default']
@ -33,13 +32,27 @@ declare module 'vue' {
Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
@ -55,27 +68,41 @@ declare module 'vue' {
ElementTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/ElementTask.vue')['default']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRate: typeof import('element-plus/es')['ElRate']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTableV2: typeof import('element-plus/es')['ElTableV2']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
@ -96,9 +123,6 @@ declare module 'vue' {
ProcessPalette: typeof import('./../components/bpmnProcessDesigner/package/palette/ProcessPalette.vue')['default']
ProcessViewer: typeof import('./../components/bpmnProcessDesigner/package/designer/ProcessViewer.vue')['default']
PropertiesPanel: typeof import('./../components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue')['default']
PurOrderSelectDialog: typeof import('./../views/biz/purreceipt copy/PurOrderSelectDialog.vue')['default']
PurReceiptForm: typeof import('./../views/biz/purreceipt copy/PurReceiptForm.vue')['default']
PurReceiptSelectDialog: typeof import('./../views/biz/purreturn/PurReceiptSelectDialog.vue')['default']
Qrcode: typeof import('./../components/Qrcode/src/Qrcode.vue')['default']
ReceiveTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ReceiveTask.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']

View File

@ -7,6 +7,8 @@ export {}
declare global {
const DICT_TYPE: typeof import('@/utils/dict')['DICT_TYPE']
const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']

View File

@ -0,0 +1,795 @@
<template>
<ContentWrap>
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item label="产线" prop="lineId">
<el-select
v-model="queryParams.lineId"
placeholder="请选择产线"
clearable
class="!w-180px"
@change="handleLineChange"
>
<el-option
v-for="item in lineOptions"
:key="item.lineId"
:label="item.lineName"
:value="item.lineId"
/>
</el-select>
</el-form-item>
<el-form-item label="机台" prop="machineId">
<el-select
v-model="queryParams.machineId"
placeholder="请选择机台"
clearable
class="!w-180px"
>
<el-option
v-for="item in filteredMachineOptions"
:key="item.machineId"
:label="item.machineName"
:value="item.machineId"
/>
</el-select>
</el-form-item>
</el-form>
<!-- 右上角日期班次批次更新 -->
<div style="display: flex; align-items: center; gap: 14px; white-space: nowrap; flex-shrink: 0; padding-top: 4px;">
<div>
<span style="font-weight: bold; color: #409EFF;">{{ defaultDate || '-' }}</span>
</div>
<div>
<span style="font-weight: bold; color: #409EFF;">
<dict-tag v-if="defaultShiftCd" :type="DICT_TYPE.SHIFT_SCHEDULE" :value="defaultShiftCd"/>
<span v-else>-</span>
</span>
</div>
<div>
<span style="font-weight: bold; color: #409EFF;">{{ defaultTeamName || '-' }}</span>
</div>
<el-button type="warning" size="small" @click="openShiftDialog">
<Icon icon="ep:clock" class="mr-5px"/>
班次更新
</el-button>
</div>
</div>
</ContentWrap>
<!-- 表1 - 生产计划 -->
<ContentWrap>
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 10px;">
<span style="font-weight: bold; font-size: 14px;">生产计划</span>
<div>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px"/>
查询
</el-button>
</div>
</div>
<el-table
ref="planTableRef"
v-loading="loading"
:data="planList"
:stripe="true"
:show-overflow-tooltip="true"
:row-class-name="getRowClass"
border
height="300px"
@row-click="handleRowClick"
:cell-style="getCellStyle"
>
<el-table-column label="序号" align="center" type="index" width="60" fixed="left"/>
<el-table-column label="机台计划状态" align="center" prop="machineProStatus" width="150"/>
<el-table-column label="生产计划号" align="center" prop="planNo" width="150" />
<el-table-column label="计划状态" align="center" prop="planStatus" width="100"/>
<!-- <template #default="scope">-->
<!-- <dict-tag :type="DICT_TYPE.PLAN_STATUS" :value="scope.row.planStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="产品名称" align="center" prop="materialName" width="180" />
<el-table-column label="规格型号" align="center" prop="spec" width="120"/>
<el-table-column label="计划数量" align="center" prop="planQty" width="120"/>
<el-table-column label="完成数量" align="center" prop="completeQty" width="140"/>
<el-table-column label="计划开始日期" align="center" prop="planBgDate" width="150" :formatter="dateFormatter2"/>
<el-table-column label="计划结束日期" align="center" prop="planEndDate" width="150" :formatter="dateFormatter2"/>
<el-table-column label="工艺流程" align="center" prop="techProc" width="200" />
<el-table-column label="计划日期" align="center" prop="proDate" width="150" :formatter="dateFormatter2"/>
</el-table>
</ContentWrap>
<!-- 表2 + 表3 -->
<ContentWrap>
<el-row :gutter="20">
<!-- 表2 - 物料配置左侧 -->
<el-col :span="12">
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<span style="font-weight: bold; font-size: 14px; margin-right: 15px;">物料配置</span>
<el-button type="primary" size="small" @click="handleAutoCollect">
<Icon icon="ep:collection" class="mr-5px"/>
自动采集
</el-button>
<el-button type="primary" size="small" :disabled="!canMatConfigConfirm" @click="handleMatConfigConfirm">
<Icon icon="ep:check" class="mr-5px"/>
确认
</el-button>
</div>
<el-table
v-loading="matConfigLoading"
:data="matConfigList"
:stripe="true"
:show-overflow-tooltip="true"
border
highlight-current-row
height="280px"
>
<el-table-column label="序号" align="center" type="index" width="60"/>
<el-table-column label="物料名称" align="center" prop="materialName" width="140"/>
<el-table-column label="物料类型" align="center" prop="matType" width="90"/>
<el-table-column label="采集数量" align="center" prop="atoQty" width="100"/>
<el-table-column prop="confirmQty" align="center" width="150">
<template #header>
<span>确认数量(<span style="color: red">*</span>)</span>
</template>
<template #default="scope">
<el-input
clearable
v-model="scope.row.confirmQty"
type="text"
inputmode="decimal"
min="0"
oninput="this.value=this.value.replace(/[^0-9.]/g,'').replace(/\.{2,}/g,'.').replace(/^(\d+)\.(\d{2}).*$/,'$1.$2')"
placeholder="请输入确认数量"
/>
</template>
</el-table-column>
<el-table-column label="规格" align="center" prop="spec" width="120"/>
<el-table-column label="物料编码" align="center" prop="materialCode" width="140"/>
<el-table-column label="单位" align="center" prop="unit" width="80">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MAT_UNIT" :value="scope.row.unit" />
</template>
</el-table-column>
</el-table>
</el-col>
<!-- 表3 - 投料信息右侧 -->
<el-col :span="12">
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<span style="font-weight: bold; font-size: 14px; margin-right: 15px;">投料信息</span>
<el-button type="primary" size="small" :disabled="!canConsumeStock" @click="openStockDialogForConsume">
<Icon icon="ep:box" class="mr-5px"/>
库存消耗
</el-button>
<el-button type="danger" size="small" :disabled="!selectedRow" @click="handleCancelFeed">
<Icon icon="ep:close" class="mr-5px"/>
取消
</el-button>
</div>
<el-table
v-loading="feedInfoLoading"
:data="feedInfoList"
:stripe="true"
:show-overflow-tooltip="true"
border
height="280px"
>
<el-table-column label="序号" align="center" type="index" width="60"/>
<el-table-column label="物料名称" align="center" prop="materialName" width="120"/>
<el-table-column label="物料类型" align="center" prop="matType" width="90"/>
<el-table-column label="确认数量" align="center" prop="confirmQty" width="100"/>
<el-table-column label="投料数量" align="center" prop="feedQty" width="100"/>
<el-table-column label="批次号" align="center" prop="lotNo" width="150"/>
<el-table-column label="规格" align="center" prop="spec" width="100"/>
<el-table-column label="仓库" align="center" prop="storeHouseName" width="100"/>
<el-table-column label="库区" align="center" prop="storeAreaName" width="100"/>
<el-table-column label="物料编码" align="center" prop="materialCode" width="130"/>
<el-table-column label="计划号" align="center" prop="planNo" width="140"/>
<el-table-column label="投料人" align="center" prop="feedEmpName" width="90"/>
<el-table-column label="投料时间" align="center" prop="feedTime" width="160"/>
</el-table>
</el-col>
</el-row>
</ContentWrap>
<!-- 批次更新弹窗 -->
<el-dialog v-model="shiftDialogVisible" title="批次更新" width="400px" :close-on-click-modal="false">
<el-form :model="shiftForm" :rules="shiftFormRules" label-width="100px" ref="shiftFormRef">
<el-form-item label="生产日期" prop="proDate">
<el-date-picker
v-model="shiftForm.proDate"
value-format="YYYY-MM-DD"
type="date"
placeholder="选择生产日期"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="班次" prop="shiftCd">
<el-select v-model="shiftForm.shiftCd" placeholder="请选择班次" class="!w-240px" clearable>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.SHIFT_SCHEDULE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="班组" prop="teamName">
<el-select v-model="shiftForm.teamName" placeholder="请选择班组" class="!w-240px" clearable>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.TEAM_OR_GROUP)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="shiftDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveShift">保存</el-button>
</template>
</el-dialog>
<!-- 库存信息弹窗表2确认 / 表3库存消耗共用 -->
<el-dialog v-model="stockDialogVisible" :title="stockDialogTitle" width="1100px" :close-on-click-modal="false">
<!-- 弹窗上半部分物料配置只读展示 -->
<div style="margin-bottom: 16px;">
<span style="font-weight: bold; font-size: 14px;">物料配置</span>
</div>
<el-table
:data="stockMatConfigList"
:stripe="true"
:show-overflow-tooltip="true"
border
max-height="200px"
size="small"
>
<el-table-column label="序号" align="center" type="index" width="55"/>
<el-table-column label="物料名称" align="center" prop="materialName" width="120"/>
<el-table-column label="物料类型" align="center" prop="matType" width="85"/>
<el-table-column label="采集数量" align="center" prop="atoQty" width="90"/>
<el-table-column label="确认数量(*)" align="center" prop="confirmQty" width="100"/>
<el-table-column label="规格" align="center" prop="spec" width="100"/>
<el-table-column label="物料编码" align="center" prop="materialCode" width="130"/>
<el-table-column label="单位" align="center" prop="unit" width="70"/>
</el-table>
<!-- 弹窗下半部分库存信息 -->
<div style="margin-top: 16px; margin-bottom: 12px;">
<span style="font-weight: bold; font-size: 14px;">库存信息</span>
</div>
<el-table
v-loading="stockDialogLoading"
:data="stockInfoList"
:stripe="true"
:show-overflow-tooltip="true"
border
height="280px"
>
<el-table-column label="序号" align="center" prop="rowNum" width="55"/>
<el-table-column label="物料名称" align="center" prop="materialName" width="110"/>
<el-table-column label="物料类型" align="center" prop="matType" width="85"/>
<el-table-column label="规格" align="center" prop="spec" width="100"/>
<el-table-column label="批次号" align="center" prop="lotNo" width="130"/>
<el-table-column label="库存数量" align="center" prop="stockQty" width="95"/>
<el-table-column prop="feedQty" align="center" width="130">
<template #header>
<span>投料数量(<span style="color: red">*</span>)</span>
</template>
<template #default="scope">
<el-input
clearable
v-model="scope.row.feedQty"
type="text"
inputmode="decimal"
min="0"
oninput="this.value=this.value.replace(/[^0-9.]/g,'').replace(/\.{2,}/g,'.').replace(/^(\d+)\.(\d{2}).*$/,'$1.$2')"
placeholder=""
/>
</template>
</el-table-column>
<el-table-column label="仓储" align="center" prop="storeHouseName" width="90"/>
<el-table-column label="库区" align="center" prop="storeAreaName" width="90"/>
<el-table-column label="物料编码" align="center" prop="materialCode" width="130"/>
<el-table-column label="最早入库日期" align="center" prop="earliestInDate" width="130" :formatter="dateFormatter2"/>
<el-table-column label="存货账单号" align="center" prop="inventBillNo" width="140"/>
</el-table>
<template #footer>
<el-button type="primary" @click="handleStockConfirm">确认</el-button>
<el-button @click="stockDialogVisible = false">取消</el-button>
</template>
</el-dialog>
</template>
<script setup lang="tsx">
import * as MatFeedExecuteApi from '@/api/biz/matfeedexecute'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { dateFormatter2, formatDate } from '@/utils/formatTime'
const message = useMessage()
defineOptions({ name: 'MatFeedExecute' })
//
const loading = ref(false)
const matConfigLoading = ref(false)
const feedInfoLoading = ref(false)
const stockDialogLoading = ref(false)
//
const planList = ref<MatFeedExecuteApi.MatFeedExecutePlanVO[]>([])
const matConfigList = ref<MatFeedExecuteApi.MatFeedExecuteMatConfigVO[]>([])
const feedInfoList = ref<MatFeedExecuteApi.MatFeedExecuteFeedInfoVO[]>([])
//
const lineOptions = ref<MatFeedExecuteApi.LineOption[]>([])
const machineOptions = ref<MatFeedExecuteApi.MachineOption[]>([])
//
const defaultDate = ref('')
const defaultShiftCd = ref('')
const defaultTeamName = ref('')
//
const selectedRow = ref<MatFeedExecuteApi.MatFeedExecutePlanVO | null>(null)
const planTableRef = ref()
//
const queryParams = reactive({
lineId: undefined as number | undefined,
machineId: undefined as number | undefined
})
// 线
const filteredMachineOptions = computed(() => {
if (!queryParams.lineId) return []
return machineOptions.value.filter(item => item.lineId === queryParams.lineId)
})
//
const canMatConfigConfirm = computed(() => {
return selectedRow.value
&& (selectedRow.value.isExecuting
|| selectedRow.value.machineProStatus === '执行中'
|| selectedRow.value.machineProStatus === '2')
})
//
const canConsumeStock = computed(() => {
return selectedRow.value
&& (selectedRow.value.isExecuting
|| selectedRow.value.machineProStatus === '执行中'
|| selectedRow.value.machineProStatus === '2')
})
// -
const getCellStyle = ({ row }) => {
if (row.isExecuting) {
return { backgroundColor: '#e6f7ff' }
}
return {}
}
/** 通过 row-class-name 响应式控制选中行高亮(替代 highlight-current-row */
const getRowClass = ({ row }: { row: MatFeedExecuteApi.MatFeedExecutePlanVO }) => {
if (selectedRow.value && selectedRow.value.planNo === row.planNo) {
return 'current-row'
}
return ''
}
/** 查询生产计划列表 */
const getPlanList = async () => {
if (!queryParams.machineId || !queryParams.lineId) {
message.warning('产线、机台不能为空,请确认')
return
}
if (!defaultDate.value) {
message.warning('生产日期不能为空,请确认!')
return
}
if (!defaultShiftCd.value) {
message.warning('班次不能为空,请确认!')
return
}
loading.value = true
try {
planList.value = await MatFeedExecuteApi.getPlanList({
machineId: queryParams.machineId,
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value,
lineId: queryParams.lineId
})
//
const executingPlan = planList.value.find(item => item.isExecuting)
if (executingPlan) {
handleRowClick(executingPlan!)
} else {
selectedRow.value = null
matConfigList.value = []
feedInfoList.value = []
}
} finally {
loading.value = false
}
}
/** 产线变化 */
const handleLineChange = () => {
queryParams.machineId = undefined
}
/** 点击生产计划行 */
const handleRowClick = (row: MatFeedExecuteApi.MatFeedExecutePlanVO) => {
selectedRow.value = row
matConfigList.value = []
feedInfoList.value = []
if (!queryParams.machineId) return
loadMatConfig(row)
loadFeedInfo(row)
}
/** 刷新主表并选中当前计划 */
const refreshWithCurrentPlan = async () => {
const planNo = selectedRow.value?.planNo
await getPlanList()
if (planNo) {
const found = planList.value.find(item => item.planNo === planNo)
if (found) handleRowClick(found)
}
}
/** 加载物料配置表2 */
const loadMatConfig = async (row: MatFeedExecuteApi.MatFeedExecutePlanVO) => {
if (!queryParams.machineId) return
matConfigLoading.value = true
try {
matConfigList.value = await MatFeedExecuteApi.getMatConfig({
machineId: queryParams.machineId,
planNo: row.planNo || '',
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value
})
} catch {
matConfigList.value = []
} finally {
matConfigLoading.value = false
}
}
/** 加载投料信息表3 */
const loadFeedInfo = async (row: MatFeedExecuteApi.MatFeedExecutePlanVO) => {
if (!queryParams.machineId) return
feedInfoLoading.value = true
try {
feedInfoList.value = await MatFeedExecuteApi.getFeedInfo({
machineId: queryParams.machineId,
planNo: row.planNo || '',
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value
})
} catch {
feedInfoList.value = []
} finally {
feedInfoLoading.value = false
}
}
/** 查询按钮 */
const handleQuery = () => getPlanList()
/** 自动采集 */
const handleAutoCollect = async () => {
if (!queryParams.lineId || !queryParams.machineId) {
message.warning('产线、机台不能为空,请确认!')
return
}
if (!defaultDate.value || !defaultShiftCd.value) {
message.warning('生产日期或班次不能为空,请确认!')
return
}
if (!selectedRow.value) {
message.warning('请先选择生产计划!')
return
}
// getMatConfig
await loadMatConfig(selectedRow.value)
message.success('物料配置已刷新')
}
// ================== ==================
const stockDialogVisible = ref(false)
const stockDialogTitle = ref('')
const stockDialogMode = ref<'confirm' | 'consume'>('confirm') // confirm=, consume=
const stockMatConfigList = ref<any[]>([]) //
const stockInfoList = ref<MatFeedExecuteApi.MatFeedExecuteStockVO[]>([]) //
/** 打开弹窗 - 物料配置确认模式 */
const openStockDialog = () => {
if (!selectedRow.value) {
message.warning('请先选择生产计划')
return
}
if (!canMatConfigConfirm.value) {
message.warning('当前机台状态非执行中,不能进行物料确认!')
return
}
stockDialogMode.value = 'confirm'
stockDialogTitle.value = '物料配置确认'
// 2
stockMatConfigList.value = JSON.parse(JSON.stringify(matConfigList.value))
//
loadStockData()
stockDialogVisible.value = true
}
/** 打开弹窗 - 库存消耗模式 */
const openStockDialogForConsume = () => {
if (!selectedRow.value) {
message.warning('请先选择生产计划')
return
}
if (!canConsumeStock.value) {
message.warning('当前机台状态非执行中,不能进行库存消耗!')
return
}
stockDialogMode.value = 'consume'
stockDialogTitle.value = '库存消耗'
// 2
stockMatConfigList.value = JSON.parse(JSON.stringify(matConfigList.value))
//
loadStockData()
stockDialogVisible.value = true
}
/** 加载弹窗中的库存信息 */
const loadStockData = async () => {
if (!selectedRow.value || !queryParams.machineId) return
stockDialogLoading.value = true
try {
const data = await MatFeedExecuteApi.getStockInfo({
machineId: queryParams.machineId,
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value,
planNo: selectedRow.value.planNo || ''
})
// feedQtyconfirmQty
const list = data.map((item: any, idx: number) => ({
...item,
rowNum: idx + 1,
feedQty: item.confirmQty != null ? String(item.confirmQty) : ''
}))
stockInfoList.value = list
} catch {
stockInfoList.value = []
} finally {
stockDialogLoading.value = false
}
}
/** 弹窗确认按钮 */
const handleStockConfirm = async () => {
if (stockDialogMode.value === 'confirm') {
await handleMatConfigConfirm()
} else {
await handleConsumeStock()
}
}
/** 物料配置确认提交 */
const handleMatConfigConfirm = async () => {
// (3) 2
if (!matConfigList.value || matConfigList.value.length === 0) {
message.warning('物料配置信息列表为空,请查询数据!')
return
}
// (4) 0
for (const item of matConfigList.value) {
const qty = Number(item.confirmQty)
if (item.confirmQty == null || item.confirmQty === '' || isNaN(qty) || qty <= 0) {
message.warning(`该物料(${item.materialName})投料数量等于0请确认`)
return
}
}
// (5) ''
if (!selectedRow.value || !selectedRow.value.isExecuting) {
message.warning('当前机台没有执行中的计划,请确认')
return
}
try {
await message.confirm('确定要确认当前物料配置吗?')
//
const matConfigItems = matConfigList.value.map(item => ({
id: item.id,
materialId: item.materialId,
materialName: item.materialName,
materialCode: item.materialCode,
spec: item.spec,
unit: item.unit,
confirmQty: item.confirmQty,
atoQty: item.atoQty,
dataSource: item.dataSource,
matTypeRaw: item.matTypeRaw
}))
await MatFeedExecuteApi.confirmMatConfig({
lineId: queryParams.lineId!,
lineName: selectedRow.value!.lineName,
lineCd: selectedRow.value!.lineCd || '',
machineId: queryParams.machineId!,
machineName: selectedRow.value!.machineName,
machineCd: selectedRow.value!.machineCd || '',
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value,
groupCd: defaultTeamName.value,
planNo: selectedRow.value!.planNo,
planId: selectedRow.value!.id,
matConfigItems
})
message.success('物料配置确认成功')
stockDialogVisible.value = false
await refreshWithCurrentPlan()
} catch {}
}
/** 库存消耗提交 */
const handleConsumeStock = async () => {
//
for (const item of stockInfoList.value) {
const qty = Number(item.feedQty)
if (item.feedQty == null || item.feedQty === '' || isNaN(qty) || qty <= 0) {
message.warning(`该物料(${item.materialName})投料数量等于0或为空请确认`)
return
}
}
try {
await message.confirm('确定要进行库存消耗操作吗?')
const feedItems = stockInfoList.value.map(item => ({
id: item.id,
materialId: item.materialId,
materialName: item.materialName,
matType: item.matTypeRaw || item.matType,
spec: item.spec,
lotNo: item.lotNo,
feedQty: item.feedQty,
storeHouseId: item.storeHouseId,
storeHouseName: item.storeHouseName,
storeAreaId: item.storeAreaId,
storeAreaName: item.storeAreaName,
materialCode: item.materialCode,
inventBillNo: item.inventBillNo,
confirmQty: item.confirmQty
}))
await MatFeedExecuteApi.consumeStock({
lineId: queryParams.lineId!,
lineName: selectedRow.value!.lineName,
lineCd: '',
machineId: queryParams.machineId!,
machineName: selectedRow.value!.machineName,
machineCd: selectedRow.value!.machineCd,
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value,
groupCd: defaultTeamName.value,
planNo: selectedRow.value!.planNo,
planId: selectedRow.value!.id,
feedItems
})
message.success('库存消耗成功')
stockDialogVisible.value = false
await refreshWithCurrentPlan()
} catch {}
}
/** 取消投料 */
const handleCancelFeed = async () => {
if (!selectedRow.value) {
message.warning('请先选择生产计划')
return
}
if (!queryParams.lineId || !queryParams.machineId) {
message.warning('产线、机台不能为空,请确认!')
return
}
if (!defaultDate.value || !defaultShiftCd.value) {
message.warning('生产日期或班次不能为空,请确认!')
return
}
if (!selectedRow.value.planNo) {
message.warning('选择执行计划为空,请选择!')
return
}
try {
await message.confirm('确定要取消当前投料信息吗?')
await MatFeedExecuteApi.cancelFeed({
lineId: queryParams.lineId!,
lineName: selectedRow.value.lineName,
lineCd: '',
machineId: queryParams.machineId!,
machineName: selectedRow.value.machineName,
machineCd: selectedRow.value.machineCd,
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value,
groupCd: defaultTeamName.value,
planNo: selectedRow.value.planNo,
planId: selectedRow.value.id
})
message.success('投料取消成功')
await refreshWithCurrentPlan()
} catch {}
}
// ================== ==================
const shiftDialogVisible = ref(false)
const shiftFormRef = ref()
const shiftForm = reactive({ proDate: '', shiftCd: '', teamName: '' })
const shiftFormRules = {
proDate: [{ required: true, message: '生产日期不能为空', trigger: 'change' }],
shiftCd: [{ required: true, message: '班次不能为空', trigger: 'change' }],
teamName: [{ required: true, message: '班组不能为空', trigger: 'change' }]
}
const openShiftDialog = () => {
shiftForm.proDate = defaultDate.value || formatDate(new Date(), 'YYYY-MM-DD')
shiftForm.shiftCd = defaultShiftCd.value || ''
shiftForm.teamName = defaultTeamName.value || ''
shiftDialogVisible.value = true
nextTick(() => { shiftFormRef.value?.clearValidate() })
}
const handleSaveShift = () => {
if (!shiftForm.proDate) { message.warning('生产日期不能为空'); return }
if (!shiftForm.shiftCd) { message.warning('班次不能为空'); return }
if (!shiftForm.teamName) { message.warning('班组不能为空'); return }
defaultDate.value = shiftForm.proDate
defaultShiftCd.value = shiftForm.shiftCd
defaultTeamName.value = shiftForm.teamName
message.success('保存成功')
shiftDialogVisible.value = false
}
onMounted(async () => {
try {
const initData = await MatFeedExecuteApi.getMatFeedExecuteInit()
lineOptions.value = initData.lineList || []
machineOptions.value = initData.machineList || []
if (lineOptions.value.length === 1) queryParams.lineId = lineOptions.value[0].lineId
if (machineOptions.value.length === 1) queryParams.machineId = machineOptions.value[0].machineId
if (initData.defaultDate) defaultDate.value = formatDate(new Date(initData.defaultDate), 'YYYY-MM-DD')
defaultShiftCd.value = initData.shiftCd || ''
defaultTeamName.value = initData.groupCd || ''
if (queryParams.machineId && defaultDate.value && defaultShiftCd.value) {
await getPlanList()
}
} catch {}
})
</script>
<style>
</style>

View File

@ -121,7 +121,6 @@
<el-table-column label="计划开始日期" align="center" prop="planBgDate" width="150" :formatter="dateFormatter2"/>
<el-table-column label="计划结束日期" align="center" prop="planEndDate" width="150" :formatter="dateFormatter2"/>
<el-table-column label="工艺流程名称" align="center" prop="techProc" width="200" />
<el-table-column label="计划日期" align="center" prop="proDate" width="150" :formatter="dateFormatter2"/>
</el-table>
</ContentWrap>
@ -137,11 +136,11 @@
<Icon icon="ep:collection" class="mr-5px"/>
自动采集
</el-button>
<el-button type="primary" size="small" :disabled="!selectedOutputRow" @click="handleOutputConfirm">
<el-button type="primary" size="small" :disabled="!canOutputConfirm" @click="handleOutputConfirm">
<Icon icon="ep:check" class="mr-5px"/>
确认
</el-button>
<el-button type="danger" size="small" :disabled="!selectedOutputRow" @click="handleOutputCancel">
<el-button type="danger" size="small" @click="handleOutputCancel">
<Icon icon="ep:close" class="mr-5px"/>
取消
</el-button>
@ -478,6 +477,12 @@ const canComplete = computed(() => {
)
})
// =
const canOutputConfirm = computed(() => {
return selectedRow.value && (selectedRow.value.machineProStatus === '执行中'
|| selectedRow.value.machineProStatus === '2')
})
//
const getCellStyle = ({ row, column }) => {
if (row.machineProStatus === '执行中' && column.property === 'machineProStatus') {
@ -592,6 +597,7 @@ const getPlanList = async () => {
machineId: queryParams.machineId,
proDate: defaultDate.value,
shiftCd: defaultShiftCd.value,
lineId:queryParams.lineId
})
//
@ -873,6 +879,12 @@ const handleOutputConfirm = async () => {
message.warning('请先选择生产计划')
return
}
// (0) =
const status = selectedRow.value.machineProStatus
if (status !== '执行中' && status !== '2') {
message.warning('当前机台状态非执行中,不能进行产出确认!')
return
}
// (1) 线/
if (!queryParams.lineId || !queryParams.machineId) {
message.warning('产线、机台不能为空,请确认!')