| | |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.ruoyi.ai.assistant.Assistant; |
| | | import com.ruoyi.approve.mapper.ApprovalInstanceMapper; |
| | | import com.ruoyi.approve.mapper.ApprovalInstanceNodeMapper; |
| | | import com.ruoyi.approve.mapper.ApprovalRecordMapper; |
| | | import com.ruoyi.approve.mapper.ApprovalTaskMapper; |
| | | import com.ruoyi.approve.pojo.ApprovalInstance; |
| | | import com.ruoyi.approve.pojo.ApprovalInstanceNode; |
| | | import com.ruoyi.approve.pojo.ApprovalRecord; |
| | | import com.ruoyi.approve.pojo.ApprovalTask; |
| | | import com.ruoyi.approve.service.ApprovalInstanceService; |
| | | import com.ruoyi.basic.mapper.ProductMapper; |
| | | import com.ruoyi.basic.mapper.ProductModelMapper; |
| | | import com.ruoyi.basic.pojo.Customer; |
| | | import com.ruoyi.basic.pojo.Product; |
| | | import com.ruoyi.basic.pojo.ProductModel; |
| | | import com.ruoyi.basic.pojo.SupplierManage; |
| | | import com.ruoyi.basic.service.ICustomerService; |
| | | import com.ruoyi.basic.service.ISupplierService; |
| | | import com.ruoyi.common.enums.ReviewStatusEnum; |
| | | import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; |
| | | import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; |
| | | import com.ruoyi.common.utils.OrderUtils; |
| | | import com.ruoyi.mock.dto.DataGenerateRequest; |
| | | import com.ruoyi.mock.prompt.MockDataPrompt; |
| | | import com.ruoyi.mock.service.DataGenerateService; |
| | | import com.ruoyi.mock.vo.DataGenerateResult; |
| | | import com.ruoyi.mock.vo.DataGenerateResult.ModuleSummary; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionPlan; |
| | | import com.ruoyi.procurementrecord.utils.StockUtils; |
| | | import com.ruoyi.production.bean.dto.ProductionOrderPickDto; |
| | | import com.ruoyi.production.bean.dto.ProductionPlanDto; |
| | | import com.ruoyi.production.bean.dto.ProductionProductMainDto; |
| | | import com.ruoyi.production.bean.vo.ProductionBomStructureVo; |
| | | import com.ruoyi.production.mapper.*; |
| | | import com.ruoyi.production.pojo.*; |
| | | import com.ruoyi.production.service.ProductionOrderPickService; |
| | | import com.ruoyi.production.service.ProductionOrderService; |
| | | import com.ruoyi.production.service.ProductionPlanService; |
| | | import com.ruoyi.production.service.ProductionProductMainService; |
| | | import com.ruoyi.purchase.dto.PurchaseLedgerDto; |
| | | import com.ruoyi.purchase.pojo.PurchaseLedger; |
| | | import com.ruoyi.purchase.service.IPurchaseLedgerService; |
| | | import com.ruoyi.project.system.domain.SysDept; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | | import com.ruoyi.project.system.mapper.SysDeptMapper; |
| | | import com.ruoyi.project.system.mapper.SysUserDeptMapper; |
| | | import com.ruoyi.project.system.mapper.SysUserMapper; |
| | | import com.ruoyi.quality.mapper.QualityInspectMapper; |
| | | import com.ruoyi.quality.pojo.QualityInspect; |
| | | import com.ruoyi.quality.pojo.QualityTestStandard; |
| | | import com.ruoyi.quality.pojo.QualityTestStandardBinding; |
| | |
| | | import com.ruoyi.quality.service.IQualityTestStandardService; |
| | | import com.ruoyi.quality.service.QualityTestStandardBindingService; |
| | | import com.ruoyi.sales.dto.SalesLedgerDto; |
| | | import com.ruoyi.sales.dto.ShippingInfoDto; |
| | | import com.ruoyi.sales.mapper.SalesLedgerMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | | import com.ruoyi.sales.mapper.ShippingInfoMapper; |
| | | import com.ruoyi.sales.mapper.ShippingProductDetailMapper; |
| | | import com.ruoyi.sales.pojo.SalesLedger; |
| | | import com.ruoyi.sales.pojo.SalesLedgerProduct; |
| | | import com.ruoyi.sales.pojo.ShippingInfo; |
| | | import com.ruoyi.sales.pojo.ShippingProductDetail; |
| | | import com.ruoyi.sales.service.ISalesLedgerService; |
| | | import com.ruoyi.sales.service.ShippingInfoService; |
| | | import com.ruoyi.stock.mapper.StockInRecordMapper; |
| | | import com.ruoyi.stock.mapper.StockInventoryMapper; |
| | | import com.ruoyi.stock.mapper.StockOutRecordMapper; |
| | | import com.ruoyi.stock.pojo.StockInventory; |
| | | import com.ruoyi.stock.pojo.StockOutRecord; |
| | | import com.ruoyi.stock.dto.StockInventoryDto; |
| | | import com.ruoyi.stock.service.StockInRecordService; |
| | | import com.ruoyi.stock.service.StockOutRecordService; |
| | | import com.ruoyi.stock.service.StockInventoryService; |
| | | import com.ruoyi.technology.mapper.TechnologyBomStructureMapper; |
| | | import com.ruoyi.technology.mapper.TechnologyRoutingMapper; |
| | | import com.ruoyi.technology.mapper.TechnologyRoutingOperationMapper; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Optional; |
| | | import java.util.Set; |
| | | import java.util.concurrent.ThreadLocalRandom; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | // 采购完整流程需要的service |
| | | private final ApprovalInstanceService approvalInstanceService; |
| | | private final ApprovalInstanceMapper approvalInstanceMapper; |
| | | private final ApprovalInstanceNodeMapper approvalInstanceNodeMapper; |
| | | private final ApprovalTaskMapper approvalTaskMapper; |
| | | private final ApprovalRecordMapper approvalRecordMapper; |
| | | private final IQualityInspectService qualityInspectService; |
| | | private final QualityInspectMapper qualityInspectMapper; |
| | | private final StockInRecordService stockInRecordService; |
| | | private final StockInRecordMapper stockInRecordMapper; |
| | | |
| | | // 销售完整流程需要的service/mapper |
| | | private final SalesLedgerMapper salesLedgerMapper; |
| | | private final SalesLedgerProductMapper salesLedgerProductMapper; |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionProductMainService productionProductMainService; |
| | | private final ShippingInfoService shippingInfoService; |
| | | private final ShippingInfoMapper shippingInfoMapper; |
| | | private final ShippingProductDetailMapper shippingProductDetailMapper; |
| | | private final StockUtils stockUtils; |
| | | private final StockOutRecordMapper stockOutRecordMapper; |
| | | private final StockOutRecordService stockOutRecordService; |
| | | private final StockInventoryMapper stockInventoryMapper; |
| | | |
| | | // 工艺数据检测 |
| | | private final TechnologyRoutingMapper technologyRoutingMapper; |
| | | private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; |
| | | private final TechnologyBomStructureMapper technologyBomStructureMapper; |
| | | |
| | | // 领料 |
| | | private final ProductionOrderPickService productionOrderPickService; |
| | | private final ProductionOrderBomMapper productionOrderBomMapper; |
| | | private final ProductionBomStructureMapper productionBomStructureMapper; |
| | | |
| | | // 用户/角色查询 |
| | | private final SysUserMapper sysUserMapper; |
| | | private final SysDeptMapper sysDeptMapper; |
| | | private final SysUserDeptMapper sysUserDeptMapper; |
| | | |
| | | @Override |
| | | public DataGenerateResult generate(DataGenerateRequest request) { |
| | |
| | | int totalGenerated = 0; |
| | | |
| | | try { |
| | | List<Long> productIds = productMapper.selectList(null).stream() |
| | | .map(p -> p.getId()).collect(Collectors.toList()); |
| | | List<Long> productModelIds = productModelMapper.selectList(null).stream() |
| | | .map(m -> m.getId()).collect(Collectors.toList()); |
| | | // 构建产品规格分类:通过Product树根节点判断成品/原材料 |
| | | Map<Long, String> productModelIdToCategory = buildProductModelCategoryMap(); |
| | | List<Long> productModelIds = new ArrayList<>(productModelIdToCategory.keySet()); |
| | | |
| | | // 加载已有客户/供应商(不新增,使用已有数据) |
| | | Map<String, Long> customerNameToId = loadExistingCustomers(); |
| | | Map<String, Long> supplierNameToId = loadExistingSuppliers(); |
| | | List<String> existingCustomerNames = new ArrayList<>(customerNameToId.keySet()); |
| | | List<String> existingSupplierNames = new ArrayList<>(supplierNameToId.keySet()); |
| | | |
| | | // 加载系统用户信息(昵称、角色、部门) |
| | | List<SysUser> systemUsers = loadSystemUsersWithDetail(); |
| | | |
| | | String systemPrompt = MockDataPrompt.buildSystemPrompt(); |
| | | String userMessage = MockDataPrompt.buildUserMessage( |
| | |
| | | request.getCountMin(), request.getCountMax(), |
| | | request.getDateStart(), request.getDateEnd(), |
| | | request.getAdditionalInfo(), |
| | | productIds, productModelIds); |
| | | productModelIdToCategory, |
| | | systemUsers, |
| | | existingCustomerNames, existingSupplierNames); |
| | | |
| | | String fullPrompt = systemPrompt + "\n\n" + userMessage; |
| | | log.info("调用AI生成模拟数据, modules={}, industries={}", request.getModules(), request.getIndustries()); |
| | |
| | | return result; |
| | | } |
| | | |
| | | // 修正AI生成的日期到指定时间范围内 |
| | | String dateStart = request.getDateStart(); |
| | | String dateEnd = request.getDateEnd(); |
| | | if (dateStart != null && dateEnd != null) { |
| | | fixDatesInRange(entities, dateStart, dateEnd); |
| | | } |
| | | |
| | | Map<String, List<JSONObject>> grouped = entities.stream() |
| | | .collect(Collectors.groupingBy(e -> e.getString("entity"))); |
| | | |
| | | // 名称→ID 映射表,用于创建业务单据时回填外键 |
| | | Map<String, Long> customerNameToId = new HashMap<>(); |
| | | Map<String, Long> supplierNameToId = new HashMap<>(); |
| | | |
| | | // Tier 0: 基础数据 |
| | | if (grouped.containsKey("customer")) { |
| | | ModuleSummary s = createCustomers(grouped.get("customer"), customerNameToId); |
| | | summaries.add(s); |
| | | totalGenerated += s.getSuccessCount(); |
| | | } |
| | | if (grouped.containsKey("supplier")) { |
| | | ModuleSummary s = createSuppliers(grouped.get("supplier"), supplierNameToId); |
| | | summaries.add(s); |
| | | totalGenerated += s.getSuccessCount(); |
| | | } |
| | | // Tier 0: 基础数据(客户和供应商不自动生成,使用已有数据) |
| | | if (grouped.containsKey("qualityTestStandard")) { |
| | | ModuleSummary s = createQualityStandards(grouped.get("qualityTestStandard")); |
| | | summaries.add(s); |
| | |
| | | |
| | | // Tier 1: 业务单据 |
| | | if (grouped.containsKey("salesLedger")) { |
| | | ModuleSummary s = createSalesLedgers(grouped.get("salesLedger"), customerNameToId); |
| | | ModuleSummary s = createSalesLedgers(grouped.get("salesLedger"), customerNameToId, request.getDateStart()); |
| | | summaries.add(s); |
| | | totalGenerated += s.getSuccessCount(); |
| | | } |
| | | if (grouped.containsKey("purchaseLedger")) { |
| | | ModuleSummary s = createPurchaseLedgers(grouped.get("purchaseLedger"), supplierNameToId, |
| | | request.getAdditionalInfo(), request.getDateEnd()); |
| | | request.getAdditionalInfo(), request.getDateStart(), request.getDateEnd()); |
| | | summaries.add(s); |
| | | totalGenerated += s.getSuccessCount(); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 修正AI生成的日期到指定时间范围内。 |
| | | * 遍历所有实体的所有字段,如果发现日期字符串(yyyy-MM-dd格式)超出范围,替换为范围内的随机日期。 |
| | | */ |
| | | private void fixDatesInRange(List<JSONObject> entities, String dateStart, String dateEnd) { |
| | | LocalDate start; |
| | | LocalDate end; |
| | | try { |
| | | start = LocalDate.parse(dateStart); |
| | | end = LocalDate.parse(dateEnd); |
| | | } catch (Exception e) { |
| | | log.warn("日期范围解析失败: {} ~ {}", dateStart, dateEnd); |
| | | return; |
| | | } |
| | | long daysBetween = ChronoUnit.DAYS.between(start, end); |
| | | if (daysBetween <= 0) { |
| | | daysBetween = 1; |
| | | } |
| | | |
| | | // 所有已知的日期字段名 |
| | | java.util.Set<String> dateFields = java.util.Set.of( |
| | | "entryDate", "executionDate", "deliveryDate", "requiredDate", |
| | | "promisedDeliveryDate", "planCompleteTime", "maintenanceTime", |
| | | "checkTime", "registerDate" |
| | | ); |
| | | |
| | | for (JSONObject entity : entities) { |
| | | for (String key : dateFields) { |
| | | String val = entity.getString(key); |
| | | if (val != null && val.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | try { |
| | | LocalDate d = LocalDate.parse(val); |
| | | if (d.isBefore(start) || d.isAfter(end)) { |
| | | LocalDate fixed = start.plusDays(ThreadLocalRandom.current().nextLong(daysBetween + 1)); |
| | | entity.put(key, fixed.toString()); |
| | | log.debug("修正日期 {}: {} -> {}", key, val, fixed); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | } else if (val != null && !val.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | // AI返回了无效日期格式,直接替换为范围内的随机日期 |
| | | LocalDate fixed = start.plusDays(ThreadLocalRandom.current().nextLong(daysBetween + 1)); |
| | | entity.put(key, fixed.toString()); |
| | | log.debug("修正无效日期 {}: {} -> {}", key, val, fixed); |
| | | } |
| | | } |
| | | // 修正合同编号中的日期: CG-20230601-001 -> CG-YYYYMMDD-001 |
| | | for (String key : java.util.Set.of("purchaseContractNumber", "salesContractNo", "standardNo", "batchNo")) { |
| | | String val = entity.getString(key); |
| | | if (val != null && val.matches(".*\\d{8}.*")) { |
| | | String datePart = val.replaceAll(".*?(\\d{8}).*", "$1"); |
| | | try { |
| | | // 验证提取到的是有效日期 |
| | | String dateStr = datePart.substring(0, 4) + "-" + datePart.substring(4, 6) + "-" + datePart.substring(6, 8); |
| | | LocalDate d = LocalDate.parse(dateStr); |
| | | if (d.isBefore(start) || d.isAfter(end)) { |
| | | LocalDate fixed = start.plusDays(ThreadLocalRandom.current().nextLong(daysBetween + 1)); |
| | | String newDatePart = fixed.format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE); |
| | | String newVal = val.replace(datePart, newDatePart); |
| | | entity.put(key, newVal); |
| | | log.debug("修正编号 {}: {} -> {}", key, val, newVal); |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ---- Tier 0: 基础数据 ---- |
| | | |
| | | /** |
| | | * 从数据库加载已有客户名称→ID映射 |
| | | */ |
| | | private Map<String, Long> loadExistingCustomers() { |
| | | Map<String, Long> map = new HashMap<>(); |
| | | List<Customer> customers = customerService.list(); |
| | | for (Customer c : customers) { |
| | | if (c.getCustomerName() != null) { |
| | | map.put(c.getCustomerName(), c.getId()); |
| | | } |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | /** |
| | | * 从数据库加载已有供应商名称→ID映射 |
| | | */ |
| | | private Map<String, Long> loadExistingSuppliers() { |
| | | Map<String, Long> map = new HashMap<>(); |
| | | List<SupplierManage> suppliers = supplierService.list(); |
| | | for (SupplierManage s : suppliers) { |
| | | if (s.getSupplierName() != null) { |
| | | map.put(s.getSupplierName(), s.getId()); |
| | | } |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | /** |
| | | * 加载系统用户信息(含角色和部门名称) |
| | | */ |
| | | private List<SysUser> loadSystemUsersWithDetail() { |
| | | return sysUserMapper.selectUserListWithDetail(); |
| | | } |
| | | |
| | | /** |
| | | * 构建产品规格ID→顶级产品分类名称的映射 |
| | | * 通过Product树向上找到根节点,判断"成品"/"原材料"/"半成品" |
| | | */ |
| | | private Map<Long, String> buildProductModelCategoryMap() { |
| | | // 加载所有Product节点 |
| | | List<Product> allProducts = productMapper.selectList(null); |
| | | Map<Long, Product> productMap = allProducts.stream() |
| | | .collect(Collectors.toMap(Product::getId, p -> p, (a, b) -> a)); |
| | | |
| | | // 对每个Product,沿parentId向上找到根节点名称 |
| | | Map<Long, String> productIdToRootName = new HashMap<>(); |
| | | for (Product p : allProducts) { |
| | | if (productIdToRootName.containsKey(p.getId())) continue; |
| | | // 向上遍历找根 |
| | | List<Long> chain = new ArrayList<>(); |
| | | Long currentId = p.getId(); |
| | | while (currentId != null) { |
| | | chain.add(currentId); |
| | | Product current = productMap.get(currentId); |
| | | if (current == null || current.getParentId() == null) break; |
| | | currentId = current.getParentId(); |
| | | } |
| | | // 根节点的productName就是分类名 |
| | | Product root = productMap.get(chain.get(chain.size() - 1)); |
| | | String rootName = root != null ? root.getProductName() : "其他"; |
| | | for (Long id : chain) { |
| | | productIdToRootName.put(id, rootName); |
| | | } |
| | | } |
| | | |
| | | // 对每个ProductModel,通过productId找到其所属分类 |
| | | List<ProductModel> allModels = productModelMapper.selectList(null); |
| | | Map<Long, String> result = new HashMap<>(); |
| | | for (ProductModel m : allModels) { |
| | | String category = m.getProductId() != null |
| | | ? productIdToRootName.getOrDefault(m.getProductId(), "其他") |
| | | : "其他"; |
| | | result.put(m.getId(), category); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private ModuleSummary createCustomers(List<JSONObject> items, Map<String, Long> nameToId) { |
| | | int success = 0, fail = 0; |
| | |
| | | c.setTaxpayerIdentificationNumber(item.getString("taxpayerIdentificationNumber")); |
| | | c.setMaintainer(item.getString("maintainer")); |
| | | if (item.containsKey("maintenanceTime")) { |
| | | c.setMaintenanceTime(java.sql.Date.valueOf(item.getString("maintenanceTime"))); |
| | | String mtStr = item.getString("maintenanceTime"); |
| | | if (mtStr != null && mtStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | c.setMaintenanceTime(java.sql.Date.valueOf(mtStr)); |
| | | } |
| | | } else { |
| | | c.setMaintenanceTime(new java.sql.Date(System.currentTimeMillis())); |
| | | } |
| | |
| | | |
| | | // ---- Tier 1: 业务单据 ---- |
| | | |
| | | private ModuleSummary createSalesLedgers(List<JSONObject> items, Map<String, Long> customerNameToId) { |
| | | private ModuleSummary createSalesLedgers(List<JSONObject> items, Map<String, Long> customerNameToId, String dateStart) { |
| | | int success = 0, fail = 0; |
| | | for (JSONObject item : items) { |
| | | try { |
| | |
| | | dto.setSalesman(item.getString("salesman")); |
| | | dto.setPaymentMethod(item.getString("paymentMethod")); |
| | | dto.setType(item.getInteger("type")); |
| | | // 录入人:优先使用AI提供的数据,否则用当前登录用户 |
| | | if (item.containsKey("entryPerson")) { |
| | | dto.setEntryPerson(item.getString("entryPerson")); |
| | | |
| | | // 录入人:优先用AI返回的,否则从"销售"角色/部门随机选 |
| | | String entryPerson = item.getString("entryPerson"); |
| | | if (entryPerson == null || entryPerson.isBlank()) { |
| | | SysUser salesUser = randomUserByKeyword("销售"); |
| | | entryPerson = salesUser != null ? salesUser.getNickName() : "系统"; |
| | | } |
| | | dto.setEntryPerson(entryPerson); |
| | | |
| | | // 业务员:优先用AI返回的,否则从"销售"角色/部门随机选 |
| | | String salesman = dto.getSalesman(); |
| | | if (salesman == null || salesman.isBlank()) { |
| | | SysUser salesUser = randomUserByKeyword("销售"); |
| | | salesman = salesUser != null ? salesUser.getNickName() : "系统"; |
| | | dto.setSalesman(salesman); |
| | | } |
| | | // customerId为null会导致"客户不存在"异常,跳过 |
| | | if (customerId == null) { |
| | | log.warn("跳过销售台账,客户[{}]不存在", customerName); |
| | | fail++; |
| | | continue; |
| | | } |
| | | LocalDate entryDate = null; |
| | | if (item.containsKey("entryDate")) { |
| | | dto.setEntryDate(java.sql.Date.valueOf(item.getString("entryDate"))); |
| | | String entryDateStr = item.getString("entryDate"); |
| | | if (entryDateStr != null && entryDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | entryDate = LocalDate.parse(entryDateStr); |
| | | dto.setEntryDate(java.sql.Date.valueOf(entryDateStr)); |
| | | } else { |
| | | dto.setEntryDate(java.sql.Date.valueOf(dateStart)); |
| | | } |
| | | } |
| | | if (item.containsKey("executionDate")) { |
| | | dto.setExecutionDate(LocalDate.parse(item.getString("executionDate"))); |
| | | String execDateStr = item.getString("executionDate"); |
| | | if (execDateStr != null && execDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | dto.setExecutionDate(LocalDate.parse(execDateStr)); |
| | | } else { |
| | | dto.setExecutionDate(LocalDate.parse(dateStart)); |
| | | } |
| | | } |
| | | if (item.containsKey("deliveryDate")) { |
| | | dto.setDeliveryDate(LocalDate.parse(item.getString("deliveryDate"))); |
| | | String delDateStr = item.getString("deliveryDate"); |
| | | if (delDateStr != null && delDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | dto.setDeliveryDate(LocalDate.parse(delDateStr)); |
| | | } else { |
| | | dto.setDeliveryDate(LocalDate.parse(dateStart)); |
| | | } |
| | | } |
| | | JSONArray productData = item.getJSONArray("productData"); |
| | | boolean hasProduction = false; |
| | | if (productData != null) { |
| | | List<SalesLedgerProduct> products = new ArrayList<>(); |
| | | for (int i = 0; i < productData.size(); i++) { |
| | |
| | | if (pd.containsKey("taxExclusiveTotalPrice")) { |
| | | slp.setTaxExclusiveTotalPrice(pd.getBigDecimal("taxExclusiveTotalPrice")); |
| | | } else if (pd.getBigDecimal("taxInclusiveTotalPrice") != null && pd.getBigDecimal("taxRate") != null) { |
| | | // 不含税总价 = 含税总价 / (1 + 税率/100) |
| | | slp.setTaxExclusiveTotalPrice( |
| | | pd.getBigDecimal("taxInclusiveTotalPrice").divide( |
| | | BigDecimal.ONE.add(pd.getBigDecimal("taxRate").divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP)), |
| | |
| | | } else { |
| | | slp.setTaxExclusiveTotalPrice(BigDecimal.ZERO); |
| | | } |
| | | slp.setIsProduction(true); |
| | | products.add(slp); |
| | | hasProduction = true; |
| | | } |
| | | dto.setProductData(products); |
| | | } |
| | | salesLedgerService.addOrUpdateSalesLedger(dto); |
| | | // addOrUpdateSalesLedger内部会自动生成合同号并insert,但dto上没有回写 |
| | | // 直接用dto的entryDate和customerId查最新创建的记录 |
| | | SalesLedger savedLedger = salesLedgerMapper.selectOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SalesLedger>() |
| | | .eq(SalesLedger::getCustomerId, dto.getCustomerId()) |
| | | .orderByDesc(SalesLedger::getId) |
| | | .last("limit 1")); |
| | | if (savedLedger != null && hasProduction) { |
| | | processSalesFullFlow(savedLedger, entryDate); |
| | | } |
| | | success++; |
| | | } catch (Exception e) { |
| | | log.warn("创建销售台账失败: {}", e.getMessage()); |
| | | log.warn("创建销售台账失败: {}", e.getMessage(), e); |
| | | fail++; |
| | | } |
| | | } |
| | | return summary("sales", "销售台账", items.size(), success, fail); |
| | | } |
| | | |
| | | // ==================== 销售完整流程 ==================== |
| | | |
| | | /** |
| | | * 销售完整流程: 生产计划合并下发 → 领料 → 报工 → 质检(可选) → 入库 → 发货 → 出库 |
| | | * 时间以销售录入日期为基准,各环节递推0-3天 |
| | | */ |
| | | private void processSalesFullFlow(SalesLedger salesLedger, LocalDate entryDate) { |
| | | try { |
| | | LocalDate baseDate = entryDate != null ? entryDate : LocalDate.now(); |
| | | |
| | | // 1. 查询销售台账产品(含生产计划) |
| | | List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SalesLedgerProduct>() |
| | | .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId()) |
| | | .eq(SalesLedgerProduct::getIsProduction, true)); |
| | | if (products.isEmpty()) { |
| | | log.info("销售台账[{}]无需生产,跳过生产流程", salesLedger.getSalesContractNo()); |
| | | return; |
| | | } |
| | | |
| | | // 检查产品是否具备完整的工艺数据(工艺路线、工序、BOM) |
| | | List<Long> missingTechProductIds = checkTechnologyDataReadiness(products); |
| | | if (!missingTechProductIds.isEmpty()) { |
| | | log.warn("销售台账[{}]以下产品缺少工艺数据(工艺路线/工序/BOM),跳过生产流程: {}", |
| | | salesLedger.getSalesContractNo(), missingTechProductIds); |
| | | return; |
| | | } |
| | | |
| | | // 2. 生产计划合并下发 → 生产订单 |
| | | LocalDate planDate = baseDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | processProductionCombine(salesLedger, planDate); |
| | | |
| | | // 3. 领料(物料不足自动入库后再领) |
| | | LocalDate pickDate = planDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); |
| | | processMaterialPick(salesLedger, pickDate); |
| | | |
| | | // 4. 生产报工 |
| | | LocalDate reportDate = pickDate.plusDays(ThreadLocalRandom.current().nextInt(1, 4)); |
| | | processProductionReport(salesLedger, reportDate); |
| | | |
| | | // 5. 质检提交 + 入库审批 |
| | | LocalDate qualityDate = reportDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | processSalesQualityAndStockIn(salesLedger, qualityDate); |
| | | |
| | | // 6. 发货 + 发货审批 + 出库审批 |
| | | LocalDate shipDate = qualityDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | processSalesShipping(salesLedger, shipDate); |
| | | |
| | | log.info("销售台账[{}]完整流程处理完毕", salesLedger.getSalesContractNo()); |
| | | } catch (Exception e) { |
| | | log.warn("销售完整流程处理失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 查询角色或部门含指定关键字的用户,随机返回一个 |
| | | */ |
| | | private SysUser randomUserByKeyword(String keyword) { |
| | | try { |
| | | List<Long> userIds = new ArrayList<>(sysUserMapper.getUserByRole(keyword)); |
| | | |
| | | List<SysDept> depts = sysDeptMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysDept>() |
| | | .like(SysDept::getDeptName, keyword) |
| | | .eq(SysDept::getDelFlag, "0") |
| | | .eq(SysDept::getStatus, "0")); |
| | | if (!depts.isEmpty()) { |
| | | List<Long> deptIds = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); |
| | | List<Long> deptUserIds = sysUserDeptMapper.selectDistinctUserIdsByDeptIds(deptIds); |
| | | userIds.addAll(deptUserIds); |
| | | } |
| | | |
| | | if (userIds.isEmpty()) { |
| | | return null; |
| | | } |
| | | List<Long> distinctIds = userIds.stream().distinct().collect(Collectors.toList()); |
| | | Long randomId = distinctIds.get(ThreadLocalRandom.current().nextInt(distinctIds.size())); |
| | | return sysUserMapper.selectUserById(randomId); |
| | | } catch (Exception e) { |
| | | log.warn("查询[{}]角色/部门用户失败: {}", keyword, e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 查询角色或部门含"生产"的用户,随机返回一个 |
| | | */ |
| | | private SysUser randomProductionUser() { |
| | | return randomUserByKeyword("生产"); |
| | | } |
| | | |
| | | /** |
| | | * 检查产品是否具备完整的工艺数据(工艺路线、工序、BOM) |
| | | * @return 缺少工艺数据的产品规格ID列表(空列表表示全部就绪) |
| | | */ |
| | | private List<Long> checkTechnologyDataReadiness(List<SalesLedgerProduct> products) { |
| | | List<Long> missingIds = new ArrayList<>(); |
| | | for (SalesLedgerProduct slp : products) { |
| | | Long productModelId = slp.getProductModelId(); |
| | | if (productModelId == null) continue; |
| | | |
| | | // 1. 检查工艺路线 |
| | | List<com.ruoyi.technology.pojo.TechnologyRouting> routings = |
| | | technologyRoutingMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.technology.pojo.TechnologyRouting>() |
| | | .eq(com.ruoyi.technology.pojo.TechnologyRouting::getProductModelId, productModelId)); |
| | | if (routings.isEmpty()) { |
| | | log.warn("产品规格[{}]缺少工艺路线", productModelId); |
| | | missingIds.add(productModelId); |
| | | continue; |
| | | } |
| | | Long routingId = routings.get(0).getId(); |
| | | |
| | | // 2. 检查工艺路线是否有关联工序 |
| | | List<com.ruoyi.technology.pojo.TechnologyRoutingOperation> routingOps = |
| | | technologyRoutingOperationMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.technology.pojo.TechnologyRoutingOperation>() |
| | | .eq(com.ruoyi.technology.pojo.TechnologyRoutingOperation::getTechnologyRoutingId, routingId)); |
| | | if (routingOps.isEmpty()) { |
| | | log.warn("产品规格[{}]工艺路线[{}]缺少工序", productModelId, routingId); |
| | | missingIds.add(productModelId); |
| | | continue; |
| | | } |
| | | |
| | | // 3. 检查BOM(可选但有更好) |
| | | com.ruoyi.technology.pojo.TechnologyRouting routing = routings.get(0); |
| | | if (routing.getBomId() != null) { |
| | | List<com.ruoyi.technology.pojo.TechnologyBomStructure> bomStructures = |
| | | technologyBomStructureMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.technology.pojo.TechnologyBomStructure>() |
| | | .eq(com.ruoyi.technology.pojo.TechnologyBomStructure::getBomId, routing.getBomId())); |
| | | if (bomStructures.isEmpty()) { |
| | | log.warn("产品规格[{}]BOM[{}]缺少产品结构", productModelId, routing.getBomId()); |
| | | missingIds.add(productModelId); |
| | | } |
| | | } |
| | | } |
| | | return missingIds; |
| | | } |
| | | |
| | | /** |
| | | * 生产计划合并下发 → 生产订单(按productModelId分组,同型号合并下发) |
| | | */ |
| | | private void processProductionCombine(SalesLedger salesLedger, LocalDate planDate) { |
| | | try { |
| | | List<ProductionPlan> plans = productionPlanMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ProductionPlan>() |
| | | .eq(ProductionPlan::getSalesLedgerId, salesLedger.getId()) |
| | | .and(w -> w.eq(ProductionPlan::getStatus, 0).or().isNull(ProductionPlan::getStatus))); |
| | | if (plans.isEmpty()) { |
| | | log.info("销售台账[{}]无待下发生产计划", salesLedger.getSalesContractNo()); |
| | | return; |
| | | } |
| | | |
| | | // 按productModelId分组,同型号才能合并下发;productModelId为null的单独逐个下发 |
| | | Map<Long, List<ProductionPlan>> grouped = plans.stream() |
| | | .filter(p -> p.getProductModelId() != null) |
| | | .collect(Collectors.groupingBy(ProductionPlan::getProductModelId)); |
| | | |
| | | for (Map.Entry<Long, List<ProductionPlan>> entry : grouped.entrySet()) { |
| | | try { |
| | | List<ProductionPlan> sameModelPlans = entry.getValue(); |
| | | BigDecimal totalQty = sameModelPlans.stream() |
| | | .map(p -> Optional.ofNullable(p.getQtyRequired()).orElse(BigDecimal.ZERO)) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | ProductionPlanDto combineDto = new ProductionPlanDto(); |
| | | combineDto.setIds(sameModelPlans.stream().map(ProductionPlan::getId).collect(Collectors.toList())); |
| | | combineDto.setTotalAssignedQuantity(totalQty); |
| | | combineDto.setPlanCompleteTime(planDate.plusDays(ThreadLocalRandom.current().nextInt(3, 7))); |
| | | productionPlanService.combine(combineDto); |
| | | |
| | | // 修正生产订单的planCompleteTime |
| | | List<ProductionOrder> orders = productionOrderMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ProductionOrder>() |
| | | .like(ProductionOrder::getProductionPlanIds, String.valueOf(sameModelPlans.get(0).getId()))); |
| | | for (ProductionOrder order : orders) { |
| | | order.setPlanCompleteTime(planDate.plusDays(ThreadLocalRandom.current().nextInt(3, 7))); |
| | | order.setStartTime(planDate.atStartOfDay()); |
| | | productionOrderMapper.updateById(order); |
| | | } |
| | | log.info("销售台账[{}]生产计划下发成功, productModelId={}, 计划数: {}", |
| | | salesLedger.getSalesContractNo(), entry.getKey(), sameModelPlans.size()); |
| | | } catch (Exception e) { |
| | | log.warn("销售台账[{}]生产计划下发失败, productModelId={}: {}", |
| | | salesLedger.getSalesContractNo(), entry.getKey(), e.getMessage()); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("生产计划下发失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 领料: 查询生产订单的BOM领料清单,检查库存,不足则先自动入库审批后再领料 |
| | | */ |
| | | private void processMaterialPick(SalesLedger salesLedger, LocalDate pickDate) { |
| | | try { |
| | | List<ProductionOrder> orders = findOrdersForSalesLedger(salesLedger); |
| | | if (orders.isEmpty()) { |
| | | log.info("销售台账[{}]无生产订单,跳过领料", salesLedger.getSalesContractNo()); |
| | | return; |
| | | } |
| | | |
| | | for (ProductionOrder order : orders) { |
| | | try { |
| | | // 查询订单BOM领料清单 |
| | | List<com.ruoyi.production.bean.vo.ProductionOrderPickVo> pickList = productionOrderService.pick(order.getId()); |
| | | if (pickList == null || pickList.isEmpty()) { |
| | | log.info("生产订单[{}]无领料清单", order.getId()); |
| | | continue; |
| | | } |
| | | |
| | | // 检查库存不足的物料,先自动入库 |
| | | ensureMaterialStock(pickList, pickDate); |
| | | |
| | | // 重新获取领料清单(入库后批号和库存量已更新) |
| | | pickList = productionOrderService.pick(order.getId()); |
| | | if (pickList == null || pickList.isEmpty()) { |
| | | log.info("生产订单[{}]重新获取领料清单为空", order.getId()); |
| | | continue; |
| | | } |
| | | |
| | | // 执行领料 |
| | | List<ProductionOrderPickDto> pickDtoList = new ArrayList<>(); |
| | | for (com.ruoyi.production.bean.vo.ProductionOrderPickVo pickVo : pickList) { |
| | | ProductionOrderPickDto pickDto = new ProductionOrderPickDto(); |
| | | pickDto.setProductionOrderId(order.getId()); |
| | | pickDto.setProductModelId(pickVo.getProductModelId()); |
| | | pickDto.setPickQuantity(pickVo.getDemandedQuantity()); |
| | | pickDto.setPickType((byte) 1); |
| | | pickDto.setOperationName(pickVo.getOperationName()); |
| | | pickDto.setTechnologyOperationId(pickVo.getTechnologyOperationId()); |
| | | pickDto.setDemandedQuantity(pickVo.getDemandedQuantity()); |
| | | pickDto.setBom(pickVo.getBom()); |
| | | // 设置批号(用更新后的批号列表) |
| | | if (pickVo.getBatchNoList() != null && !pickVo.getBatchNoList().isEmpty()) { |
| | | pickDto.setBatchNoList(pickVo.getBatchNoList()); |
| | | pickDto.setBatchNo(pickVo.getBatchNoList().get(0)); |
| | | } |
| | | pickDtoList.add(pickDto); |
| | | } |
| | | |
| | | if (!pickDtoList.isEmpty()) { |
| | | ProductionOrderPickDto batchDto = new ProductionOrderPickDto(); |
| | | batchDto.setPickList(pickDtoList); |
| | | productionOrderPickService.savePick(batchDto); |
| | | log.info("生产订单[{}]领料成功, 领料项数: {}", order.getId(), pickDtoList.size()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("生产订单[{}]领料失败: {}", order.getId(), e.getMessage()); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("领料流程失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 确保物料库存充足,不足则先自动入库审批 |
| | | */ |
| | | private void ensureMaterialStock(List<com.ruoyi.production.bean.vo.ProductionOrderPickVo> pickList, LocalDate pickDate) { |
| | | LocalDateTime pickDateTime = pickDate.atTime(9, 0, 0).plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | String pickDateStr = pickDate.format(DateTimeFormatter.BASIC_ISO_DATE); |
| | | |
| | | for (com.ruoyi.production.bean.vo.ProductionOrderPickVo pickVo : pickList) { |
| | | BigDecimal demanded = pickVo.getDemandedQuantity() != null ? pickVo.getDemandedQuantity() : BigDecimal.ZERO; |
| | | BigDecimal stockQty = pickVo.getStockQuantity() != null ? pickVo.getStockQuantity() : BigDecimal.ZERO; |
| | | BigDecimal shortage = demanded.subtract(stockQty); |
| | | |
| | | if (shortage.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; // 库存充足 |
| | | } |
| | | |
| | | // 库存不足,自动入库补充 |
| | | try { |
| | | StockInventoryDto dto = new StockInventoryDto(); |
| | | dto.setProductModelId(pickVo.getProductModelId()); |
| | | dto.setQualitity(shortage); |
| | | dto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode()); |
| | | dto.setRecordId(0L); |
| | | stockInventoryService.addStockInRecordOnly(dto); |
| | | |
| | | // 查找刚创建的入库记录 |
| | | List<com.ruoyi.stock.pojo.StockInRecord> records = stockInRecordService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>() |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getProductModelId, pickVo.getProductModelId()) |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getApprovalStatus, 0) |
| | | .orderByDesc(com.ruoyi.stock.pojo.StockInRecord::getId) |
| | | .last("limit 1")); |
| | | if (!records.isEmpty()) { |
| | | com.ruoyi.stock.pojo.StockInRecord record = records.get(0); |
| | | record.setCreateTime(pickDateTime); |
| | | record.setBatchNo(fixBatchNoDate(record.getBatchNo(), pickDateStr)); |
| | | record.setInboundBatches(fixBatchNoDate(record.getInboundBatches(), pickDateStr)); |
| | | stockInRecordMapper.updateById(record); |
| | | // 审批通过 |
| | | stockInRecordService.batchApprove(List.of(record.getId()), ReviewStatusEnum.APPROVED.getCode()); |
| | | log.info("物料[{}]自动入库审批完成, 入库数量: {}", pickVo.getProductModelId(), shortage); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("物料[{}]自动入库失败: {}", pickVo.getProductModelId(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 生产报工: 遍历工单逐个报工,从生产角色用户中随机选取报工人 |
| | | */ |
| | | private void processProductionReport(SalesLedger salesLedger, LocalDate reportDate) { |
| | | try { |
| | | List<ProductionOrder> orders = findOrdersForSalesLedger(salesLedger); |
| | | if (orders.isEmpty()) { |
| | | log.info("销售台账[{}]无生产订单", salesLedger.getSalesContractNo()); |
| | | return; |
| | | } |
| | | |
| | | for (ProductionOrder order : orders) { |
| | | List<ProductionOperationTask> tasks = productionOperationTaskMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ProductionOperationTask>() |
| | | .eq(ProductionOperationTask::getProductionOrderId, order.getId()) |
| | | .orderByAsc(ProductionOperationTask::getId)); |
| | | for (ProductionOperationTask task : tasks) { |
| | | if (task.getStatus() != null && task.getStatus() >= 3) continue; |
| | | |
| | | // 每次报工随机选一个生产用户 |
| | | SysUser productionUser = randomProductionUser(); |
| | | Long userId = productionUser != null ? productionUser.getUserId() : 1L; |
| | | String userName = productionUser != null ? productionUser.getNickName() : "系统"; |
| | | |
| | | // 报工 |
| | | ProductionProductMainDto reportDto = new ProductionProductMainDto(); |
| | | reportDto.setProductionOperationTaskId(task.getId()); |
| | | reportDto.setQuantity(task.getPlanQuantity()); |
| | | reportDto.setScrapQty(BigDecimal.ZERO); |
| | | reportDto.setUserId(userId); |
| | | reportDto.setUserName(userName); |
| | | productionProductMainService.addProductMain(reportDto); |
| | | |
| | | // 修正报工时间(重新从DB读取,避免覆盖addProductMain更新的completeQuantity) |
| | | fixProductionReportTimes(task.getId(), reportDate); |
| | | } |
| | | // 修正生产订单时间(重新从DB读取,避免覆盖addProductMain更新的completeQuantity) |
| | | fixProductionOrderTimes(order.getId(), reportDate); |
| | | } |
| | | log.info("销售台账[{}]生产报工完成, 订单数: {}", salesLedger.getSalesContractNo(), orders.size()); |
| | | } catch (Exception e) { |
| | | log.warn("生产报工失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 查找销售台账关联的生产订单 |
| | | */ |
| | | private List<ProductionOrder> findOrdersForSalesLedger(SalesLedger salesLedger) { |
| | | List<ProductionOrder> orders = new ArrayList<>(); |
| | | List<ProductionPlan> plans = productionPlanMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ProductionPlan>() |
| | | .eq(ProductionPlan::getSalesLedgerId, salesLedger.getId())); |
| | | for (ProductionPlan plan : plans) { |
| | | List<ProductionOrder> po = productionOrderMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ProductionOrder>() |
| | | .like(ProductionOrder::getProductionPlanIds, String.valueOf(plan.getId()))); |
| | | orders.addAll(po); |
| | | } |
| | | // 去重 |
| | | return orders.stream() |
| | | .collect(Collectors.toMap(ProductionOrder::getId, o -> o, (a, b) -> a)) |
| | | .values().stream().collect(Collectors.toList()); |
| | | } |
| | | |
| | | /** |
| | | * 修正生产报工相关时间(重新从DB读取task,避免覆盖addProductMain已更新的completeQuantity) |
| | | */ |
| | | private void fixProductionReportTimes(Long taskId, LocalDate reportDate) { |
| | | try { |
| | | ProductionOperationTask freshTask = productionOperationTaskMapper.selectById(taskId); |
| | | if (freshTask != null) { |
| | | freshTask.setActualStartTime(reportDate); |
| | | freshTask.setActualEndTime(reportDate); |
| | | freshTask.setStatus(4); |
| | | productionOperationTaskMapper.updateById(freshTask); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("修正生产报工时间失败: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 修正生产订单时间(重新从DB读取order,避免覆盖addProductMain已更新的completeQuantity) |
| | | */ |
| | | private void fixProductionOrderTimes(Long orderId, LocalDate reportDate) { |
| | | try { |
| | | ProductionOrder freshOrder = productionOrderMapper.selectById(orderId); |
| | | if (freshOrder != null) { |
| | | freshOrder.setStartTime(reportDate.minusDays(1).atStartOfDay()); |
| | | freshOrder.setEndTime(reportDate.plusDays(1).atStartOfDay()); |
| | | freshOrder.setStatus(3); |
| | | productionOrderMapper.updateById(freshOrder); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("修正生产订单时间失败: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 销售质检提交 + 入库审批 |
| | | */ |
| | | private void processSalesQualityAndStockIn(SalesLedger salesLedger, LocalDate qualityDate) { |
| | | try { |
| | | LocalDateTime qualityDateTime = qualityDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | |
| | | // 查所有未提交的质检单(inspectType=1过程检验, 2=出厂检验,由生产报工生成) |
| | | List<QualityInspect> allUnsubmitted = qualityInspectService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityInspect>() |
| | | .eq(QualityInspect::getInspectState, 0) |
| | | .in(QualityInspect::getInspectType, java.util.Arrays.asList(1, 2))); |
| | | for (QualityInspect qi : allUnsubmitted) { |
| | | qualityInspectService.autoSubmit(qi.getId()); |
| | | qi.setCreateTime(qualityDateTime); |
| | | qualityInspectMapper.updateById(qi); |
| | | } |
| | | log.info("销售台账[{}]质检提交完成, 质检单数: {}", salesLedger.getSalesContractNo(), allUnsubmitted.size()); |
| | | |
| | | // 入库审批:审批所有待审批的入库记录 |
| | | LocalDate stockDate = qualityDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); |
| | | LocalDateTime stockDateTime = stockDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | String stockDateStr = stockDate.format(DateTimeFormatter.BASIC_ISO_DATE); |
| | | List<com.ruoyi.stock.pojo.StockInRecord> stockRecords = stockInRecordService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>() |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getApprovalStatus, 0)); |
| | | List<Long> recordIds = stockRecords.stream() |
| | | .map(com.ruoyi.stock.pojo.StockInRecord::getId) |
| | | .collect(Collectors.toList()); |
| | | if (!recordIds.isEmpty()) { |
| | | for (com.ruoyi.stock.pojo.StockInRecord sr : stockRecords) { |
| | | sr.setCreateTime(stockDateTime); |
| | | sr.setBatchNo(fixBatchNoDate(sr.getBatchNo(), stockDateStr)); |
| | | sr.setInboundBatches(fixBatchNoDate(sr.getInboundBatches(), stockDateStr)); |
| | | stockInRecordMapper.updateById(sr); |
| | | } |
| | | stockInRecordService.batchApprove(recordIds, ReviewStatusEnum.APPROVED.getCode()); |
| | | for (com.ruoyi.stock.pojo.StockInRecord sr : stockRecords) { |
| | | StockInventory si = stockInventoryMapper.selectOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<StockInventory>() |
| | | .eq(StockInventory::getProductModelId, sr.getProductModelId()) |
| | | .eq(StockInventory::getBatchNo, sr.getBatchNo())); |
| | | if (si != null) { |
| | | si.setBatchNo(fixBatchNoDate(si.getBatchNo(), stockDateStr)); |
| | | stockInventoryMapper.updateById(si); |
| | | } |
| | | } |
| | | log.info("销售台账[{}]入库审批完成, 入库记录数: {}", salesLedger.getSalesContractNo(), recordIds.size()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("销售质检入库失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 发货 + 发货审批通过 + 出库审批 |
| | | */ |
| | | private void processSalesShipping(SalesLedger salesLedger, LocalDate shipDate) { |
| | | try { |
| | | LocalDateTime shipDateTime = shipDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | String shipDateStr = shipDate.format(DateTimeFormatter.BASIC_ISO_DATE); |
| | | |
| | | // 查找有库存的产品规格 |
| | | List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SalesLedgerProduct>() |
| | | .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId())); |
| | | |
| | | List<ShippingProductDetail> details = new ArrayList<>(); |
| | | for (SalesLedgerProduct slp : products) { |
| | | StockInventory si = stockInventoryMapper.selectOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<StockInventory>() |
| | | .eq(StockInventory::getProductModelId, slp.getProductModelId()) |
| | | .gt(StockInventory::getQualitity, BigDecimal.ZERO) |
| | | .orderByDesc(StockInventory::getId) |
| | | .last("limit 1")); |
| | | if (si != null) { |
| | | ShippingProductDetail detail = new ShippingProductDetail(); |
| | | detail.setStockInventoryId(si.getId()); |
| | | detail.setProductModelId(slp.getProductModelId()); |
| | | detail.setBatchNo(si.getBatchNo()); |
| | | detail.setQuantity(slp.getQuantity()); |
| | | details.add(detail); |
| | | } |
| | | } |
| | | if (details.isEmpty()) { |
| | | log.info("销售台账[{}]无可用库存,跳过发货", salesLedger.getSalesContractNo()); |
| | | return; |
| | | } |
| | | |
| | | // 创建发货单 |
| | | ShippingInfoDto shipDto = new ShippingInfoDto(); |
| | | shipDto.setSalesLedgerId(salesLedger.getId()); |
| | | shipDto.setSalesLedgerProductId(products.get(0).getId()); |
| | | shipDto.setCreateTime(shipDateTime); |
| | | shipDto.setBatchNoDetailList(details); |
| | | shippingInfoService.addReq(shipDto); |
| | | |
| | | // 找到发货单和审批实例 |
| | | ShippingInfo shippingInfo = shippingInfoMapper.selectOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ShippingInfo>() |
| | | .eq(ShippingInfo::getShippingNo, shipDto.getShippingNo()) |
| | | .last("limit 1")); |
| | | if (shippingInfo != null) { |
| | | shippingInfo.setCreateTime(shipDateTime); |
| | | shippingInfo.setShippingDate(java.sql.Date.valueOf(shipDate)); |
| | | // 根据客户地址生成发货车牌号 |
| | | shippingInfo.setShippingCarNumber(generateCarNumber(salesLedger.getCustomerName())); |
| | | shippingInfoMapper.updateById(shippingInfo); |
| | | |
| | | // 发货审批自动通过 |
| | | ApprovalInstance shipApproval = approvalInstanceMapper.selectOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalInstance>() |
| | | .eq(ApprovalInstance::getBusinessId, shippingInfo.getId()) |
| | | .eq(ApprovalInstance::getBusinessType, 7L) |
| | | .eq(ApprovalInstance::getDeleted, 0) |
| | | .orderByDesc(ApprovalInstance::getId) |
| | | .last("limit 1")); |
| | | if (shipApproval != null) { |
| | | LocalDate shipApproveDate = shipDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); |
| | | LocalDateTime shipApproveDateTime = shipApproveDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | approvalInstanceService.autoApprove(shipApproval.getId()); |
| | | fixApprovalTimes(shipApproval.getId(), shipApproveDateTime); |
| | | |
| | | shippingInfo.setStatus("已发货"); |
| | | shippingInfo.setShippingDate(java.sql.Date.valueOf(shipApproveDate)); |
| | | shippingInfoMapper.updateById(shippingInfo); |
| | | } |
| | | |
| | | // 出库审批 |
| | | LocalDate outDate = shipDate.plusDays(ThreadLocalRandom.current().nextInt(0, 3)); |
| | | LocalDateTime outDateTime = outDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | String outDateStr = outDate.format(DateTimeFormatter.BASIC_ISO_DATE); |
| | | List<StockOutRecord> outRecords = stockOutRecordMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<StockOutRecord>() |
| | | .eq(StockOutRecord::getRecordId, shippingInfo.getId()) |
| | | .eq(StockOutRecord::getRecordType, |
| | | String.valueOf(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode()))); |
| | | List<Long> outIds = new ArrayList<>(); |
| | | for (StockOutRecord sr : outRecords) { |
| | | if (sr.getApprovalStatus() == null || sr.getApprovalStatus() == 0 || sr.getApprovalStatus() == 3) { |
| | | sr.setCreateTime(outDateTime); |
| | | sr.setOutboundBatches(fixBatchNoDate(sr.getOutboundBatches(), outDateStr)); |
| | | sr.setBatchNo(fixBatchNoDate(sr.getBatchNo(), outDateStr)); |
| | | stockOutRecordMapper.updateById(sr); |
| | | outIds.add(sr.getId()); |
| | | } |
| | | } |
| | | if (!outIds.isEmpty()) { |
| | | // 出库审批通过(走正式审批流程,内部会扣减库存) |
| | | stockOutRecordService.batchApprove(outIds, ReviewStatusEnum.APPROVED.getCode()); |
| | | log.info("销售台账[{}]出库审批完成, 出库记录数: {}", |
| | | salesLedger.getSalesContractNo(), outIds.size()); |
| | | } |
| | | } |
| | | log.info("销售台账[{}]发货流程完成", salesLedger.getSalesContractNo()); |
| | | } catch (Exception e) { |
| | | log.warn("销售发货流程失败[{}]: {}", salesLedger.getSalesContractNo(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private ModuleSummary createPurchaseLedgers(List<JSONObject> items, Map<String, Long> supplierNameToId, |
| | | String additionalInfo, String dateEnd) { |
| | | String additionalInfo, String dateStart, String dateEnd) { |
| | | // 是否需要质检,从补充信息判断,默认不需要 |
| | | boolean needQualityInspect = additionalInfo != null |
| | | && (additionalInfo.contains("质检") || additionalInfo.contains("需要检验")); |
| | |
| | | |
| | | String entryDateStr = item.getString("entryDate"); |
| | | LocalDate entryDate = null; |
| | | if (item.containsKey("entryDate")) { |
| | | if (item.containsKey("entryDate") && entryDateStr != null && entryDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | entryDate = LocalDate.parse(entryDateStr); |
| | | dto.setEntryDate(java.sql.Date.valueOf(entryDateStr)); |
| | | } else { |
| | | dto.setEntryDate(java.sql.Date.valueOf(dateStart)); |
| | | } |
| | | if (item.containsKey("executionDate")) { |
| | | dto.setExecutionDate(java.sql.Date.valueOf(item.getString("executionDate"))); |
| | | String execDateStr = item.getString("executionDate"); |
| | | if (execDateStr != null && execDateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | dto.setExecutionDate(java.sql.Date.valueOf(execDateStr)); |
| | | } else { |
| | | dto.setExecutionDate(java.sql.Date.valueOf(dateStart)); |
| | | } |
| | | } |
| | | JSONArray productData = item.getJSONArray("productData"); |
| | | if (productData != null) { |
| | |
| | | } |
| | | success++; |
| | | } catch (Exception e) { |
| | | log.warn("创建采购台账失败: {}", e.getMessage()); |
| | | log.warn("创建采购台账失败: {}", e.getMessage(), e); |
| | | fail++; |
| | | } |
| | | } |
| | |
| | | |
| | | /** |
| | | * 采购完整流程: 审核通过 → 质检(可选) → 入库审核通过 |
| | | * 各环节时间以采购录入日期为基准,偶尔推迟1-3天 |
| | | */ |
| | | private void processPurchaseFullFlow(PurchaseLedger purchaseLedger, boolean needQualityInspect, |
| | | LocalDate entryDate, String dateEnd) { |
| | | try { |
| | | // 基准日期:采购录入日期,为空则用当天 |
| | | LocalDate baseDate = entryDate != null ? entryDate : LocalDate.now(); |
| | | // 审批时间:基于录入日期,随机推0-3天 |
| | | LocalDate approveDate = baseDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | LocalDateTime approveDateTime = approveDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | |
| | | // 1. 审批自动通过 |
| | | ApprovalInstance approvalInstance = approvalInstanceMapper.selectOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalInstance>() |
| | |
| | | .orderByDesc(ApprovalInstance::getId) |
| | | .last("limit 1")); |
| | | if (approvalInstance != null) { |
| | | // 结算审批开始时间:基于录入日期,随机推0-3天 |
| | | LocalDate baseDate = entryDate != null ? entryDate : LocalDate.now(); |
| | | LocalDate approveDate = baseDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | // 使用autoApprove完成审批 |
| | | approvalInstanceService.autoApprove(approvalInstance.getId()); |
| | | // 修正审批相关时间为采购录入日期范围 |
| | | fixApprovalTimes(approvalInstance.getId(), approveDateTime); |
| | | log.info("采购台账[{}]审批通过, 审批日期: {}", purchaseLedger.getPurchaseContractNumber(), approveDate); |
| | | } |
| | | |
| | | // 2. 质检流程(如果需要质检) |
| | | if (needQualityInspect) { |
| | | processQualityInspect(purchaseLedger, entryDate, dateEnd); |
| | | // 质检时间:审批之后再推0-3天 |
| | | LocalDate inspectDate = approveDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | processQualityInspect(purchaseLedger, inspectDate); |
| | | } |
| | | |
| | | // 3. 入库审批通过 |
| | | processStockInApprove(purchaseLedger, dateEnd); |
| | | // 入库时间:质检时间(有质检)或审批时间之后再推0-3天 |
| | | LocalDate stockBaseDate = needQualityInspect |
| | | ? approveDate.plusDays(ThreadLocalRandom.current().nextInt(1, 4)) |
| | | : approveDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4)); |
| | | processStockInApprove(purchaseLedger, stockBaseDate); |
| | | |
| | | } catch (Exception e) { |
| | | log.warn("采购完整流程处理失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage()); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 修正审批实例及关联节点、任务、记录的时间 |
| | | */ |
| | | private void fixApprovalTimes(Long instanceId, LocalDateTime dateTime) { |
| | | try { |
| | | // 更新审批实例的finishTime和createTime |
| | | ApprovalInstance instance = approvalInstanceMapper.selectById(instanceId); |
| | | if (instance != null) { |
| | | instance.setFinishTime(dateTime); |
| | | instance.setCreateTime(dateTime.minusHours(1)); |
| | | approvalInstanceMapper.updateById(instance); |
| | | } |
| | | |
| | | // 更新审批实例节点的finishTime |
| | | List<ApprovalInstanceNode> nodes = approvalInstanceNodeMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalInstanceNode>() |
| | | .eq(ApprovalInstanceNode::getInstanceId, instanceId)); |
| | | for (ApprovalInstanceNode node : nodes) { |
| | | node.setFinishTime(dateTime); |
| | | node.setCreateTime(dateTime.minusHours(1)); |
| | | approvalInstanceNodeMapper.updateById(node); |
| | | } |
| | | |
| | | // 更新审批任务的createTime |
| | | List<ApprovalTask> tasks = approvalTaskMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask>() |
| | | .eq(ApprovalTask::getInstanceId, instanceId)); |
| | | for (ApprovalTask task : tasks) { |
| | | task.setCreateTime(dateTime.minusMinutes(30)); |
| | | approvalTaskMapper.updateById(task); |
| | | } |
| | | |
| | | // 更新审批记录的createTime |
| | | List<ApprovalRecord> records = approvalRecordMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalRecord>() |
| | | .eq(ApprovalRecord::getInstanceId, instanceId)); |
| | | for (ApprovalRecord record : records) { |
| | | record.setCreateTime(dateTime.minusMinutes(10)); |
| | | approvalRecordMapper.updateById(record); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("修正审批时间失败: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 质检: 找到采购关联的质检单,自动提交为合格 |
| | | */ |
| | | private void processQualityInspect(PurchaseLedger purchaseLedger, LocalDate entryDate, String dateEnd) { |
| | | private void processQualityInspect(PurchaseLedger purchaseLedger, LocalDate inspectDate) { |
| | | try { |
| | | LocalDateTime inspectDateTime = inspectDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | List<QualityInspect> inspectList = qualityInspectService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityInspect>() |
| | | .eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId())); |
| | | for (QualityInspect qi : inspectList) { |
| | | if (qi.getInspectState() == null || qi.getInspectState() == 0) { |
| | | qualityInspectService.autoSubmit(qi.getId()); |
| | | log.info("采购台账[{}]质检单[{}]自动提交合格", purchaseLedger.getPurchaseContractNumber(), qi.getId()); |
| | | // 修正质检时间 |
| | | qi.setCreateTime(inspectDateTime); |
| | | qualityInspectMapper.updateById(qi); |
| | | log.info("采购台账[{}]质检单[{}]自动提交合格, 质检日期: {}", |
| | | purchaseLedger.getPurchaseContractNumber(), qi.getId(), inspectDate); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * 入库审核: 找到入库记录并审批通过 |
| | | * 入库审核: 找到入库记录并审批通过,并修正时间与批号日期 |
| | | */ |
| | | private void processStockInApprove(PurchaseLedger purchaseLedger, String dateEnd) { |
| | | private void processStockInApprove(PurchaseLedger purchaseLedger, LocalDate stockDate) { |
| | | try { |
| | | LocalDateTime stockDateTime = stockDate.atTime(9, 0, 0) |
| | | .plusMinutes(ThreadLocalRandom.current().nextInt(0, 480)); |
| | | String stockDateStr = stockDate.format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE); |
| | | |
| | | List<com.ruoyi.stock.pojo.StockInRecord> stockRecords = stockInRecordService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>() |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordId, purchaseLedger.getId()) |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordType, |
| | | String.valueOf(com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode()))); |
| | | // 如果按PURCHASE_STOCK_IN找不到,尝试CUSTOMIZATION_UNSTOCK_OUT(质检合格入库) |
| | | if (stockRecords.isEmpty()) { |
| | | stockRecords = stockInRecordService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>() |
| | |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordType, |
| | | String.valueOf(com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()))); |
| | | } |
| | | // 也尝试按质检单ID查找(质检单的recordId是质检单ID) |
| | | for (QualityInspect qi : qualityInspectService.list( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityInspect>() |
| | | .eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId()))) { |
| | |
| | | .eq(com.ruoyi.stock.pojo.StockInRecord::getRecordId, qi.getId())); |
| | | stockRecords.addAll(qiRecords); |
| | | } |
| | | // 去重 |
| | | stockRecords = stockRecords.stream() |
| | | .collect(Collectors.toMap(com.ruoyi.stock.pojo.StockInRecord::getId, r -> r, (a, b) -> a)) |
| | | .values().stream().collect(Collectors.toList()); |
| | |
| | | .map(com.ruoyi.stock.pojo.StockInRecord::getId) |
| | | .collect(Collectors.toList()); |
| | | if (!recordIds.isEmpty()) { |
| | | // 先修正入库记录的createTime和批号日期(batchApprove内部会用batchNo创建StockInventory) |
| | | for (Long recordId : recordIds) { |
| | | com.ruoyi.stock.pojo.StockInRecord sr = stockInRecordMapper.selectById(recordId); |
| | | if (sr != null) { |
| | | sr.setCreateTime(stockDateTime); |
| | | sr.setBatchNo(fixBatchNoDate(sr.getBatchNo(), stockDateStr)); |
| | | String s = OrderUtils.countTodayByCreateTime(stockInRecordMapper, "RK", "inbound_batches", stockDateTime); |
| | | sr.setInboundBatches(s); |
| | | stockInRecordMapper.updateById(sr); |
| | | } |
| | | } |
| | | stockInRecordService.batchApprove(recordIds, ReviewStatusEnum.APPROVED.getCode()); |
| | | log.info("采购台账[{}]入库审批通过, 入库记录数: {}", purchaseLedger.getPurchaseContractNumber(), recordIds.size()); |
| | | log.info("采购台账[{}]入库审批通过, 入库日期: {}, 入库记录数: {}", |
| | | purchaseLedger.getPurchaseContractNumber(), stockDate, recordIds.size()); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 替换批号中的日期部分(yyyyMMdd → 指定日期) |
| | | */ |
| | | /** |
| | | * 根据客户名称查地址,推断省份简称生成车牌号 |
| | | * 格式: 省份简称 + 字母 + 5位随机数字字母, 如 苏A12345 |
| | | */ |
| | | private String generateCarNumber(String customerName) { |
| | | // 省份简称到城市字母的常见映射 |
| | | Map<String, String[]> provinceCityMap = new HashMap<>(); |
| | | provinceCityMap.put("京", new String[]{"A", "B", "C", "E", "F", "G"}); |
| | | provinceCityMap.put("沪", new String[]{"A", "B", "D", "E", "F"}); |
| | | provinceCityMap.put("粤", new String[]{"A", "B", "E", "F", "G", "H", "J", "K"}); |
| | | provinceCityMap.put("苏", new String[]{"A", "B", "E", "F", "G", "H", "J", "K", "L", "M"}); |
| | | provinceCityMap.put("浙", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L"}); |
| | | provinceCityMap.put("鲁", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "Y"}); |
| | | provinceCityMap.put("豫", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "U"}); |
| | | provinceCityMap.put("川", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}); |
| | | provinceCityMap.put("渝", new String[]{"A", "B", "C", "D"}); |
| | | provinceCityMap.put("鄂", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S"}); |
| | | provinceCityMap.put("湘", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "S"}); |
| | | provinceCityMap.put("闽", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K"}); |
| | | provinceCityMap.put("赣", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M"}); |
| | | provinceCityMap.put("皖", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S"}); |
| | | provinceCityMap.put("冀", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "R", "T"}); |
| | | provinceCityMap.put("辽", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P"}); |
| | | provinceCityMap.put("吉", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K"}); |
| | | provinceCityMap.put("黑", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R"}); |
| | | provinceCityMap.put("晋", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M"}); |
| | | provinceCityMap.put("陕", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "V"}); |
| | | provinceCityMap.put("桂", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R"}); |
| | | provinceCityMap.put("云", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S"}); |
| | | provinceCityMap.put("贵", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J"}); |
| | | provinceCityMap.put("甘", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P"}); |
| | | provinceCityMap.put("琼", new String[]{"A", "B", "C", "D", "E", "F"}); |
| | | provinceCityMap.put("宁", new String[]{"A", "B", "C", "D", "E"}); |
| | | provinceCityMap.put("青", new String[]{"A", "B", "C", "D", "E", "F", "G", "H"}); |
| | | provinceCityMap.put("藏", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J"}); |
| | | provinceCityMap.put("蒙", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M"}); |
| | | provinceCityMap.put("新", new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R"}); |
| | | provinceCityMap.put("津", new String[]{"A", "B", "C", "E", "F", "G", "H", "J", "K", "L", "M", "N", "Q", "R"}); |
| | | |
| | | // 地址关键词到省份简称的映射 |
| | | Map<String, String> addressToProvince = new HashMap<>(); |
| | | addressToProvince.put("北京", "京"); addressToProvince.put("上海", "沪"); |
| | | addressToProvince.put("广州", "粤"); addressToProvince.put("深圳", "粤"); |
| | | addressToProvince.put("东莞", "粤"); addressToProvince.put("佛山", "粤"); |
| | | addressToProvince.put("南京", "苏"); addressToProvince.put("苏州", "苏"); |
| | | addressToProvince.put("无锡", "苏"); addressToProvince.put("南通", "苏"); |
| | | addressToProvince.put("常州", "苏"); addressToProvince.put("徐州", "苏"); |
| | | addressToProvince.put("杭州", "浙"); addressToProvince.put("宁波", "浙"); |
| | | addressToProvince.put("温州", "浙"); addressToProvince.put("嘉兴", "浙"); |
| | | addressToProvince.put("济南", "鲁"); addressToProvince.put("青岛", "鲁"); |
| | | addressToProvince.put("郑州", "豫"); addressToProvince.put("成都", "川"); |
| | | addressToProvince.put("重庆", "渝"); addressToProvince.put("武汉", "鄂"); |
| | | addressToProvince.put("长沙", "湘"); addressToProvince.put("福州", "闽"); |
| | | addressToProvince.put("厦门", "闽"); addressToProvince.put("南昌", "赣"); |
| | | addressToProvince.put("合肥", "皖"); addressToProvince.put("石家庄", "冀"); |
| | | addressToProvince.put("沈阳", "辽"); addressToProvince.put("大连", "辽"); |
| | | addressToProvince.put("长春", "吉"); addressToProvince.put("哈尔滨", "黑"); |
| | | addressToProvince.put("太原", "晋"); addressToProvince.put("西安", "陕"); |
| | | addressToProvince.put("南宁", "桂"); addressToProvince.put("昆明", "云"); |
| | | addressToProvince.put("贵阳", "贵"); addressToProvince.put("兰州", "甘"); |
| | | addressToProvince.put("海口", "琼"); addressToProvince.put("银川", "宁"); |
| | | addressToProvince.put("西宁", "青"); addressToProvince.put("拉萨", "藏"); |
| | | addressToProvince.put("呼和浩特", "蒙"); addressToProvince.put("乌鲁木齐", "新"); |
| | | addressToProvince.put("天津", "津"); |
| | | // 省名映射 |
| | | addressToProvince.put("江苏", "苏"); addressToProvince.put("浙江", "浙"); |
| | | addressToProvince.put("山东", "鲁"); addressToProvince.put("河南", "豫"); |
| | | addressToProvince.put("四川", "川"); addressToProvince.put("湖北", "鄂"); |
| | | addressToProvince.put("湖南", "湘"); addressToProvince.put("福建", "闽"); |
| | | addressToProvince.put("江西", "赣"); addressToProvince.put("安徽", "皖"); |
| | | addressToProvince.put("河北", "冀"); addressToProvince.put("辽宁", "辽"); |
| | | addressToProvince.put("吉林", "吉"); addressToProvince.put("黑龙江", "黑"); |
| | | addressToProvince.put("山西", "晋"); addressToProvince.put("陕西", "陕"); |
| | | addressToProvince.put("广东", "粤"); addressToProvince.put("广西", "桂"); |
| | | addressToProvince.put("云南", "云"); addressToProvince.put("贵州", "贵"); |
| | | addressToProvince.put("甘肃", "甘"); addressToProvince.put("海南", "琼"); |
| | | addressToProvince.put("宁夏", "宁"); addressToProvince.put("青海", "青"); |
| | | addressToProvince.put("西藏", "藏"); addressToProvince.put("内蒙古", "蒙"); |
| | | addressToProvince.put("新疆", "新"); |
| | | |
| | | String province = "苏"; // 默认江苏 |
| | | // 尝试从客户地址推断省份 |
| | | if (customerName != null) { |
| | | try { |
| | | Customer customer = customerService.getOne( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Customer>() |
| | | .eq(Customer::getCustomerName, customerName) |
| | | .last("limit 1")); |
| | | if (customer != null && customer.getCompanyAddress() != null) { |
| | | String addr = customer.getCompanyAddress(); |
| | | for (Map.Entry<String, String> e : addressToProvince.entrySet()) { |
| | | if (addr.contains(e.getKey())) { |
| | | province = e.getValue(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.debug("查客户地址失败: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | // 生成车牌号: 省份简称 + 城市字母 + 5位随机字符(数字+大写字母) |
| | | String[] cities = provinceCityMap.getOrDefault(province, new String[]{"A", "B", "C"}); |
| | | String city = cities[ThreadLocalRandom.current().nextInt(cities.length)]; |
| | | String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789"; |
| | | StringBuilder suffix = new StringBuilder(); |
| | | for (int i = 0; i < 5; i++) { |
| | | suffix.append(chars.charAt(ThreadLocalRandom.current().nextInt(chars.length()))); |
| | | } |
| | | return province + city + suffix; |
| | | } |
| | | |
| | | private String fixBatchNoDate(String batchNo, String newDate) { |
| | | if (batchNo == null || batchNo.length() < 8) { |
| | | return batchNo; |
| | | } |
| | | // 批号格式: yyyyMMdd-产品编码-NNN |
| | | String prefix = batchNo.substring(0, 8); |
| | | if (prefix.matches("\\d{8}")) { |
| | | return newDate + batchNo.substring(8); |
| | | } |
| | | return batchNo; |
| | | } |
| | | |
| | | private ModuleSummary createProductionPlans(List<JSONObject> items) { |
| | | int success = 0, fail = 0; |
| | | List<ProductionPlan> createdPlans = new ArrayList<>(); |
| | | for (JSONObject item : items) { |
| | | try { |
| | | ProductionPlan plan = new ProductionPlan(); |
| | |
| | | plan.setSource(item.getString("source")); |
| | | plan.setRemark(item.getString("remark")); |
| | | if (item.containsKey("requiredDate")) { |
| | | plan.setRequiredDate(LocalDate.parse(item.getString("requiredDate"))); |
| | | String dateStr = item.getString("requiredDate"); |
| | | if (dateStr != null && dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | plan.setRequiredDate(LocalDate.parse(dateStr)); |
| | | } |
| | | } |
| | | if (item.containsKey("promisedDeliveryDate")) { |
| | | plan.setPromisedDeliveryDate(LocalDate.parse(item.getString("promisedDeliveryDate"))); |
| | | String dateStr = item.getString("promisedDeliveryDate"); |
| | | if (dateStr != null && dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | plan.setPromisedDeliveryDate(LocalDate.parse(dateStr)); |
| | | } |
| | | } |
| | | productionPlanService.save(plan); |
| | | createdPlans.add(plan); |
| | | success++; |
| | | } catch (Exception e) { |
| | | log.warn("创建生产计划失败: {}", e.getMessage()); |
| | | fail++; |
| | | } |
| | | } |
| | | |
| | | // 按productModelId分组,自动合并下发 |
| | | Map<Long, List<ProductionPlan>> grouped = createdPlans.stream() |
| | | .filter(p -> p.getProductModelId() != null) |
| | | .collect(Collectors.groupingBy(ProductionPlan::getProductModelId)); |
| | | for (Map.Entry<Long, List<ProductionPlan>> entry : grouped.entrySet()) { |
| | | try { |
| | | List<Long> planIds = entry.getValue().stream().map(ProductionPlan::getId).collect(Collectors.toList()); |
| | | BigDecimal totalQty = entry.getValue().stream() |
| | | .map(ProductionPlan::getQtyRequired) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | ProductionPlanDto combineDto = new ProductionPlanDto(); |
| | | combineDto.setIds(planIds); |
| | | combineDto.setTotalAssignedQuantity(totalQty); |
| | | combineDto.setPlanCompleteTime(LocalDate.now().plusDays(ThreadLocalRandom.current().nextInt(3, 10))); |
| | | productionPlanService.combine(combineDto); |
| | | log.info("生产计划自动下发成功, productModelId={}, 计划数: {}, 下发数量: {}", |
| | | entry.getKey(), planIds.size(), totalQty); |
| | | } catch (Exception e) { |
| | | log.warn("生产计划自动下发失败, productModelId={}: {}", entry.getKey(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | return summary("production", "生产计划", items.size(), success, fail); |
| | | } |
| | | |
| | |
| | | order.setProductModelId(item.getLong("productModelId")); |
| | | order.setQuantity(item.getBigDecimal("quantity")); |
| | | if (item.containsKey("planCompleteTime")) { |
| | | order.setPlanCompleteTime(LocalDate.parse(item.getString("planCompleteTime"))); |
| | | String dateStr = item.getString("planCompleteTime"); |
| | | if (dateStr != null && dateStr.matches("\\d{4}-\\d{2}-\\d{2}")) { |
| | | order.setPlanCompleteTime(LocalDate.parse(dateStr)); |
| | | } |
| | | } |
| | | productionOrderService.saveProductionOrder(order); |
| | | success++; |