package com.jsh.erp.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.jsh.erp.constants.ExceptionConstants; import com.jsh.erp.datasource.entities.DepotHeadVo4Body; import com.jsh.erp.datasource.entities.MaterialVo4Unit; import com.jsh.erp.datasource.entities.Supplier; import com.jsh.erp.datasource.entities.Unit; import com.jsh.erp.datasource.tesco.request.ErpXsddReqVO; import com.jsh.erp.datasource.vo.DepotHeadXsddRequestVO; import com.jsh.erp.datasource.vo.DepotItemXsddRequestVO; import com.jsh.erp.datasource.vo.Material4UnitPrice; import com.jsh.erp.exception.BusinessRunTimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; /** * @Description TODO * @Author MS.BLUE * @Date 2025-04-12 */ @Service public class SyncTescoSystemService { private Logger logger = LoggerFactory.getLogger(SyncTescoSystemService.class); @Resource private MaterialService materialService; @Resource private DepotHeadService depotHeadService; @Resource private SequenceService sequenceService; @Resource private SupplierService supplierService; public String syncSystemSku(String param) { logger.info("获取商品系统sku,返回信息集采系统 param:{}", param); JSONObject result = new JSONObject(); JSONArray dataArray = new JSONArray(); try { // 参数校验 if (param == null || param.isEmpty()) { result.put("code", 1); result.put("msg", "参数不能为空"); return result.toJSONString(); } // 解析传入的JSON参数 JSONArray barCodeArray = JSON.parseArray(param); if (barCodeArray == null || barCodeArray.isEmpty()) { result.put("code", 1); result.put("msg", "参数格式错误"); return result.toJSONString(); } // 提取 barCode 到字符串数组 List barCodeList = new ArrayList<>(); for (int i = 0; i < barCodeArray.size(); i++) { JSONObject barCodeObj = barCodeArray.getJSONObject(i); String barCode = barCodeObj.getString("barCode"); barCodeList.add(barCode); } // 查询商品信息 List materialList = materialService.getMaterialByBarCode(barCodeList); if (materialList != null && !materialList.isEmpty()) { result.put("code", 0); for (MaterialVo4Unit material : materialList) { JSONObject item = new JSONObject(); item.put("barCode", material.getmBarCode()); item.put("systemSku", material.getSystemSku()); dataArray.add(item); } } else { result.put("code", 1); result.put("msg", "未找到对应商品信息"); } } catch (Exception e) { e.printStackTrace(); result.put("code", 1); result.put("msg", "系统异常,请稍后重试"); } // 将数据数组放入返回结果 result.put("data", dataArray); logger.info(" result:{}", result); return result.toJSONString(); } /** * 集采下单成功->封装同步erp销售订单请求体 { orderSn:"订单编号", customer:{//客户信息 name:"客户名称", contact:"客户联系方式", address:"客户地址" }, item:[{//订单商品信息 erp_sku:"erp_sku编号", unitPrice:"商品单价", quantity:"商品下单数量", price:"商品实付价格" }] } * @param param * @return */ public String syncOrder(String param) { logger.info("同步集采订单-》销售订单 param:{}", param); JSONObject result = new JSONObject(); try { // 解析传入的JSON参数为 ErpXsddReqVO ErpXsddReqVO erpXsddReqVO = JSON.parseObject(param, ErpXsddReqVO.class); if (erpXsddReqVO == null) { result.put("code", 1); result.put("msg", "参数不能为空"); return result.toJSONString(); } // 校验订单编号 if (erpXsddReqVO.getOrderSn() == null || erpXsddReqVO.getOrderSn().isEmpty()) { result.put("code", 1); result.put("msg", "订单编号不能为空"); return result.toJSONString(); } // 校验客户信息 if (erpXsddReqVO.getCustomer() == null) { result.put("code", 1); result.put("msg", "客户信息不能为空"); return result.toJSONString(); } // 校验订单商品信息 if (erpXsddReqVO.getItems() == null || erpXsddReqVO.getItems().isEmpty()) { result.put("code", 1); result.put("msg", "订单商品信息不能为空"); return result.toJSONString(); } // 构建订单和商品信息 buildOrderAndItem(erpXsddReqVO); // 返回成功结果 result.put("code", 0); result.put("msg", "订单同步成功"); } catch (Exception e) { logger.error("同步订单发生异常", e); result.put("code", 1); result.put("msg", "系统异常,请稍后重试"); } return result.toJSONString(); } @Transactional(value = "transactionManager", rollbackFor = Exception.class) private void buildOrderAndItem(ErpXsddReqVO erpXsddReqVO) throws Exception { // 1. 初始化订单基本信息 DepotHeadXsddRequestVO order = initSalesOrder(erpXsddReqVO); // 2. 处理订单商品项 List itemList = processOrderItems(erpXsddReqVO, order); // 3. 提交订单 depotHeadService.syncOrderToXsdd(order, itemList); } private DepotHeadXsddRequestVO initSalesOrder(ErpXsddReqVO erpXsddReqVO) throws Exception { DepotHeadXsddRequestVO order = new DepotHeadXsddRequestVO(); order.setType("其它"); order.setSubType("销售订单"); order.setLinkTesco(erpXsddReqVO.getOrderSn()); ErpXsddReqVO.Customer customer = erpXsddReqVO.getCustomer(); // 根据手机号查询客户 Supplier supplier = supplierService.getCustomerByPhone(customer.getAccount()); if (supplier == null) { // 如果客户不存在,创建新客户 supplier = new Supplier(); supplier.setSupplier(customer.getName()); supplier.setTelephone(customer.getAccount()); supplier.setContacts(customer.getReceiverName()); supplier.setPhoneNum(customer.getReceiverPhone()); supplier.setAddress(customer.getReceiverAddress()); supplier = supplierService.createCustomer(supplier); } // 绑定客户 ID order.setOrganId(supplier.getId()); order.setReceiverName(customer.getReceiverName()); order.setReceiverPhone(customer.getReceiverPhone()); order.setReceiverAddress(customer.getReceiverAddress()); order.setRemark("集采同步订单"); String number = sequenceService.buildOnlyNumber(); String defaultNumber = "XSDD" + number; order.setDefaultNumber(defaultNumber); order.setNumber(defaultNumber); order.setCreateTime(new Date()); order.setOperTime(new Date()); order.setCreator(1L); // 创建人ID order.setAccountId(1L); // 账户ID order.setChangeAmount(BigDecimal.ZERO); order.setBackAmount(BigDecimal.ZERO); order.setTotalPrice(erpXsddReqVO.getTotalPrice()); order.setPayType("现付"); order.setDiscount(BigDecimal.ZERO); order.setDiscountMoney(BigDecimal.ZERO); order.setDiscountLastMoney(erpXsddReqVO.getTotalPrice()); order.setStatus("0"); order.setPurchaseStatus("0"); order.setSource("2"); // 来源集采商城 order.setTenantId(null); order.setDeleteFlag("0"); return order; } /** * 处理订单商品项 */ private List processOrderItems(ErpXsddReqVO erpXsddReqVO, DepotHeadXsddRequestVO order) throws Exception { List itemList = new ArrayList<>(); List reqVOItems = erpXsddReqVO.getItems(); // 1. 获取所有商品信息 List systemSkuList = reqVOItems.stream().map(ErpXsddReqVO.OrderItem::getErpSku).collect(Collectors.toList()); List materialList = materialService.getMaterialBySystemSku(systemSkuList); Map> skuToMaterialMap = materialList.stream().collect(Collectors.groupingBy(MaterialVo4Unit::getSystemSku)); // 2. 处理每个订单项 for (ErpXsddReqVO.OrderItem reqVOItem : reqVOItems) { try { processSingleOrderItem(reqVOItem, order, itemList, skuToMaterialMap); } catch (Exception e) { logger.error("处理订单项失败 erp_sku: {}: {}", reqVOItem.getErpSku(), e.getMessage()); throw e; } } return itemList; } /** * 处理单个订单项 */ private void processSingleOrderItem(ErpXsddReqVO.OrderItem reqVOItem, DepotHeadXsddRequestVO order, List itemList, Map> skuToMaterialMap) throws Exception { // 1. 参数校验 validateOrderItem(reqVOItem); // 2. 获取商品信息 List materials = skuToMaterialMap.get(reqVOItem.getErpSku()); if (CollectionUtils.isEmpty(materials)) { throw new BusinessRunTimeException(ExceptionConstants.MATERIAL_ERP_SKU_NOT_DECIMAL_CODE, String.format(ExceptionConstants.MATERIAL_ERP_SKU_NOT_DECIMAL_MSG, reqVOItem.getErpSku())); } // 3. 获取单位信息 MaterialVo4Unit primaryMaterial = materials.get(0); Material4UnitPrice material4UnitPrice = getMaterial4UnitPrice(primaryMaterial); // 4. 匹配销售单位 UnitMatchResult matchResult = matchUnitAndCalculateBaseQuantity(reqVOItem.getUnitPrice(), BigDecimal.valueOf(reqVOItem.getQuantity()), material4UnitPrice); // 5. 根据单位类型分配库存 if (matchResult.isLargeUnit()) { allocateLargeUnitStock(reqVOItem, order, itemList, materials, matchResult, material4UnitPrice); } else { allocateBasicUnitStock(reqVOItem, order, itemList, materials, matchResult); } } /** * 分配大单位库存(不拆分配) */ private void allocateLargeUnitStock(ErpXsddReqVO.OrderItem reqVOItem, DepotHeadXsddRequestVO order, List itemList, List materials, UnitMatchResult matchResult, Material4UnitPrice material4UnitPrice) throws Exception { String orderUnit = matchResult.getMatchedUnit(); BigDecimal unitRatio = getUnitRatio(orderUnit, material4UnitPrice); BigDecimal remainingOrderQty = matchResult.getOrderUnitQuantity(); // 分配库存 for (MaterialVo4Unit m : materials) { if (remainingOrderQty.compareTo(BigDecimal.ZERO) <= 0) break; BigDecimal inventory = m.getInventory(); if (inventory.compareTo(unitRatio) < 0) continue; // 计算可分配数量(向下取整) BigDecimal allocatableUnits = inventory.divideToIntegralValue(unitRatio).min(remainingOrderQty); if (allocatableUnits.compareTo(BigDecimal.ZERO) > 0) { BigDecimal allocBaseQty = allocatableUnits.multiply(unitRatio); // 创建订单项 DepotItemXsddRequestVO item = createOrderItem(order, m, orderUnit, allocatableUnits, allocBaseQty, reqVOItem.getUnitPrice()); itemList.add(item); // 更新库存 m.setInventory(inventory.subtract(allocBaseQty)); // 更新剩余需求 remainingOrderQty = remainingOrderQty.subtract(allocatableUnits); logger.info("分配库存:批次[{}] 分配 {} {} (剩余需求: {})", m.getBatchNumber(), allocatableUnits.toPlainString(), orderUnit, remainingOrderQty.toPlainString()); } } // 检查是否完全分配 if (remainingOrderQty.compareTo(BigDecimal.ZERO) > 0) { throw new RuntimeException(buildStockShortageMessage(reqVOItem.getErpSku(), matchResult.getOrderUnitQuantity(), remainingOrderQty, orderUnit, materials, true, // 是大单位 material4UnitPrice)); } } /** * 分配基本单位库存 */ private void allocateBasicUnitStock(ErpXsddReqVO.OrderItem reqVOItem, DepotHeadXsddRequestVO order, List itemList, List materials, UnitMatchResult matchResult) throws Exception { // 获取基本参数 BigDecimal remainingQty = matchResult.getBaseQuantity(); // 剩余需求数量(基本单位) String basicUnit = matchResult.getMatchedUnit(); // 基本单位名称 String erpSku = reqVOItem.getErpSku(); BigDecimal unitPrice = reqVOItem.getUnitPrice(); // 记录原始需求用于错误提示 BigDecimal originalQty = remainingQty; // 遍历库存批次进行分配 for (MaterialVo4Unit m : materials) { if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) { break; // 需求已满足 } BigDecimal inventory = m.getInventory(); if (inventory.compareTo(BigDecimal.ZERO) <= 0) { continue; // 跳过无库存批次 } // 计算当前批次可分配数量 BigDecimal allocateQty = inventory.min(remainingQty); // 创建订单明细项 DepotItemXsddRequestVO item = createOrderItem(order, m, basicUnit, allocateQty, allocateQty, unitPrice); itemList.add(item); // 更新库存 m.setInventory(inventory.subtract(allocateQty)); // 更新剩余需求 remainingQty = remainingQty.subtract(allocateQty); logger.info("分配基本单位库存:批次[{}] 分配 {} {} (剩余需求: {})", m.getBatchNumber(), allocateQty.toPlainString(), basicUnit, remainingQty.toPlainString()); } // 检查是否完全分配 if (remainingQty.compareTo(BigDecimal.ZERO) > 0) { // 在allocateBasicUnitStock方法中调用 throw new RuntimeException(buildStockShortageMessage(erpSku, originalQty, remainingQty, basicUnit, materials, false, null)); } } /** * 构建库存不足提示信息(通用方法,支持基本单位和大单位) * * @param erpSku 商品SKU * @param totalQty 总需求数量 * @param remainingQty 剩余未分配数量 * @param orderUnit 订单单位 * @param materials 库存列表 * @param isLargeUnit 是否为大单位 * @param material4UnitPrice 单位价格信息(大单位时需要) * @return 完整的库存不足提示信息 */ private String buildStockShortageMessage(String erpSku, BigDecimal totalQty, BigDecimal remainingQty, String orderUnit, List materials, boolean isLargeUnit, Material4UnitPrice material4UnitPrice) { StringBuilder sb = new StringBuilder(); sb.append("商品[").append(erpSku).append("]库存不足!\n"); sb.append("需求: ").append(totalQty.toPlainString()).append(" ").append(orderUnit).append("\n"); sb.append("未分配: ").append(remainingQty.toPlainString()).append(" ").append(orderUnit).append("\n"); sb.append("当前库存:\n"); // 获取基本单位名称 String basicUnit = isLargeUnit ? material4UnitPrice.getBasicUnit() : orderUnit; materials.forEach(m -> { // 计算当前批次的显示库存量 String stockDisplay; if (isLargeUnit) { BigDecimal unitRatio = getUnitRatio(orderUnit, material4UnitPrice); BigDecimal stockInOrderUnit = m.getInventory().divideToIntegralValue(unitRatio); stockDisplay = String.format("%s %s (%s %s)", stockInOrderUnit.toPlainString(), orderUnit, m.getInventory().toPlainString(), basicUnit); } else { stockDisplay = m.getInventory().toPlainString() + " " + basicUnit; } sb.append(String.format("批次[%s] 生产日期[%s] - 库存: %s\n", m.getBatchNumber(), m.getProductionDate(), stockDisplay)); }); // 添加建议提示 if (isLargeUnit) { sb.append("\n提示:").append(orderUnit).append("与").append(basicUnit).append("的换算比例为: 1").append(orderUnit).append("=").append(getUnitRatio(orderUnit, material4UnitPrice).toPlainString()).append(basicUnit); } return sb.toString(); } /** * 订单项参数校验 */ private void validateOrderItem(ErpXsddReqVO.OrderItem item) throws Exception { if (item.getQuantity() == null || item.getQuantity() <= 0) { throw new RuntimeException("商品数量必须大于0"); } if (item.getUnitPrice() == null || item.getUnitPrice().compareTo(BigDecimal.ZERO) <= 0) { throw new RuntimeException("商品单价必须大于0"); } if (StringUtils.isBlank(item.getErpSku())) { throw new RuntimeException("商品SKU不能为空"); } } /** * 创建订单明细项 */ private DepotItemXsddRequestVO createOrderItem(DepotHeadXsddRequestVO order, MaterialVo4Unit material, String unit, BigDecimal operNumber, BigDecimal basicNumber, BigDecimal unitPrice) { DepotItemXsddRequestVO item = new DepotItemXsddRequestVO(); item.setHeaderId(order.getId()); item.setMaterialId(material.getId()); item.setMaterialExtendId(material.getMeId()); item.setMaterialUnit(unit); item.setSku(material.getSku()); item.setDepotId(material.getDepotId()); item.setOperNumber(operNumber); item.setBasicNumber(basicNumber); item.setUnitPrice(unitPrice); item.setTaxRate(BigDecimal.ONE); item.setTaxMoney(BigDecimal.ZERO); item.setBatchNumber(""); item.setTaxLastMoney(unitPrice.multiply(operNumber)); item.setAllPrice(unitPrice.multiply(operNumber)); return item; } private Material4UnitPrice getMaterial4UnitPrice(MaterialVo4Unit material) throws Exception { Material4UnitPrice material4UnitPrice = new Material4UnitPrice(); Unit unitInfo = new Unit(); // 查询多单位信息 unitInfo.setBasicUnit(material.getCommodityUnit()); if(material.getUnitId() != null){ unitInfo = materialService.findUnit(material.getId()); } BeanUtils.copyProperties(unitInfo, material4UnitPrice); // 设置基础单位价格、基础数量 material4UnitPrice.setBasicUnitPrice(material.getWholesaleDecimal()); material4UnitPrice.setBasicUnitNumber(BigDecimal.ONE); // 设置其他单位价格、其他数量 if (StringUtils.isNotEmpty(material4UnitPrice.getOtherUnit())) { material4UnitPrice.setOtherUnitPrice(material.getWholesaleDecimal().multiply(material4UnitPrice.getRatio())); material4UnitPrice.setOtherUnitNumber(material4UnitPrice.getRatio()); } // 设置其他单位2价格、其他数量2 if (StringUtils.isNotEmpty(material4UnitPrice.getOtherUnitTwo())) { material4UnitPrice.setOtherUnitTwoPrice(material.getWholesaleDecimal().multiply(material4UnitPrice.getRatioTwo())); material4UnitPrice.setOtherUnitTwoNumber(material4UnitPrice.getRatioTwo()); } // 设置其他单位3价格、其他数量3 if (StringUtils.isNotEmpty(material4UnitPrice.getOtherUnitThree())) { material4UnitPrice.setOtherUnitThreePrice(material.getWholesaleDecimal().multiply(material4UnitPrice.getRatioThree())); material4UnitPrice.setOtherUnitThreeNumber(material4UnitPrice.getRatioThree()); } return material4UnitPrice; } /** * 根据单价匹配单位并计算基本单位数量
* 如果订单商品单价和商品最小单位单价相等,则使用最小单位, remainingQuantity = quantity
* 如果订单商品单价和商品其他单位单价相等,则使用其他单位, remainingQuantity = quantity * material4UnitPrice.getRatio()
* 如果订单商品单价和商品其他单位2单价相等,则使用其他单位2, remainingQuantity = quantity * material4UnitPrice.getRatioTwo()
* 如果订单商品单价和商品其他单位3单价相等,则使用其他单位3, remainingQuantity = quantity * material4UnitPrice.getRatioThree()
*/ private UnitMatchResult matchUnitAndCalculateBaseQuantity(BigDecimal orderUnitPrice, BigDecimal orderQuantity, Material4UnitPrice material4UnitPrice) { // 允许的价格误差范围 final BigDecimal PRICE_TOLERANCE = new BigDecimal("0.001"); // 1. 检查是否匹配基本单位价格 if (material4UnitPrice.getBasicUnitPrice() != null && orderUnitPrice.subtract(material4UnitPrice.getBasicUnitPrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) { return new UnitMatchResult(material4UnitPrice.getBasicUnit(), orderQuantity, orderQuantity, false); } // 2. 检查是否匹配副单位价格 if (material4UnitPrice.getOtherUnitPrice() != null && orderUnitPrice.subtract(material4UnitPrice.getOtherUnitPrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) { BigDecimal baseQuantity = orderQuantity.multiply(material4UnitPrice.getRatio()); return new UnitMatchResult(material4UnitPrice.getOtherUnit(), orderQuantity, baseQuantity, true); } // 3. 检查是否匹配副单位2价格 if (material4UnitPrice.getOtherUnitTwoPrice() != null && orderUnitPrice.subtract(material4UnitPrice.getOtherUnitTwoPrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) { BigDecimal baseQuantity = orderQuantity.multiply(material4UnitPrice.getRatioTwo()); return new UnitMatchResult(material4UnitPrice.getOtherUnitTwo(), orderQuantity, baseQuantity, true); } // 4. 检查是否匹配副单位3价格 if (material4UnitPrice.getOtherUnitThreePrice() != null && orderUnitPrice.subtract(material4UnitPrice.getOtherUnitThreePrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) { BigDecimal baseQuantity = orderQuantity.multiply(material4UnitPrice.getRatioThree()); return new UnitMatchResult(material4UnitPrice.getOtherUnitThree(), orderQuantity, baseQuantity, true); } // 5. 默认返回基本单位(如果无法匹配) return new UnitMatchResult(material4UnitPrice.getBasicUnit(), orderQuantity, orderQuantity, false); } /** * 将基本单位数量转换为订单单位数量 */ private BigDecimal convertToOrderUnitQuantity(BigDecimal baseQuantity, String orderUnit, Material4UnitPrice material4UnitPrice) { if (orderUnit.equals(material4UnitPrice.getBasicUnit())) { return baseQuantity; } else if (orderUnit.equals(material4UnitPrice.getOtherUnit())) { return baseQuantity.divide(material4UnitPrice.getRatio()); } else if (orderUnit.equals(material4UnitPrice.getOtherUnitTwo())) { return baseQuantity.divide(material4UnitPrice.getRatioTwo()); } else if (orderUnit.equals(material4UnitPrice.getOtherUnitThree())) { return baseQuantity.divide(material4UnitPrice.getRatioThree()); } return baseQuantity; } /** * 获取单位的换算比例 */ private BigDecimal getUnitRatio(String unit, Material4UnitPrice material4UnitPrice) { if (unit.equals(material4UnitPrice.getBasicUnit())) { return BigDecimal.ONE; } else if (unit.equals(material4UnitPrice.getOtherUnit())) { return material4UnitPrice.getRatio(); } else if (unit.equals(material4UnitPrice.getOtherUnitTwo())) { return material4UnitPrice.getRatioTwo(); } else if (unit.equals(material4UnitPrice.getOtherUnitThree())) { return material4UnitPrice.getRatioThree(); } return BigDecimal.ONE; } /** * 单位匹配结果类 */ private static class UnitMatchResult { private String matchedUnit; // 匹配到的单位 private BigDecimal orderUnitQuantity; // 订单单位数量 private BigDecimal baseQuantity; // 基本单位数量 private boolean isLargeUnit; // 是否是大单位 public UnitMatchResult(String matchedUnit, BigDecimal orderUnitQuantity, BigDecimal baseQuantity, boolean isLargeUnit) { this.matchedUnit = matchedUnit; this.orderUnitQuantity = orderUnitQuantity; this.baseQuantity = baseQuantity; this.isLargeUnit = isLargeUnit; } // getter方法 public String getMatchedUnit() { return matchedUnit; } public BigDecimal getBaseQuantity() { return baseQuantity; } public boolean isLargeUnit() { return isLargeUnit; } public BigDecimal getOrderUnitQuantity() { return orderUnitQuantity; } } }