package com.ruoyi.mock.service.impl;
|
|
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSONArray;
|
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.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.IQualityInspectService;
|
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.BigDecimal;
|
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;
|
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class DataGenerateServiceImpl implements DataGenerateService {
|
|
private final Assistant assistant;
|
private final ProductMapper productMapper;
|
private final ProductModelMapper productModelMapper;
|
|
private final ICustomerService customerService;
|
private final ISalesLedgerService salesLedgerService;
|
private final ISupplierService supplierService;
|
private final IPurchaseLedgerService purchaseLedgerService;
|
private final IQualityTestStandardService qualityTestStandardService;
|
private final QualityTestStandardBindingService qualityTestStandardBindingService;
|
private final ProductionPlanService productionPlanService;
|
private final ProductionOrderService productionOrderService;
|
private final StockInventoryService stockInventoryService;
|
|
// 采购完整流程需要的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) {
|
DataGenerateResult result = new DataGenerateResult();
|
List<ModuleSummary> summaries = new ArrayList<>();
|
List<String> errors = new ArrayList<>();
|
int totalGenerated = 0;
|
|
try {
|
// 构建产品规格分类:通过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.getModules(), request.getIndustries(),
|
request.getCountMin(), request.getCountMax(),
|
request.getDateStart(), request.getDateEnd(),
|
request.getAdditionalInfo(),
|
productModelIdToCategory,
|
systemUsers,
|
existingCustomerNames, existingSupplierNames);
|
|
String fullPrompt = systemPrompt + "\n\n" + userMessage;
|
log.info("调用AI生成模拟数据, modules={}, industries={}", request.getModules(), request.getIndustries());
|
String llmResponse = assistant.chat(fullPrompt);
|
log.info("AI返回数据长度: {}", llmResponse != null ? llmResponse.length() : 0);
|
|
if (llmResponse == null || llmResponse.isBlank()) {
|
result.setStatus("FAILED");
|
errors.add("AI未返回任何数据");
|
result.setErrors(errors);
|
return result;
|
}
|
|
List<JSONObject> entities = parseJsonResponse(llmResponse);
|
if (entities.isEmpty()) {
|
result.setStatus("FAILED");
|
errors.add("无法解析AI返回的数据");
|
result.setErrors(errors);
|
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")));
|
|
// Tier 0: 基础数据(客户和供应商不自动生成,使用已有数据)
|
if (grouped.containsKey("qualityTestStandard")) {
|
ModuleSummary s = createQualityStandards(grouped.get("qualityTestStandard"));
|
summaries.add(s);
|
totalGenerated += s.getSuccessCount();
|
}
|
|
// Tier 1: 业务单据
|
if (grouped.containsKey("salesLedger")) {
|
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.getDateStart(), request.getDateEnd());
|
summaries.add(s);
|
totalGenerated += s.getSuccessCount();
|
}
|
if (grouped.containsKey("productionPlan")) {
|
ModuleSummary s = createProductionPlans(grouped.get("productionPlan"));
|
summaries.add(s);
|
totalGenerated += s.getSuccessCount();
|
}
|
|
// Tier 2
|
if (grouped.containsKey("productionOrder")) {
|
ModuleSummary s = createProductionOrders(grouped.get("productionOrder"));
|
summaries.add(s);
|
totalGenerated += s.getSuccessCount();
|
}
|
if (grouped.containsKey("qualityTestStandardBinding")) {
|
ModuleSummary s = createQualityBindings(grouped.get("qualityTestStandardBinding"));
|
summaries.add(s);
|
totalGenerated += s.getSuccessCount();
|
}
|
if (grouped.containsKey("stockInventory")) {
|
ModuleSummary s = createStockInventories(grouped.get("stockInventory"));
|
summaries.add(s);
|
totalGenerated += s.getSuccessCount();
|
}
|
|
result.setStatus(errors.isEmpty() ? "SUCCESS" : "PARTIAL");
|
result.setTotalGenerated(totalGenerated);
|
result.setModuleSummaries(summaries);
|
result.setErrors(errors);
|
|
} catch (Exception e) {
|
log.error("数据生成失败", e);
|
result.setStatus("FAILED");
|
errors.add("数据生成异常: " + e.getMessage());
|
result.setErrors(errors);
|
}
|
|
return result;
|
}
|
|
private List<JSONObject> parseJsonResponse(String response) {
|
try {
|
String trimmed = response.trim();
|
if (trimmed.startsWith("```")) {
|
int start = trimmed.indexOf("\n");
|
int end = trimmed.lastIndexOf("```");
|
if (start > 0 && end > start) {
|
trimmed = trimmed.substring(start + 1, end).trim();
|
}
|
}
|
return JSON.parseArray(trimmed, JSONObject.class);
|
} catch (Exception e) {
|
log.warn("JSON解析失败,尝试提取数组片段: {}", e.getMessage());
|
int start = response.indexOf('[');
|
int end = response.lastIndexOf(']');
|
if (start >= 0 && end > start) {
|
return JSON.parseArray(response.substring(start, end + 1), JSONObject.class);
|
}
|
return List.of();
|
}
|
}
|
|
/**
|
* 修正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;
|
for (JSONObject item : items) {
|
try {
|
Customer c = new Customer();
|
c.setCustomerName(item.getString("customerName"));
|
c.setCustomerType(item.getString("customerType"));
|
c.setContactPerson(item.getString("contactPerson"));
|
c.setContactPhone(item.getString("contactPhone"));
|
c.setCompanyAddress(item.getString("companyAddress"));
|
c.setCompanyPhone(item.getString("companyPhone"));
|
c.setTaxpayerIdentificationNumber(item.getString("taxpayerIdentificationNumber"));
|
c.setMaintainer(item.getString("maintainer"));
|
if (item.containsKey("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()));
|
}
|
if (item.containsKey("bankAccount")) {
|
c.setBankAccount(item.getString("bankAccount"));
|
}
|
if (item.containsKey("basicBankAccount")) {
|
c.setBasicBankAccount(item.getString("basicBankAccount"));
|
}
|
if (item.containsKey("bankCode")) {
|
c.setBankCode(item.getString("bankCode"));
|
}
|
customerService.insertCustomer(c);
|
if (c.getId() != null) {
|
nameToId.put(c.getCustomerName(), c.getId());
|
}
|
success++;
|
} catch (Exception e) {
|
log.warn("创建客户失败: {}", e.getMessage());
|
fail++;
|
}
|
}
|
return summary("sales", "客户", items.size(), success, fail);
|
}
|
|
private ModuleSummary createSuppliers(List<JSONObject> items, Map<String, Long> nameToId) {
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
SupplierManage s = new SupplierManage();
|
s.setSupplierName(item.getString("supplierName"));
|
s.setSupplierType(item.getString("supplierType"));
|
s.setContactUserName(item.getString("contactUserName"));
|
s.setContactUserPhone(item.getString("contactUserPhone"));
|
s.setCompanyAddress(item.getString("companyAddress"));
|
s.setCompanyPhone(item.getString("companyPhone"));
|
s.setTaxpayerIdentificationNum(item.getString("taxpayerIdentificationNum"));
|
s.setBankAccountName(item.getString("bankAccountName"));
|
s.setBankAccountNum(item.getString("bankAccountNum"));
|
if (item.containsKey("isWhite")) {
|
s.setIsWhite(item.getInteger("isWhite"));
|
}
|
supplierService.saveSupplier(s);
|
if (s.getId() != null) {
|
nameToId.put(s.getSupplierName(), s.getId());
|
}
|
success++;
|
} catch (Exception e) {
|
log.warn("创建供应商失败: {}", e.getMessage());
|
fail++;
|
}
|
}
|
return summary("purchase", "供应商", items.size(), success, fail);
|
}
|
|
private ModuleSummary createQualityStandards(List<JSONObject> items) {
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
QualityTestStandard std = new QualityTestStandard();
|
std.setStandardNo(item.getString("standardNo"));
|
std.setStandardName(item.getString("standardName"));
|
std.setInspectType(item.getInteger("inspectType"));
|
std.setRemark(item.getString("remark"));
|
qualityTestStandardService.save(std);
|
success++;
|
} catch (Exception e) {
|
log.warn("创建检测标准失败: {}", e.getMessage());
|
fail++;
|
}
|
}
|
return summary("quality", "检测标准", items.size(), success, fail);
|
}
|
|
// ---- Tier 1: 业务单据 ----
|
|
private ModuleSummary createSalesLedgers(List<JSONObject> items, Map<String, Long> customerNameToId, String dateStart) {
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
String customerName = item.getString("customerName");
|
Long customerId = customerNameToId.get(customerName);
|
|
SalesLedgerDto dto = new SalesLedgerDto();
|
dto.setCustomerId(customerId);
|
dto.setCustomerName(customerName);
|
dto.setProjectName(item.getString("projectName"));
|
dto.setSalesman(item.getString("salesman"));
|
dto.setPaymentMethod(item.getString("paymentMethod"));
|
dto.setType(item.getInteger("type"));
|
|
// 录入人:优先用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")) {
|
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")) {
|
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")) {
|
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++) {
|
JSONObject pd = productData.getJSONObject(i);
|
SalesLedgerProduct slp = new SalesLedgerProduct();
|
slp.setProductId(pd.getLong("productId"));
|
slp.setProductModelId(pd.getLong("productModelId"));
|
slp.setQuantity(pd.getBigDecimal("quantity"));
|
slp.setTaxInclusiveUnitPrice(pd.getBigDecimal("taxInclusiveUnitPrice"));
|
slp.setTaxInclusiveTotalPrice(pd.getBigDecimal("taxInclusiveTotalPrice"));
|
slp.setTaxRate(pd.getBigDecimal("taxRate"));
|
slp.setUnit(pd.getString("unit"));
|
slp.setType(pd.getInteger("type"));
|
if (pd.containsKey("taxExclusiveTotalPrice")) {
|
slp.setTaxExclusiveTotalPrice(pd.getBigDecimal("taxExclusiveTotalPrice"));
|
} else if (pd.getBigDecimal("taxInclusiveTotalPrice") != null && pd.getBigDecimal("taxRate") != null) {
|
slp.setTaxExclusiveTotalPrice(
|
pd.getBigDecimal("taxInclusiveTotalPrice").divide(
|
BigDecimal.ONE.add(pd.getBigDecimal("taxRate").divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP)),
|
2, 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(), 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 dateStart, String dateEnd) {
|
// 是否需要质检,从补充信息判断,默认不需要
|
boolean needQualityInspect = additionalInfo != null
|
&& (additionalInfo.contains("质检") || additionalInfo.contains("需要检验"));
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
String supplierName = item.getString("supplierName");
|
Long supplierId = supplierNameToId.get(supplierName);
|
|
PurchaseLedgerDto dto = new PurchaseLedgerDto();
|
dto.setSupplierId(supplierId);
|
dto.setSupplierName(supplierName);
|
dto.setPurchaseContractNumber(item.getString("purchaseContractNumber"));
|
dto.setProjectName(item.getString("projectName"));
|
dto.setContractAmount(item.getBigDecimal("contractAmount"));
|
dto.setPaymentMethod(item.getString("paymentMethod"));
|
|
String entryDateStr = item.getString("entryDate");
|
LocalDate entryDate = null;
|
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")) {
|
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) {
|
List<SalesLedgerProduct> products = new ArrayList<>();
|
for (int i = 0; i < productData.size(); i++) {
|
JSONObject pd = productData.getJSONObject(i);
|
SalesLedgerProduct slp = new SalesLedgerProduct();
|
slp.setProductId(pd.getLong("productId"));
|
slp.setProductModelId(pd.getLong("productModelId"));
|
slp.setQuantity(pd.getBigDecimal("quantity"));
|
slp.setTaxInclusiveUnitPrice(pd.getBigDecimal("taxInclusiveUnitPrice"));
|
slp.setTaxInclusiveTotalPrice(pd.getBigDecimal("taxInclusiveTotalPrice"));
|
slp.setTaxRate(pd.getBigDecimal("taxRate"));
|
slp.setUnit(pd.getString("unit"));
|
slp.setType(2);
|
// 是否质检:从补充信息判断,默认不需要
|
slp.setIsChecked(needQualityInspect);
|
if (pd.containsKey("taxExclusiveTotalPrice")) {
|
slp.setTaxExclusiveTotalPrice(pd.getBigDecimal("taxExclusiveTotalPrice"));
|
} else if (pd.getBigDecimal("taxInclusiveTotalPrice") != null && pd.getBigDecimal("taxRate") != null) {
|
slp.setTaxExclusiveTotalPrice(
|
pd.getBigDecimal("taxInclusiveTotalPrice").divide(
|
BigDecimal.ONE.add(pd.getBigDecimal("taxRate").divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP)),
|
2, RoundingMode.HALF_UP));
|
} else {
|
slp.setTaxExclusiveTotalPrice(BigDecimal.ZERO);
|
}
|
products.add(slp);
|
}
|
dto.setProductData(products);
|
}
|
purchaseLedgerService.addOrEditPurchase(dto);
|
// 通过合同号找到刚创建的采购台账
|
PurchaseLedger savedLedger = purchaseLedgerService.getOne(
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PurchaseLedger>()
|
.eq(PurchaseLedger::getPurchaseContractNumber, dto.getPurchaseContractNumber())
|
.last("limit 1"));
|
if (savedLedger != null) {
|
// 走完整流程:审核通过 → 质检(可选) → 入库 + 入库审核通过
|
processPurchaseFullFlow(savedLedger, needQualityInspect, entryDate, dateEnd);
|
}
|
success++;
|
} catch (Exception e) {
|
log.warn("创建采购台账失败: {}", e.getMessage(), e);
|
fail++;
|
}
|
}
|
return summary("purchase", "采购台账", items.size(), success, 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>()
|
.eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
|
.eq(ApprovalInstance::getBusinessType, 5L)
|
.eq(ApprovalInstance::getDeleted, 0)
|
.orderByDesc(ApprovalInstance::getId)
|
.last("limit 1"));
|
if (approvalInstance != null) {
|
approvalInstanceService.autoApprove(approvalInstance.getId());
|
// 修正审批相关时间为采购录入日期范围
|
fixApprovalTimes(approvalInstance.getId(), approveDateTime);
|
log.info("采购台账[{}]审批通过, 审批日期: {}", purchaseLedger.getPurchaseContractNumber(), approveDate);
|
}
|
|
// 2. 质检流程(如果需要质检)
|
if (needQualityInspect) {
|
// 质检时间:审批之后再推0-3天
|
LocalDate inspectDate = approveDate.plusDays(ThreadLocalRandom.current().nextInt(0, 4));
|
processQualityInspect(purchaseLedger, inspectDate);
|
}
|
|
// 3. 入库审批通过
|
// 入库时间:质检时间(有质检)或审批时间之后再推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 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());
|
// 修正质检时间
|
qi.setCreateTime(inspectDateTime);
|
qualityInspectMapper.updateById(qi);
|
log.info("采购台账[{}]质检单[{}]自动提交合格, 质检日期: {}",
|
purchaseLedger.getPurchaseContractNumber(), qi.getId(), inspectDate);
|
}
|
}
|
} catch (Exception e) {
|
log.warn("质检流程处理失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage());
|
}
|
}
|
|
/**
|
* 入库审核: 找到入库记录并审批通过,并修正时间与批号日期
|
*/
|
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())));
|
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::getRecordId, purchaseLedger.getId())
|
.eq(com.ruoyi.stock.pojo.StockInRecord::getRecordType,
|
String.valueOf(com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode())));
|
}
|
for (QualityInspect qi : qualityInspectService.list(
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QualityInspect>()
|
.eq(QualityInspect::getPurchaseLedgerId, purchaseLedger.getId()))) {
|
List<com.ruoyi.stock.pojo.StockInRecord> qiRecords = stockInRecordService.list(
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.ruoyi.stock.pojo.StockInRecord>()
|
.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());
|
|
if (!stockRecords.isEmpty()) {
|
List<Long> recordIds = stockRecords.stream()
|
.filter(r -> r.getApprovalStatus() == null || r.getApprovalStatus() == 0)
|
.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(), stockDate, recordIds.size());
|
}
|
}
|
} catch (Exception e) {
|
log.warn("入库审批失败[{}]: {}", purchaseLedger.getPurchaseContractNumber(), e.getMessage());
|
}
|
}
|
|
/**
|
* 替换批号中的日期部分(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.setProductModelId(item.getLong("productModelId"));
|
plan.setQtyRequired(item.getBigDecimal("qtyRequired"));
|
plan.setSource(item.getString("source"));
|
plan.setRemark(item.getString("remark"));
|
if (item.containsKey("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")) {
|
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);
|
}
|
|
// ---- Tier 2: 后续单据 ----
|
|
private ModuleSummary createProductionOrders(List<JSONObject> items) {
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
ProductionOrder order = new ProductionOrder();
|
order.setProductModelId(item.getLong("productModelId"));
|
order.setQuantity(item.getBigDecimal("quantity"));
|
if (item.containsKey("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++;
|
} catch (Exception e) {
|
log.warn("创建生产订单失败: {}", e.getMessage());
|
fail++;
|
}
|
}
|
return summary("production", "生产订单", items.size(), success, fail);
|
}
|
|
private ModuleSummary createQualityBindings(List<JSONObject> items) {
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
QualityTestStandardBinding binding = new QualityTestStandardBinding();
|
binding.setProductId(item.getLong("productId"));
|
binding.setTestStandardId(item.getInteger("testStandardId"));
|
qualityTestStandardBindingService.add(List.of(binding));
|
success++;
|
} catch (Exception e) {
|
log.warn("创建指标绑定失败: {}", e.getMessage());
|
fail++;
|
}
|
}
|
return summary("quality", "指标绑定", items.size(), success, fail);
|
}
|
|
private ModuleSummary createStockInventories(List<JSONObject> items) {
|
int success = 0, fail = 0;
|
for (JSONObject item : items) {
|
try {
|
StockInventoryDto dto = new StockInventoryDto();
|
dto.setProductModelId(item.getLong("productModelId"));
|
dto.setQualitity(item.getBigDecimal("qualitity"));
|
dto.setBatchNo(item.getString("batchNo"));
|
dto.setWarnNum(item.getBigDecimal("warnNum"));
|
dto.setRemark(item.getString("remark"));
|
stockInventoryService.addstockInventory(dto);
|
success++;
|
} catch (Exception e) {
|
log.warn("创建库存记录失败: {}", e.getMessage());
|
fail++;
|
}
|
}
|
return summary("stock", "库存记录", items.size(), success, fail);
|
}
|
|
private ModuleSummary summary(String module, String entityName, int total, int success, int fail) {
|
ModuleSummary s = new ModuleSummary();
|
s.setModule(module);
|
s.setEntityName(entityName);
|
s.setGeneratedCount(total);
|
s.setSuccessCount(success);
|
s.setFailCount(fail);
|
return s;
|
}
|
}
|