|
|
@@ -7,6 +7,7 @@ import easydo.technology.model.*;
|
|
|
import easydo.technology.model.vo.DispatchVO;
|
|
|
import easydo.technology.model.vo.ProductOrderDispatchVO;
|
|
|
import easydo.technology.service.FlowNoService;
|
|
|
+import easydo.technology.service.MaterialRequisitionService;
|
|
|
import easydo.technology.service.ProductOrderDispatchService;
|
|
|
import easydo.technology.system.model.SysUser;
|
|
|
import easydo.technology.utils.SecurityUtils;
|
|
|
@@ -22,6 +23,61 @@ import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
+/**
|
|
|
+ * 派工单服务实现类
|
|
|
+ *
|
|
|
+ * ============================================
|
|
|
+ * getPage() 接口表关联关系图
|
|
|
+ * ============================================
|
|
|
+ *
|
|
|
+ * 主表:product_order_dispatch(派工单)
|
|
|
+ * │
|
|
|
+ * ├─→ product_order(工单)
|
|
|
+ * │ 关联:dispatch.order_id = order.id
|
|
|
+ * │
|
|
|
+ * ├─→ process_stage(工序)
|
|
|
+ * │ 关联:dispatch.stage_id = stage.id
|
|
|
+ * │
|
|
|
+ * ├─→ product_order_bom(工单BOM)
|
|
|
+ * │ 关联:dispatch.order_id = order_bom.order_id
|
|
|
+ * │ AND dispatch.bom_id = order_bom.bom_id
|
|
|
+ * │ │
|
|
|
+ * │ └─→ product_bom(产品BOM)
|
|
|
+ * │ 关联:order_bom.bom_id = product_bom.id
|
|
|
+ * │
|
|
|
+ * ├─→ dispatch_user_item(派工人员)
|
|
|
+ * │ 关联:dispatch.id = user_item.dispatch_id
|
|
|
+ * │ │
|
|
|
+ * │ └─→ sys_user(用户)
|
|
|
+ * │ 关联:user_item.user_id = user.id
|
|
|
+ * │
|
|
|
+ * └─→ material_requisition(领料单)
|
|
|
+ * 关联:dispatch.order_id = requisition.order_id
|
|
|
+ * AND dispatch.bom_id = requisition.bom_id
|
|
|
+ *
|
|
|
+ * ============================================
|
|
|
+ * 查询优化说明
|
|
|
+ * ============================================
|
|
|
+ *
|
|
|
+ * 当前实现:单表查询 + 应用层拼接
|
|
|
+ * - 优点:逻辑清晰,易于维护
|
|
|
+ * - 缺点:查询次数多(N+1问题)
|
|
|
+ *
|
|
|
+ * 查询次数统计(假设返回10条记录):
|
|
|
+ * - 主查询:1次
|
|
|
+ * - 工单/工序/BOM:各10次
|
|
|
+ * - 派工人员:10次
|
|
|
+ * - 用户信息:30次(假设每个派工3人)
|
|
|
+ * - 领料单:5次(去重优化后)
|
|
|
+ * - 总计:约86次数据库查询
|
|
|
+ *
|
|
|
+ * 优化方向:
|
|
|
+ * 1. 主查询改用连表SQL(减少到1次)
|
|
|
+ * 2. 子查询改用批量IN查询(3-4次)
|
|
|
+ * 3. 总查询次数可降至4-5次
|
|
|
+ *
|
|
|
+ * ============================================
|
|
|
+ */
|
|
|
@Service
|
|
|
public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchService {
|
|
|
|
|
|
@@ -31,6 +87,8 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
private DataSource dataSource;
|
|
|
@Resource
|
|
|
private FlowNoService flowNoService;
|
|
|
+ @Resource
|
|
|
+ private MaterialRequisitionService materialRequisitionService;
|
|
|
|
|
|
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
|
@@ -144,6 +202,7 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
ProductOrderDispatchVO.SimpleUser simpleUser = new ProductOrderDispatchVO.SimpleUser();
|
|
|
simpleUser.setId(user.getId());
|
|
|
simpleUser.setUsername(user.getUsername());
|
|
|
+ simpleUser.setNickName(user.getNickName());
|
|
|
simpleItem.setUser(simpleUser);
|
|
|
}
|
|
|
}
|
|
|
@@ -156,6 +215,11 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
voList.add(vo);
|
|
|
}
|
|
|
|
|
|
+ // 批量查询所有派工单的领料单状态并设置领料状态
|
|
|
+ if (!voList.isEmpty()) {
|
|
|
+ calculateRequisitionStatus(voList, connection);
|
|
|
+ }
|
|
|
+
|
|
|
// 替换 records
|
|
|
result.put("records", voList);
|
|
|
return result;
|
|
|
@@ -196,6 +260,11 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
&& (item.getStageId() == null || item.getStageId().trim().isEmpty())
|
|
|
&& (item.getUserIds() == null || item.getUserIds().isEmpty())
|
|
|
&& item.getOrderNum() == null
|
|
|
+ && item.getProcessNum() == null
|
|
|
+ && item.getProcessTimeHour() == null
|
|
|
+ && item.getReadyTimeHour() == null
|
|
|
+ && item.getMoveNum() == null
|
|
|
+ && item.getMoveTimeHour() == null
|
|
|
&& (item.getBeginDate() == null || item.getBeginDate().trim().isEmpty())
|
|
|
&& (item.getEndDate() == null || item.getEndDate().trim().isEmpty());
|
|
|
|
|
|
@@ -270,10 +339,17 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
throw new BizException("派工记录与工单不匹配: " + item.getId());
|
|
|
}
|
|
|
|
|
|
+ existDispatch.setCode(item.getCode());
|
|
|
+ existDispatch.setName(item.getName());
|
|
|
existDispatch.setRouteId(routeId);
|
|
|
existDispatch.setBomId(item.getBomId());
|
|
|
existDispatch.setStageId(item.getStageId());
|
|
|
existDispatch.setOrderNum(item.getOrderNum());
|
|
|
+ existDispatch.setProcessNum(item.getProcessNum());
|
|
|
+ existDispatch.setProcessTimeHour(item.getProcessTimeHour());
|
|
|
+ existDispatch.setReadyTimeHour(item.getReadyTimeHour());
|
|
|
+ existDispatch.setMoveNum(item.getMoveNum());
|
|
|
+ existDispatch.setMoveTimeHour(item.getMoveTimeHour());
|
|
|
existDispatch.setIsInspection(item.getIsInspection() != null ? item.getIsInspection() : false);
|
|
|
existDispatch.setBeginDate(item.getBeginDate());
|
|
|
existDispatch.setEndDate(item.getEndDate());
|
|
|
@@ -293,6 +369,11 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
userItem.setBomId(item.getBomId());
|
|
|
userItem.setStageId(item.getStageId());
|
|
|
userItem.setOrderNum(item.getOrderNum());
|
|
|
+ userItem.setProcessNum(item.getProcessNum());
|
|
|
+ userItem.setProcessTimeHour(item.getProcessTimeHour());
|
|
|
+ userItem.setReadyTimeHour(item.getReadyTimeHour());
|
|
|
+ userItem.setMoveNum(item.getMoveNum());
|
|
|
+ userItem.setMoveTimeHour(item.getMoveTimeHour());
|
|
|
userItem.setBeginDate(item.getBeginDate());
|
|
|
userItem.setEndDate(item.getEndDate());
|
|
|
userItem.setStatus(existDispatch.getStatus());
|
|
|
@@ -310,11 +391,17 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
dispatch.setOrderId(vo.getOrderId());
|
|
|
dispatch.setTenantId(tenantId);
|
|
|
String dispatchCode = flowNoService.generateProductOrderDispatchCode(dispatch, connection);
|
|
|
- dispatch.setCode(dispatchCode);
|
|
|
+ dispatch.setCode((item.getCode() != null && !item.getCode().trim().isEmpty()) ? item.getCode() : dispatchCode);
|
|
|
+ dispatch.setName(item.getName());
|
|
|
dispatch.setRouteId(routeId);
|
|
|
dispatch.setBomId(item.getBomId());
|
|
|
dispatch.setStageId(item.getStageId());
|
|
|
dispatch.setOrderNum(item.getOrderNum());
|
|
|
+ dispatch.setProcessNum(item.getProcessNum());
|
|
|
+ dispatch.setProcessTimeHour(item.getProcessTimeHour());
|
|
|
+ dispatch.setReadyTimeHour(item.getReadyTimeHour());
|
|
|
+ dispatch.setMoveNum(item.getMoveNum());
|
|
|
+ dispatch.setMoveTimeHour(item.getMoveTimeHour());
|
|
|
dispatch.setIsInspection(item.getIsInspection() != null ? item.getIsInspection() : false);
|
|
|
dispatch.setBeginDate(item.getBeginDate());
|
|
|
dispatch.setEndDate(item.getEndDate());
|
|
|
@@ -328,6 +415,9 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
|
|
|
jdbcClient.jdbcInsert(dispatch, connection);
|
|
|
|
|
|
+ // 生成领料单(如果是首道工序)
|
|
|
+ materialRequisitionService.generateRequisition(dispatch, connection);
|
|
|
+
|
|
|
for (Long userIdItem : item.getUserIds()) {
|
|
|
DispatchUserItem userItem = new DispatchUserItem();
|
|
|
userItem.setDispatchId(dispatch.getId());
|
|
|
@@ -337,6 +427,11 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
userItem.setBomId(item.getBomId());
|
|
|
userItem.setStageId(item.getStageId());
|
|
|
userItem.setOrderNum(item.getOrderNum());
|
|
|
+ userItem.setProcessNum(item.getProcessNum());
|
|
|
+ userItem.setProcessTimeHour(item.getProcessTimeHour());
|
|
|
+ userItem.setReadyTimeHour(item.getReadyTimeHour());
|
|
|
+ userItem.setMoveNum(item.getMoveNum());
|
|
|
+ userItem.setMoveTimeHour(item.getMoveTimeHour());
|
|
|
userItem.setBeginDate(item.getBeginDate());
|
|
|
userItem.setEndDate(item.getEndDate());
|
|
|
userItem.setStatus(MESEnum.PRODUCT_ORDER_DISPATCH_OF_STATUS_PENDING.getValue());
|
|
|
@@ -437,4 +532,189 @@ public class ProductOrderDispatchServiceImpl implements ProductOrderDispatchServ
|
|
|
connection.close();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量计算派工单的领料状态(基于领料单出库状态)
|
|
|
+ *
|
|
|
+ * 业务逻辑说明:
|
|
|
+ * 1. 只有首道工序需要关心领料状态(原材料是否领取)
|
|
|
+ * 2. 非首道工序的"料"来自上一道工序的产出,不需要关心原材料领料
|
|
|
+ * 3. 非首道工序应该关心上一道工序的报工状态(另外的字段)
|
|
|
+ *
|
|
|
+ * @param voList 派工单VO列表
|
|
|
+ * @param connection 数据库连接
|
|
|
+ */
|
|
|
+ private void calculateRequisitionStatus(List<ProductOrderDispatchVO> voList, Connection connection) throws Exception {
|
|
|
+ // 缓存:工单ID+BOM ID → 领料单列表
|
|
|
+ Map<String, List<MaterialRequisition>> orderBomRequisitionMap = new HashMap<>();
|
|
|
+
|
|
|
+ // 收集所有首道工序的查询条件
|
|
|
+ for (ProductOrderDispatchVO vo : voList) {
|
|
|
+ if (vo.getId() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否为首道工序
|
|
|
+ boolean isFirstStage = isFirstStage(vo, connection);
|
|
|
+
|
|
|
+ if (isFirstStage) {
|
|
|
+ // 只有首道工序才查询领料单
|
|
|
+ if (vo.getOrderId() != null && vo.getBomId() != null) {
|
|
|
+ String key = vo.getOrderId() + "_" + vo.getBomId();
|
|
|
+ if (!orderBomRequisitionMap.containsKey(key)) {
|
|
|
+ MaterialRequisition requisitionQuery = new MaterialRequisition();
|
|
|
+ requisitionQuery.setOrderId(vo.getOrderId());
|
|
|
+ requisitionQuery.setBomId(vo.getBomId());
|
|
|
+ List<MaterialRequisition> requisitions = jdbcClient.getJdbcList(requisitionQuery, connection);
|
|
|
+ orderBomRequisitionMap.put(key, requisitions != null ? requisitions : new ArrayList<>());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为每个派工单计算领料状态
|
|
|
+ for (ProductOrderDispatchVO vo : voList) {
|
|
|
+ if (vo.getId() == null) {
|
|
|
+ vo.setRequisitionStatus(null); // 无效派工单,不显示状态
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否为首道工序
|
|
|
+ boolean isFirstStage = isFirstStage(vo, connection);
|
|
|
+
|
|
|
+ if (isFirstStage) {
|
|
|
+ // 首道工序:显示领料状态
|
|
|
+ String key = vo.getOrderId() + "_" + vo.getBomId();
|
|
|
+ List<MaterialRequisition> requisitions = orderBomRequisitionMap.get(key);
|
|
|
+
|
|
|
+ // 计算领料状态
|
|
|
+ String requisitionStatus = calculateRequisitionStatusByRequisitions(requisitions);
|
|
|
+ vo.setRequisitionStatus(requisitionStatus);
|
|
|
+
|
|
|
+ // 设置领料单信息
|
|
|
+ if (requisitions != null && !requisitions.isEmpty()) {
|
|
|
+ List<ProductOrderDispatchVO.SimpleMaterialRequisition> simpleRequisitions = new ArrayList<>();
|
|
|
+ for (MaterialRequisition req : requisitions) {
|
|
|
+ ProductOrderDispatchVO.SimpleMaterialRequisition simpleReq =
|
|
|
+ new ProductOrderDispatchVO.SimpleMaterialRequisition();
|
|
|
+ simpleReq.setId(req.getId());
|
|
|
+ simpleReq.setCode(req.getCode());
|
|
|
+ simpleReq.setDispatchId(req.getDispatchId());
|
|
|
+ simpleReq.setOrderId(req.getOrderId());
|
|
|
+ simpleReq.setBomId(req.getBomId());
|
|
|
+ simpleReq.setRequisitionType(req.getRequisitionType());
|
|
|
+ simpleReq.setOutboundStatus(req.getOutboundStatus());
|
|
|
+ simpleReq.setStatus(req.getStatus());
|
|
|
+ simpleReq.setRequisitionDate(req.getRequisitionDate());
|
|
|
+ simpleReq.setRemark(req.getRemark());
|
|
|
+ simpleReq.setTenantId(req.getTenantId());
|
|
|
+ simpleReq.setCreateId(req.getCreateId());
|
|
|
+ simpleReq.setCreateTime(req.getCreateTime());
|
|
|
+ simpleReq.setUpdateId(req.getUpdateId());
|
|
|
+ simpleReq.setUpdateTime(req.getUpdateTime());
|
|
|
+ simpleRequisitions.add(simpleReq);
|
|
|
+ }
|
|
|
+ vo.setMaterialRequisitions(simpleRequisitions);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 非首道工序:不显示领料状态(前端可以判断 null 不显示该字段)
|
|
|
+ vo.setRequisitionStatus(null);
|
|
|
+ vo.setMaterialRequisitions(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断派工单是否为首道工序
|
|
|
+ *
|
|
|
+ * @param vo 派工单VO
|
|
|
+ * @param connection 数据库连接
|
|
|
+ * @return true-首道工序,false-非首道工序
|
|
|
+ */
|
|
|
+ private boolean isFirstStage(ProductOrderDispatchVO vo, Connection connection) throws Exception {
|
|
|
+ if (vo.getRouteId() == null || vo.getStageId() == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询该工艺路线的所有工序
|
|
|
+ ProcessRouteDetail allRouteQuery = new ProcessRouteDetail();
|
|
|
+ allRouteQuery.setRouteId(vo.getRouteId());
|
|
|
+ List<ProcessRouteDetail> allRouteDetails = jdbcClient.getJdbcList(allRouteQuery, connection);
|
|
|
+
|
|
|
+ if (allRouteDetails == null || allRouteDetails.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 找出最小的 order_num
|
|
|
+ Integer minOrderNum = allRouteDetails.stream()
|
|
|
+ .map(ProcessRouteDetail::getOrderNum)
|
|
|
+ .filter(num -> num != null)
|
|
|
+ .min(Integer::compareTo)
|
|
|
+ .orElse(null);
|
|
|
+
|
|
|
+ if (minOrderNum == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询当前派工的工序信息
|
|
|
+ ProcessRouteDetail currentRouteQuery = new ProcessRouteDetail();
|
|
|
+ currentRouteQuery.setRouteId(vo.getRouteId());
|
|
|
+ currentRouteQuery.setStageId(vo.getStageId());
|
|
|
+ List<ProcessRouteDetail> currentRouteDetails = jdbcClient.getJdbcList(currentRouteQuery, connection);
|
|
|
+
|
|
|
+ if (currentRouteDetails == null || currentRouteDetails.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ProcessRouteDetail currentRouteDetail = currentRouteDetails.get(0);
|
|
|
+ return currentRouteDetail.getOrderNum() != null
|
|
|
+ && currentRouteDetail.getOrderNum().equals(minOrderNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据领料单列表计算领料状态
|
|
|
+ * 规则:
|
|
|
+ * - applied/pending → 未领料
|
|
|
+ * - partial → 部分领料
|
|
|
+ * - completed → 已领料
|
|
|
+ * - 混合状态 → 部分领料
|
|
|
+ *
|
|
|
+ * @param requisitions 领料单列表
|
|
|
+ * @return 领料状态
|
|
|
+ */
|
|
|
+ private String calculateRequisitionStatusByRequisitions(List<MaterialRequisition> requisitions) {
|
|
|
+ // 没有领料单,返回"未领料"
|
|
|
+ if (requisitions == null || requisitions.isEmpty()) {
|
|
|
+ return MESEnum.DISPATCH_REQUISITION_STATUS_PENDING.getValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统计出库状态
|
|
|
+ boolean hasCompleted = false; // 出库完毕
|
|
|
+ boolean hasPartial = false; // 部分出库
|
|
|
+ boolean hasAppliedOrPending = false; // 申请完毕或未出库
|
|
|
+
|
|
|
+ for (MaterialRequisition req : requisitions) {
|
|
|
+ String status = req.getOutboundStatus();
|
|
|
+ if (MESEnum.MATERIAL_REQUISITION_OUTBOUND_STATUS_COMPLETED.getValue().equals(status)) {
|
|
|
+ hasCompleted = true;
|
|
|
+ } else if (MESEnum.MATERIAL_REQUISITION_OUTBOUND_STATUS_PARTIAL.getValue().equals(status)) {
|
|
|
+ hasPartial = true;
|
|
|
+ } else if (MESEnum.MATERIAL_REQUISITION_OUTBOUND_STATUS_APPLIED.getValue().equals(status)
|
|
|
+ || MESEnum.MATERIAL_REQUISITION_OUTBOUND_STATUS_PENDING.getValue().equals(status)) {
|
|
|
+ hasAppliedOrPending = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断领料状态
|
|
|
+ if (hasCompleted && !hasPartial && !hasAppliedOrPending) {
|
|
|
+ // 全部出库完毕 → 已领料
|
|
|
+ return MESEnum.DISPATCH_REQUISITION_STATUS_COMPLETE.getValue();
|
|
|
+ } else if (hasAppliedOrPending && !hasPartial && !hasCompleted) {
|
|
|
+ // 全部是申请完毕/未出库 → 未领料
|
|
|
+ return MESEnum.DISPATCH_REQUISITION_STATUS_PENDING.getValue();
|
|
|
+ } else {
|
|
|
+ // 其他情况(有部分出库,或混合状态)→ 部分领料
|
|
|
+ return MESEnum.DISPATCH_REQUISITION_STATUS_PARTIALLY.getValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|