src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java
@@ -1,8 +1,22 @@ package com.ruoyi.production.bean.vo; import com.ruoyi.basic.dto.StorageBlobVO; import com.ruoyi.production.pojo.ProductionOrder; import lombok.Data; import java.util.List; @Data public class ProductionOrderVo extends ProductionOrder { private String salesContractNo; private String customerName; private String productName; private String model; private String processRouteCode; private List<StorageBlobVO> productImages; } src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
@@ -1,5 +1,6 @@ package com.ruoyi.production.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.framework.web.domain.R; import com.ruoyi.production.bean.dto.ProductionOrderDto; @@ -32,7 +33,7 @@ @GetMapping("/page") @Operation(summary = "分页查询") public R page(ProductionOrderDto dto, Page<ProductionOrderDto> page) { public R<IPage<ProductionOrderVo>> page(ProductionOrderDto dto, Page<ProductionOrderDto> page) { return R.ok(productionOrderService.pageProductionOrder(page, dto)); } src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java
@@ -1,7 +1,11 @@ package com.ruoyi.production.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.home.dto.ProductionProgressOrderDto; import com.ruoyi.production.bean.dto.ProductionOrderDto; import com.ruoyi.production.bean.vo.ProductionOrderVo; import com.ruoyi.production.pojo.ProductionOrder; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -20,6 +24,12 @@ @Mapper public interface ProductionOrderMapper extends BaseMapper<ProductionOrder> { IPage<ProductionOrderVo> pageProductionOrder(Page<?> page, @Param("c") ProductionOrderDto dto); List<ProductionOrderVo> listProductionOrder(@Param("c") ProductionOrderDto dto); ProductionOrderVo getProductionOrderInfo(@Param("id") Long id); List<ProductionProgressOrderDto> selectProgressOrders(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime); src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
@@ -29,6 +29,9 @@ @TableId(value = "id", type = IdType.AUTO) private Long id; @Schema(description = "销售台账id。生产下单接口通常不需要前端手工填写,存在销售来源时由系统内部回填。") private Long salesLedgerId; @Schema(description = "生产计划ID列表,格式:[1,2,3]。如果按生产计划生成订单,新增时传这个字段即可,系统会自动汇总产品规格和数量。") private String productionPlanIds; @@ -61,6 +64,9 @@ @Schema(description = "结束日期") private LocalDateTime endTime; @Schema(description = "销售产品规格id。生产下单接口一般不作为前端必填项,存在销售来源时由系统内部关联。") private Integer saleLedgerProductId; @Schema(description = "创建人ID") @TableField(fill = FieldFill.INSERT) private Long createUser; @@ -76,7 +82,4 @@ @Schema(description = "状态(1.待开始、2.进行中、3.已完成、4.已取消)") private Integer status; @Schema(description = "强度") private String strength; } src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -5,6 +5,13 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.dto.StorageBlobVO; import com.ruoyi.basic.mapper.StorageAttachmentMapper; import com.ruoyi.basic.mapper.StorageBlobMapper; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.pojo.StorageBlob; import com.ruoyi.basic.utils.FileUtil; import com.ruoyi.common.constant.StorageAttachmentConstants; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.production.bean.dto.ProductionOrderDto; import com.ruoyi.production.bean.vo.ProductionOrderVo; @@ -31,6 +38,10 @@ import com.ruoyi.production.pojo.ProductionPlan; import com.ruoyi.production.pojo.ProductionProductMain; import com.ruoyi.production.enums.ProductOrderStatusEnum; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.technology.mapper.TechnologyBomMapper; import com.ruoyi.technology.mapper.TechnologyBomStructureMapper; @@ -75,27 +86,39 @@ private final ProductionOrderPickMapper productionOrderPickMapper; private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper; private final ProductionPlanMapper productionPlanMapper; private final StorageAttachmentMapper storageAttachmentMapper; private final StorageBlobMapper storageBlobMapper; private final SalesLedgerMapper salesLedgerMapper; private final SalesLedgerProductMapper salesLedgerProductMapper; private final TechnologyRoutingMapper technologyRoutingMapper; private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; private final TechnologyRoutingOperationParamMapper technologyRoutingOperationParamMapper; private final TechnologyBomMapper technologyBomMapper; private final TechnologyBomStructureMapper technologyBomStructureMapper; private final FileUtil fileUtil; @Override public com.baomidou.mybatisplus.core.metadata.IPage<ProductionOrderVo> pageProductionOrder(Page<ProductionOrderDto> page, ProductionOrderDto dto) { Page<ProductionOrder> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); return this.page(entityPage, buildQueryWrapper(dto)).convert(item -> BeanUtil.copyProperties(item, ProductionOrderVo.class)); Page<ProductionOrderVo> result = (Page<ProductionOrderVo>) baseMapper.pageProductionOrder(page, dto); fillProductImages(result.getRecords()); return result; } @Override public List<ProductionOrderVo> listProductionOrder(ProductionOrderDto dto) { return BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOrderVo.class); List<ProductionOrderVo> records = baseMapper.listProductionOrder(dto); fillProductImages(records); return records; } @Override public ProductionOrderVo getProductionOrderInfo(Long id) { ProductionOrder item = this.getById(id); return item == null ? null : BeanUtil.copyProperties(item, ProductionOrderVo.class); ProductionOrderVo item = baseMapper.getProductionOrderInfo(id); if (item == null) { return null; } fillProductImages(java.util.Collections.singletonList(item)); return item; } @Override @@ -312,6 +335,7 @@ ProductionOrder query = dto == null ? new ProductionOrder() : dto; return Wrappers.<ProductionOrder>lambdaQuery() .eq(query.getId() != null, ProductionOrder::getId, query.getId()) .eq(query.getSalesLedgerId() != null, ProductionOrder::getSalesLedgerId, query.getSalesLedgerId()) .eq(query.getProductModelId() != null, ProductionOrder::getProductModelId, query.getProductModelId()) .eq(query.getTechnologyRoutingId() != null, ProductionOrder::getTechnologyRoutingId, query.getTechnologyRoutingId()) .like(query.getNpsNo() != null && !query.getNpsNo().trim().isEmpty(), ProductionOrder::getNpsNo, query.getNpsNo()) @@ -363,9 +387,10 @@ if (productionOrder == null) { throw new ServiceException("Production order is required"); } fillFromSalesLedgerProduct(productionOrder); fillFromProductionPlans(productionOrder); if (productionOrder.getProductModelId() == null) { throw new ServiceException("productModelId is required when manually creating a production order"); throw new ServiceException("productModelId is required"); } if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("quantity must be greater than 0"); @@ -388,6 +413,36 @@ || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId()) || compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0) { throw new ServiceException("Started production orders cannot modify product, routing or quantity"); } } } private void fillFromSalesLedgerProduct(ProductionOrder productionOrder) { if (productionOrder.getSaleLedgerProductId() == null) { return; } // 销售明细是订单来源时,以销售明细为准回填销售台账、产品规格和默认数量。 SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productionOrder.getSaleLedgerProductId().longValue()); if (salesLedgerProduct == null) { throw new ServiceException("Sales ledger product not found"); } if (productionOrder.getSalesLedgerId() == null) { productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); } else if (!Objects.equals(productionOrder.getSalesLedgerId(), salesLedgerProduct.getSalesLedgerId())) { throw new ServiceException("salesLedgerId does not match the sales ledger product"); } if (productionOrder.getProductModelId() == null) { productionOrder.setProductModelId(salesLedgerProduct.getProductModelId()); } else if (!Objects.equals(productionOrder.getProductModelId(), salesLedgerProduct.getProductModelId())) { throw new ServiceException("productModelId does not match the sales ledger product"); } if (productionOrder.getQuantity() == null || productionOrder.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { productionOrder.setQuantity(salesLedgerProduct.getQuantity()); } if (productionOrder.getPlanCompleteTime() == null && productionOrder.getSalesLedgerId() != null) { SalesLedger salesLedger = salesLedgerMapper.selectById(productionOrder.getSalesLedgerId()); if (salesLedger != null && salesLedger.getDeliveryDate() != null) { productionOrder.setPlanCompleteTime(salesLedger.getDeliveryDate()); } } } @@ -547,4 +602,65 @@ private int compareDecimal(BigDecimal left, BigDecimal right) { return defaultDecimal(left).compareTo(defaultDecimal(right)); } private void fillProductImages(List<ProductionOrderVo> records) { if (records == null || records.isEmpty()) { return; } List<Long> productModelIds = records.stream() .map(ProductionOrderVo::getProductModelId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (productModelIds.isEmpty()) { return; } List<StorageAttachment> attachments = storageAttachmentMapper.selectList( Wrappers.<StorageAttachment>lambdaQuery() .in(StorageAttachment::getRecordId, productModelIds) .eq(StorageAttachment::getApplication, StorageAttachmentConstants.StorageAttachmentImage) .eq(StorageAttachment::getDeleted, 0L) .orderByAsc(StorageAttachment::getId)); if (attachments == null || attachments.isEmpty()) { return; } Map<Long, List<StorageAttachment>> attachmentMap = attachments.stream() .collect(Collectors.groupingBy(StorageAttachment::getRecordId, java.util.LinkedHashMap::new, Collectors.toList())); List<Long> blobIds = attachments.stream() .map(StorageAttachment::getStorageBlobId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (blobIds.isEmpty()) { return; } Map<Long, StorageBlob> blobMap = storageBlobMapper.selectBatchIds(blobIds).stream() .filter(Objects::nonNull) .collect(Collectors.toMap(StorageBlob::getId, item -> item)); for (ProductionOrderVo record : records) { List<StorageAttachment> modelAttachments = attachmentMap.get(record.getProductModelId()); if (modelAttachments == null || modelAttachments.isEmpty()) { continue; } List<StorageBlobVO> images = modelAttachments.stream() .map(StorageAttachment::getStorageBlobId) .map(blobMap::get) .filter(Objects::nonNull) .map(this::toStorageBlobVO) .collect(Collectors.toList()); if (!images.isEmpty()) { record.setProductImages(images); } } } private StorageBlobVO toStorageBlobVO(StorageBlob blob) { StorageBlobVO vo = BeanUtil.copyProperties(blob, StorageBlobVO.class); vo.setPreviewURL(fileUtil.buildSignedPreviewUrl(vo)); vo.setDownloadURL(fileUtil.buildSignedDownloadUrl(vo)); return vo; } } src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -86,7 +86,6 @@ productionOrder.setQuantity(productionPlanDto.getTotalAssignedQuantity()); productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime()); productionOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode()); productionOrder.setStrength(productionPlanDto.getStrength()); return true; } src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -245,6 +245,8 @@ } ProductionAccount productionAccount = new ProductionAccount(); productionAccount.setProductionProductMainId(productionProductMain.getId()); productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId()); productionAccount.setSalesLedgerProductId(productionOrder.getSaleLedgerProductId() == null ? null : productionOrder.getSaleLedgerProductId().longValue()); productionAccount.setSchedulingUserId(user == null ? null : user.getUserId()); productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName()); productionAccount.setFinishedNum(productQty); src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; import com.ruoyi.framework.web.domain.R; @@ -14,8 +13,9 @@ import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.ProductionAccount; import com.ruoyi.production.pojo.ProductionOperationTask; import com.ruoyi.production.pojo.ProductionPlan; import com.ruoyi.production.pojo.ProductionOrder; import com.ruoyi.production.pojo.ProductionProductMain; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.quality.mapper.QualityInspectMapper; @@ -44,7 +44,9 @@ import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -66,8 +68,9 @@ private final ProductionAccountMapper productionAccountMapper; private final SalesLedgerMapper salesLedgerMapper; private final PurchaseLedgerMapper purchaseLedgerMapper; private final ProductionPlanMapper productionPlanMapper; private final ProductionOrderMapper productionOrderMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionOrderService productionOrderService; private final TechnologyRoutingMapper technologyRoutingMapper; private final TechnologyBomStructureMapper technologyBomStructureMapper; private final InvoiceRegistrationProductMapper invoiceRegistrationProductMapper; @@ -253,49 +256,70 @@ * 新增生产数据 */ public void addProductionData(SalesLedgerProduct salesLedgerProduct) { if (!Integer.valueOf(1).equals(salesLedgerProduct.getType())) { return; ProductionOrder productionOrder = new ProductionOrder(); productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); productionOrder.setProductModelId(salesLedgerProduct.getProductModelId()); productionOrder.setSaleLedgerProductId(salesLedgerProduct.getId().intValue()); productionOrder.setNpsNo(generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))); productionOrder.setQuantity(salesLedgerProduct.getQuantity()); productionOrder.setCompleteQuantity(BigDecimal.ZERO); TechnologyRouting routing = technologyRoutingMapper.selectOne( new QueryWrapper<TechnologyRouting>().lambda() .eq(TechnologyRouting::getProductModelId, salesLedgerProduct.getProductModelId()) .orderByDesc(TechnologyRouting::getCreateTime) .last("LIMIT 1")); if (routing != null) { productionOrder.setTechnologyRoutingId(routing.getId()); } SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId()); ProductionPlan productionPlan = new ProductionPlan(); productionPlan.setApplyNo(buildSalesPlanApplyNo(salesLedgerProduct.getId())); productionPlan.setSource("sales"); productionPlan.setProductModelId(salesLedgerProduct.getProductModelId()); productionPlan.setQtyRequired(salesLedgerProduct.getQuantity()); productionPlan.setRemark("销售台账自动生成"); productionPlan.setIssued(Boolean.FALSE); productionPlan.setStatus(0); if (salesLedger != null) { productionPlan.setMpsNo(salesLedger.getSalesContractNo()); if (salesLedger.getDeliveryDate() != null) { productionPlan.setRequiredDate(salesLedger.getDeliveryDate().atStartOfDay()); productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate().atStartOfDay()); } productionOrderMapper.insert(productionOrder); if (productionOrder.getTechnologyRoutingId() != null) { productionOrderService.syncProductionOrderSnapshot(productionOrder.getId()); } productionPlanMapper.insert(productionPlan); } /** * 删除生产数据 */ public void deleteProductionData(List<Long> productIds) { List<String> applyNos = productIds.stream() .filter(Objects::nonNull) .map(this::buildSalesPlanApplyNo) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(applyNos)) { List<ProductionOrder> productionOrders = productionOrderMapper.selectList( new LambdaQueryWrapper<ProductionOrder>() .in(ProductionOrder::getSaleLedgerProductId, productIds.stream().map(Long::intValue).collect(Collectors.toList()))); if (org.springframework.util.CollectionUtils.isEmpty(productionOrders)) { return; } List<ProductionPlan> productionPlans = productionPlanMapper.selectList( new LambdaQueryWrapper<ProductionPlan>().in(ProductionPlan::getApplyNo, applyNos)); if (CollectionUtils.isEmpty(productionPlans)) { return; List<Long> orderIds = productionOrders.stream().map(ProductionOrder::getId).collect(Collectors.toList()); List<Long> taskIds = productionOperationTaskMapper.selectList( new LambdaQueryWrapper<ProductionOperationTask>() .in(ProductionOperationTask::getProductionOrderId, orderIds)) .stream().map(ProductionOperationTask::getId).collect(Collectors.toList()); if (!taskIds.isEmpty()) { List<ProductionProductMain> productMains = productionProductMainMapper.selectList( new LambdaQueryWrapper<ProductionProductMain>() .in(ProductionProductMain::getProductionOperationTaskId, taskIds)); List<Long> productMainIds = productMains.stream().map(ProductionProductMain::getId).collect(Collectors.toList()); if (!productMainIds.isEmpty()) { List<QualityInspect> qualityInspects = qualityInspectMapper.selectList( new LambdaQueryWrapper<QualityInspect>().in(QualityInspect::getProductMainId, productMainIds)); qualityInspects.forEach(qualityInspect -> { if (qualityInspect.getInspectState() == 1) { throw new RuntimeException("已提交的检验单不能删除"); } }); productionProductOutputMapper.deleteByProductMainIds(productMainIds); productionProductInputMapper.deleteByProductMainIds(productMainIds); qualityInspectMapper.deleteByProductMainIds(productMainIds); productionAccountMapper.delete(new LambdaQueryWrapper<ProductionAccount>() .in(ProductionAccount::getProductionProductMainId, productMainIds)); for (Long productMainId : productMainIds) { stockUtils.deleteStockOutRecord(productMainId, StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode()); stockUtils.deleteStockInRecord(productMainId, StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode()); } } productionProductMainMapper.delete(new LambdaQueryWrapper<ProductionProductMain>() .in(ProductionProductMain::getProductionOperationTaskId, taskIds)); } boolean hasIssued = productionPlans.stream().anyMatch(item -> Boolean.TRUE.equals(item.getIssued()) || (item.getStatus() != null && item.getStatus() > 0)); if (hasIssued) { throw new ServiceException("对应生产计划已下发生成生产订单,请先处理生产计划/生产订单后再修改销售台账"); } productionPlanMapper.deleteByIds(productionPlans.stream().map(ProductionPlan::getId).collect(Collectors.toList())); productionOrderService.removeProductionOrder(orderIds); } @Override @@ -408,7 +432,21 @@ return R.ok(); } private String buildSalesPlanApplyNo(Long salesLedgerProductId) { return "SALE_PRODUCT_" + salesLedgerProductId; private String generateNextOrderNo(String datePrefix) { QueryWrapper<ProductionOrder> queryWrapper = new QueryWrapper<>(); queryWrapper.likeRight("nps_no", "SC" + datePrefix); queryWrapper.orderByDesc("nps_no"); queryWrapper.last("LIMIT 1"); ProductionOrder latestOrder = productionOrderMapper.selectOne(queryWrapper); int sequence = 1; if (latestOrder != null && latestOrder.getNpsNo() != null && !latestOrder.getNpsNo().isEmpty()) { String sequenceStr = latestOrder.getNpsNo().substring(("SC" + datePrefix).length()); try { sequence = Integer.parseInt(sequenceStr) + 1; } catch (NumberFormatException e) { sequence = 1; } } return "SC" + datePrefix + String.format("%04d", sequence); } } src/main/resources/mapper/production/ProductionOrderMapper.xml
@@ -5,6 +5,7 @@ <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.ruoyi.production.pojo.ProductionOrder"> <id column="id" property="id" /> <result column="sales_ledger_id" property="salesLedgerId" /> <result column="production_plan_ids" property="productionPlanIds" /> <result column="product_model_id" property="productModelId" /> <result column="nps_no" property="npsNo" /> @@ -15,6 +16,7 @@ <result column="complete_quantity" property="completeQuantity" /> <result column="start_time" property="startTime" /> <result column="end_time" property="endTime" /> <result column="sale_ledger_product_id" property="saleLedgerProductId" /> <result column="create_user" property="createUser" /> <result column="dept_id" property="deptId" /> </resultMap>