SyncTescoSystemService.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. package com.jsh.erp.service;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  6. import com.fasterxml.jackson.databind.ObjectMapper;
  7. import com.jsh.erp.constants.ExceptionConstants;
  8. import com.jsh.erp.datasource.entities.MaterialVo4Unit;
  9. import com.jsh.erp.datasource.entities.Supplier;
  10. import com.jsh.erp.datasource.entities.Unit;
  11. import com.jsh.erp.datasource.mappers.MaterialCurrentStockMapperEx;
  12. import com.jsh.erp.datasource.tesco.request.ErpXsddReqVO;
  13. import com.jsh.erp.datasource.vo.DepotHeadXsddRequestVO;
  14. import com.jsh.erp.datasource.vo.DepotItemXsddRequestVO;
  15. import com.jsh.erp.datasource.vo.Material4UnitPrice;
  16. import com.jsh.erp.datasource.vo.MaterialCurrentStock4SystemSku;
  17. import com.jsh.erp.exception.BusinessRunTimeException;
  18. import com.jsh.erp.utils.HttpClient;
  19. import org.slf4j.Logger;
  20. import org.slf4j.LoggerFactory;
  21. import org.springframework.beans.BeanUtils;
  22. import org.springframework.beans.factory.annotation.Value;
  23. import org.springframework.stereotype.Service;
  24. import org.springframework.transaction.annotation.Transactional;
  25. import org.springframework.util.CollectionUtils;
  26. import javax.annotation.Resource;
  27. import java.math.BigDecimal;
  28. import java.math.RoundingMode;
  29. import java.util.ArrayList;
  30. import java.util.Arrays;
  31. import java.util.Date;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.stream.Collectors;
  35. /**
  36. * @Author MS.BLUE
  37. * @Date 2025-04-12
  38. */
  39. @Service
  40. public class SyncTescoSystemService {
  41. private Logger logger = LoggerFactory.getLogger(SyncTescoSystemService.class);
  42. @Resource
  43. private MaterialService materialService;
  44. @Resource
  45. private DepotHeadService depotHeadService;
  46. @Resource
  47. private SequenceService sequenceService;
  48. @Resource
  49. private SupplierService supplierService;
  50. @Resource
  51. private MaterialCurrentStockMapperEx materialCurrentStockMapperEx;
  52. @Value("${tesco.openSycn}")
  53. private boolean openSycn;
  54. @Value("${tesco.sycnErpMaterialStockUrl}")
  55. private String sycnErpMaterialStockUrl;
  56. @Value("${tesco.sycnErpMaterialPriceUrl}")
  57. private String sycnErpMaterialPriceUrl;
  58. public String syncSystemSku(String param) {
  59. logger.info("获取商品系统sku,返回信息集采系统 param:{}", param);
  60. JSONObject result = new JSONObject();
  61. JSONArray dataArray = new JSONArray();
  62. try {
  63. // 参数校验
  64. if (param == null || param.isEmpty()) {
  65. result.put("code", 1);
  66. result.put("msg", "参数不能为空");
  67. return result.toJSONString();
  68. }
  69. // 解析传入的JSON参数
  70. JSONArray barCodeArray = JSON.parseArray(param);
  71. if (barCodeArray == null || barCodeArray.isEmpty()) {
  72. result.put("code", 1);
  73. result.put("msg", "参数格式错误");
  74. return result.toJSONString();
  75. }
  76. // 提取 barCode 到字符串数组
  77. List<String> barCodeList = new ArrayList<>();
  78. for (int i = 0; i < barCodeArray.size(); i++) {
  79. JSONObject barCodeObj = barCodeArray.getJSONObject(i);
  80. String barCode = barCodeObj.getString("barCode");
  81. barCodeList.add(barCode);
  82. }
  83. // 查询商品信息
  84. List<MaterialVo4Unit> materialList = materialService.getMaterialByBarCode(barCodeList);
  85. if (materialList != null && !materialList.isEmpty()) {
  86. result.put("code", 0);
  87. for (MaterialVo4Unit material : materialList) {
  88. JSONObject item = new JSONObject();
  89. item.put("barCode", material.getmBarCode());
  90. item.put("systemSku", material.getSystemSku());
  91. dataArray.add(item);
  92. }
  93. } else {
  94. result.put("code", 1);
  95. result.put("msg", "未找到对应商品信息");
  96. }
  97. } catch (Exception e) {
  98. e.printStackTrace();
  99. result.put("code", 1);
  100. result.put("msg", "系统异常,请稍后重试");
  101. }
  102. // 将数据数组放入返回结果
  103. result.put("data", dataArray);
  104. logger.info(" result:{}", result);
  105. return result.toJSONString();
  106. }
  107. /**
  108. *
  109. 集采下单成功->封装同步erp销售订单请求体
  110. {
  111. orderSn:"订单编号",
  112. customer:{//客户信息
  113. name:"客户名称",
  114. contact:"客户联系方式",
  115. address:"客户地址"
  116. },
  117. item:[{//订单商品信息
  118. erp_sku:"erp_sku编号",
  119. unitPrice:"商品单价",
  120. quantity:"商品下单数量",
  121. price:"商品实付价格"
  122. }]
  123. }
  124. * @param param
  125. * @return
  126. */
  127. public String syncOrder(String param) {
  128. logger.info("同步集采订单-》销售订单 param:{}", param);
  129. JSONObject result = new JSONObject();
  130. try {
  131. // 解析传入的JSON参数为 ErpXsddReqVO
  132. ErpXsddReqVO erpXsddReqVO = JSON.parseObject(param, ErpXsddReqVO.class);
  133. if (erpXsddReqVO == null) {
  134. result.put("code", 1);
  135. result.put("msg", "参数不能为空");
  136. return result.toJSONString();
  137. }
  138. // 校验订单编号
  139. if (erpXsddReqVO.getOrderSn() == null || erpXsddReqVO.getOrderSn().isEmpty()) {
  140. result.put("code", 1);
  141. result.put("msg", "订单编号不能为空");
  142. return result.toJSONString();
  143. }
  144. // 校验客户信息
  145. if (erpXsddReqVO.getCustomer() == null) {
  146. result.put("code", 1);
  147. result.put("msg", "客户信息不能为空");
  148. return result.toJSONString();
  149. }
  150. // 校验订单商品信息
  151. if (erpXsddReqVO.getItems() == null || erpXsddReqVO.getItems().isEmpty()) {
  152. result.put("code", 1);
  153. result.put("msg", "订单商品信息不能为空");
  154. return result.toJSONString();
  155. }
  156. // 构建订单和商品信息
  157. buildOrderAndItem(erpXsddReqVO);
  158. // 返回成功结果
  159. result.put("code", 0);
  160. result.put("msg", "订单同步成功");
  161. } catch (Exception e) {
  162. logger.error("同步订单发生异常", e);
  163. result.put("code", 1);
  164. result.put("msg", "系统异常,请稍后重试");
  165. }
  166. return result.toJSONString();
  167. }
  168. public String sycnTescoStock(Long mId){
  169. return sycnTescoStock(Arrays.asList(mId));
  170. }
  171. public String sycnTescoStock(List<Long> mIdList) {
  172. if (!openSycn) {
  173. return "tesco系统同步未开启";
  174. }
  175. List<MaterialCurrentStock4SystemSku> stockList = materialCurrentStockMapperEx.getCurrentStockMapByMdIdList(mIdList);
  176. if (!CollectionUtils.isEmpty(stockList)) {
  177. try {
  178. for (MaterialCurrentStock4SystemSku stock : stockList) {
  179. Unit unitInfo = new Unit(); // 查询多单位信息
  180. stock.setBasicUnit(stock.getUnit());
  181. stock.setBasicUnitTotalStock(stock.getBasicUnitTotalStock().setScale(0, RoundingMode.DOWN));
  182. if (stock.getUnitId() != null) {
  183. unitInfo = materialService.findUnit(stock.getMId());
  184. stock.setBasicUnit(unitInfo.getBasicUnit());
  185. stock.setLargeUnit(true);
  186. processOtherUnitStock(stock, unitInfo);
  187. }
  188. }
  189. String jsonParam = new ObjectMapper().writeValueAsString(stockList);
  190. logger.info("同步集采库存请求参数:{}", jsonParam);
  191. String response = HttpClient.httpPost(sycnErpMaterialStockUrl, jsonParam);
  192. logger.info("同步集采库存响应:{}", response);
  193. return jsonParam;
  194. } catch (Exception e) {
  195. throw new RuntimeException(e);
  196. }
  197. }
  198. return null;
  199. }
  200. private void processOtherUnitStock(MaterialCurrentStock4SystemSku stock, Unit unitInfo) {
  201. if (StringUtils.isNotEmpty(unitInfo.getOtherUnit())) {
  202. BigDecimal otherUnitTotalStock = stock.getBasicUnitTotalStock().divide(unitInfo.getRatio(), 0, RoundingMode.DOWN);
  203. String otherUnitStr = "1" + unitInfo.getOtherUnit() + "=" + unitInfo.getRatio().setScale(0, RoundingMode.DOWN) + unitInfo.getBasicUnit();
  204. stock.setOtherUnit(otherUnitStr);
  205. stock.setRatio(unitInfo.getRatio().setScale(0, RoundingMode.DOWN));
  206. stock.setOtherUnitTotalStock(otherUnitTotalStock);
  207. if (StringUtils.isNotEmpty(unitInfo.getOtherUnitTwo())) {
  208. BigDecimal otherUnitTwoTotalStock = stock.getBasicUnitTotalStock().divide(unitInfo.getRatioTwo(), 0, RoundingMode.DOWN);
  209. String otherUnitTwoStr = "1" + unitInfo.getOtherUnitTwo() + "=" + unitInfo.getRatioTwo().setScale(0, RoundingMode.DOWN) + unitInfo.getBasicUnit();
  210. stock.setOtherUnitTwo(otherUnitTwoStr);
  211. stock.setRatioTwo(unitInfo.getRatioTwo().setScale(0, RoundingMode.DOWN));
  212. stock.setOtherUnitTwoTotalStock(otherUnitTwoTotalStock);
  213. if (StringUtils.isNotEmpty(unitInfo.getOtherUnitThree())) {
  214. BigDecimal otherUnitThreeTotalStock = stock.getBasicUnitTotalStock().divide(unitInfo.getRatioThree(), 0, RoundingMode.DOWN);
  215. String otherUnitThreeStr = "1" + unitInfo.getOtherUnitThree() + "=" + unitInfo.getRatioThree().setScale(0, RoundingMode.DOWN) + unitInfo.getBasicUnit();
  216. stock.setOtherUnitTwo(otherUnitThreeStr);
  217. stock.setRatioThree(unitInfo.getRatioThree().setScale(0, RoundingMode.DOWN));
  218. stock.setOtherUnitThreeTotalStock(otherUnitThreeTotalStock);
  219. }
  220. }
  221. }
  222. }
  223. public String sycnTescoSalePrice(List<Long> mIdList) {
  224. if (!openSycn) {
  225. return "tesco系统同步未开启";
  226. }
  227. try {
  228. List<MaterialCurrentStock4SystemSku> mList = materialService.getMaterialCurrentPriceByIdList(mIdList);
  229. if (!CollectionUtils.isEmpty(mList)) {
  230. for (MaterialCurrentStock4SystemSku material : mList){
  231. Unit unitInfo = new Unit(); // 查询多单位信息
  232. material.setBasicUnit(material.getUnit());
  233. if (material.getUnitId() != null) {
  234. unitInfo = materialService.findUnit(material.getMId());
  235. material.setBasicUnit(unitInfo.getBasicUnit());
  236. material.setLargeUnit(true);
  237. processOtherUnitSalePrice(material, unitInfo);
  238. }
  239. }
  240. String jsonParam = new ObjectMapper().writeValueAsString(mList);
  241. logger.info("同步集采销售价格请求参数:{}", jsonParam);
  242. String response = HttpClient.httpPost(sycnErpMaterialPriceUrl, jsonParam);
  243. logger.info("同步集采销售价格响应:{}", response);
  244. return jsonParam;
  245. }
  246. } catch (Exception e) {
  247. throw new RuntimeException(e);
  248. }
  249. return null;
  250. }
  251. private void processOtherUnitSalePrice(MaterialCurrentStock4SystemSku stock, Unit unitInfo) {
  252. if (StringUtils.isNotEmpty(unitInfo.getOtherUnit())) {
  253. BigDecimal otherUnitSalePrice = stock.getBasicUnitSalePrice().multiply(unitInfo.getRatio());
  254. String otherUnitStr = "1" + unitInfo.getOtherUnit() + "=" + unitInfo.getRatio().setScale(0, RoundingMode.DOWN) + unitInfo.getBasicUnit();
  255. stock.setOtherUnit(otherUnitStr);
  256. stock.setRatio(unitInfo.getRatio().setScale(0, RoundingMode.DOWN));
  257. stock.setOtherUnitSalePrice(otherUnitSalePrice);
  258. if (StringUtils.isNotEmpty(unitInfo.getOtherUnitTwo())) {
  259. BigDecimal otherUnitTwoSalePrice = stock.getBasicUnitSalePrice().multiply(unitInfo.getRatioTwo());
  260. String otherUnitTwoStr = "1" + unitInfo.getOtherUnitTwo() + "=" + unitInfo.getRatioTwo().setScale(0, RoundingMode.DOWN) + unitInfo.getBasicUnit();
  261. stock.setOtherUnitTwo(otherUnitTwoStr);
  262. stock.setRatioTwo(unitInfo.getRatioTwo().setScale(0, RoundingMode.DOWN));
  263. stock.setOtherUnitTwoSalePrice(otherUnitTwoSalePrice);
  264. if (StringUtils.isNotEmpty(unitInfo.getOtherUnitThree())) {
  265. BigDecimal otherUnitThreeSalePrice = stock.getBasicUnitSalePrice().multiply(unitInfo.getRatioThree());
  266. String otherUnitThreeStr = "1" + unitInfo.getOtherUnitThree() + "=" + unitInfo.getRatioThree().setScale(0, RoundingMode.DOWN) + unitInfo.getBasicUnit();
  267. stock.setOtherUnitTwo(otherUnitThreeStr);
  268. stock.setRatioThree(unitInfo.getRatioThree().setScale(0, RoundingMode.DOWN));
  269. stock.setOtherUnitThreeSalePrice(otherUnitThreeSalePrice);
  270. }
  271. }
  272. }
  273. }
  274. @Transactional(value = "transactionManager", rollbackFor = Exception.class)
  275. private void buildOrderAndItem(ErpXsddReqVO erpXsddReqVO) throws Exception {
  276. // 1. 初始化订单基本信息
  277. DepotHeadXsddRequestVO order = initSalesOrder(erpXsddReqVO);
  278. // 2. 处理订单商品项
  279. List<DepotItemXsddRequestVO> itemList = processOrderItems(erpXsddReqVO, order);
  280. // 3. 提交订单
  281. depotHeadService.syncOrderToXsdd(order, itemList);
  282. }
  283. private DepotHeadXsddRequestVO initSalesOrder(ErpXsddReqVO erpXsddReqVO) throws Exception {
  284. DepotHeadXsddRequestVO order = new DepotHeadXsddRequestVO();
  285. order.setType("其它");
  286. order.setSubType("销售订单");
  287. order.setLinkTesco(erpXsddReqVO.getOrderSn());
  288. ErpXsddReqVO.Customer customer = erpXsddReqVO.getCustomer();
  289. // 根据手机号查询客户
  290. Supplier supplier = supplierService.getCustomerByPhone(customer.getAccount());
  291. if (supplier == null) {
  292. // 如果客户不存在,创建新客户
  293. supplier = new Supplier();
  294. supplier.setEnabled(true);
  295. supplier.setSupplier(customer.getName());
  296. supplier.setTelephone(customer.getAccount());
  297. supplier.setContacts(customer.getReceiverName());
  298. supplier.setPhoneNum(customer.getReceiverPhone());
  299. supplier.setAddress(customer.getReceiverAddress());
  300. supplier = supplierService.createCustomer(supplier);
  301. }
  302. // 绑定客户 ID
  303. order.setOrganId(supplier.getId());
  304. order.setReceiverName(customer.getReceiverName());
  305. order.setReceiverPhone(customer.getReceiverPhone());
  306. order.setReceiverAddress(customer.getReceiverAddress());
  307. order.setRemark("集采同步订单");
  308. String number = sequenceService.buildOnlyNumber();
  309. String defaultNumber = "XSDD" + number;
  310. order.setDefaultNumber(defaultNumber);
  311. order.setNumber(defaultNumber);
  312. order.setCreateTime(new Date());
  313. order.setOperTime(new Date());
  314. order.setCreator(1L); // 创建人ID
  315. order.setAccountId(1L); // 账户ID
  316. order.setChangeAmount(BigDecimal.ZERO);
  317. order.setBackAmount(BigDecimal.ZERO);
  318. order.setTotalPrice(erpXsddReqVO.getTotalPrice());
  319. order.setPayType("现付");
  320. order.setDiscount(BigDecimal.ZERO);
  321. order.setDiscountMoney(BigDecimal.ZERO);
  322. order.setDiscountLastMoney(erpXsddReqVO.getTotalPrice());
  323. order.setStatus("0");
  324. order.setPurchaseStatus("0");
  325. order.setSource("2"); // 来源集采商城
  326. order.setTenantId(null);
  327. order.setDeleteFlag("0");
  328. return order;
  329. }
  330. /**
  331. * 处理订单商品项
  332. */
  333. private List<DepotItemXsddRequestVO> processOrderItems(ErpXsddReqVO erpXsddReqVO, DepotHeadXsddRequestVO order) throws Exception {
  334. List<DepotItemXsddRequestVO> itemList = new ArrayList<>();
  335. List<ErpXsddReqVO.OrderItem> reqVOItems = erpXsddReqVO.getItems();
  336. // 1. 获取所有商品信息
  337. List<String> systemSkuList = reqVOItems.stream().map(ErpXsddReqVO.OrderItem::getErpSku).collect(Collectors.toList());
  338. List<MaterialVo4Unit> materialList = materialService.getMaterialBySystemSku(systemSkuList);
  339. Map<String, List<MaterialVo4Unit>> skuToMaterialMap = materialList.stream().collect(Collectors.groupingBy(MaterialVo4Unit::getSystemSku));
  340. // 2. 处理每个订单项
  341. for (ErpXsddReqVO.OrderItem reqVOItem : reqVOItems) {
  342. try {
  343. processSingleOrderItem(reqVOItem, order, itemList, skuToMaterialMap);
  344. } catch (Exception e) {
  345. logger.error("处理订单项失败 erp_sku: {}: {}", reqVOItem.getErpSku(), e.getMessage());
  346. throw e;
  347. }
  348. }
  349. return itemList;
  350. }
  351. /**
  352. * 处理单个订单项
  353. */
  354. private void processSingleOrderItem(ErpXsddReqVO.OrderItem reqVOItem, DepotHeadXsddRequestVO order, List<DepotItemXsddRequestVO> itemList, Map<String, List<MaterialVo4Unit>> skuToMaterialMap) throws Exception {
  355. // 1. 参数校验
  356. validateOrderItem(reqVOItem);
  357. // 2. 获取商品信息
  358. List<MaterialVo4Unit> materials = skuToMaterialMap.get(reqVOItem.getErpSku());
  359. if (CollectionUtils.isEmpty(materials)) {
  360. throw new BusinessRunTimeException(ExceptionConstants.MATERIAL_ERP_SKU_NOT_DECIMAL_CODE, String.format(ExceptionConstants.MATERIAL_ERP_SKU_NOT_DECIMAL_MSG, reqVOItem.getErpSku()));
  361. }
  362. // 3. 获取单位信息
  363. MaterialVo4Unit primaryMaterial = materials.get(0);
  364. Material4UnitPrice material4UnitPrice = getMaterial4UnitPrice(primaryMaterial);
  365. // 4. 匹配销售单位
  366. UnitMatchResult matchResult = matchUnitAndCalculateBaseQuantity(reqVOItem.getUnitPrice(), BigDecimal.valueOf(reqVOItem.getQuantity()), material4UnitPrice);
  367. // 5. 根据单位类型分配库存
  368. if (matchResult.isLargeUnit()) {
  369. allocateLargeUnitStock(reqVOItem, order, itemList, materials, matchResult, material4UnitPrice);
  370. } else {
  371. allocateBasicUnitStock(reqVOItem, order, itemList, materials, matchResult);
  372. }
  373. }
  374. /**
  375. * 分配大单位库存(不拆分配)
  376. */
  377. private void allocateLargeUnitStock(ErpXsddReqVO.OrderItem reqVOItem, DepotHeadXsddRequestVO order, List<DepotItemXsddRequestVO> itemList, List<MaterialVo4Unit> materials,
  378. UnitMatchResult matchResult, Material4UnitPrice material4UnitPrice) throws Exception {
  379. String orderUnit = matchResult.getMatchedUnit();
  380. BigDecimal unitRatio = getUnitRatio(orderUnit, material4UnitPrice);
  381. BigDecimal remainingOrderQty = matchResult.getOrderUnitQuantity();
  382. // 分配库存
  383. for (MaterialVo4Unit m : materials) {
  384. if (remainingOrderQty.compareTo(BigDecimal.ZERO) <= 0)
  385. break;
  386. BigDecimal inventory = m.getInventory();
  387. if (inventory.compareTo(unitRatio) < 0)
  388. continue;
  389. // 计算可分配数量(向下取整)
  390. BigDecimal allocatableUnits = inventory.divideToIntegralValue(unitRatio).min(remainingOrderQty);
  391. if (allocatableUnits.compareTo(BigDecimal.ZERO) > 0) {
  392. BigDecimal allocBaseQty = allocatableUnits.multiply(unitRatio);
  393. // 创建订单项
  394. DepotItemXsddRequestVO item = createOrderItem(order, m, orderUnit, allocatableUnits, allocBaseQty, reqVOItem.getUnitPrice());
  395. itemList.add(item);
  396. // 更新库存
  397. m.setInventory(inventory.subtract(allocBaseQty));
  398. // 更新剩余需求
  399. remainingOrderQty = remainingOrderQty.subtract(allocatableUnits);
  400. logger.info("分配库存:批次[{}] 分配 {} {} (剩余需求: {})", m.getBatchNumber(), allocatableUnits.toPlainString(), orderUnit, remainingOrderQty.toPlainString());
  401. }
  402. }
  403. // 检查是否完全分配
  404. if (remainingOrderQty.compareTo(BigDecimal.ZERO) > 0) {
  405. throw new RuntimeException(buildStockShortageMessage(reqVOItem.getErpSku(), matchResult.getOrderUnitQuantity(), remainingOrderQty, orderUnit, materials, true, // 是大单位
  406. material4UnitPrice));
  407. }
  408. }
  409. /**
  410. * 分配基本单位库存
  411. */
  412. private void allocateBasicUnitStock(ErpXsddReqVO.OrderItem reqVOItem, DepotHeadXsddRequestVO order, List<DepotItemXsddRequestVO> itemList, List<MaterialVo4Unit> materials, UnitMatchResult matchResult) throws Exception {
  413. // 获取基本参数
  414. BigDecimal remainingQty = matchResult.getBaseQuantity(); // 剩余需求数量(基本单位)
  415. String basicUnit = matchResult.getMatchedUnit(); // 基本单位名称
  416. String erpSku = reqVOItem.getErpSku();
  417. BigDecimal unitPrice = reqVOItem.getUnitPrice();
  418. // 记录原始需求用于错误提示
  419. BigDecimal originalQty = remainingQty;
  420. // 遍历库存批次进行分配
  421. for (MaterialVo4Unit m : materials) {
  422. if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) {
  423. break; // 需求已满足
  424. }
  425. BigDecimal inventory = m.getInventory();
  426. if (inventory.compareTo(BigDecimal.ZERO) <= 0) {
  427. continue; // 跳过无库存批次
  428. }
  429. // 计算当前批次可分配数量
  430. BigDecimal allocateQty = inventory.min(remainingQty);
  431. // 创建订单明细项
  432. DepotItemXsddRequestVO item = createOrderItem(order, m, basicUnit, allocateQty, allocateQty, unitPrice);
  433. itemList.add(item);
  434. // 更新库存
  435. m.setInventory(inventory.subtract(allocateQty));
  436. // 更新剩余需求
  437. remainingQty = remainingQty.subtract(allocateQty);
  438. logger.info("分配基本单位库存:批次[{}] 分配 {} {} (剩余需求: {})", m.getBatchNumber(), allocateQty.toPlainString(), basicUnit, remainingQty.toPlainString());
  439. }
  440. // 检查是否完全分配
  441. if (remainingQty.compareTo(BigDecimal.ZERO) > 0) {
  442. // 在allocateBasicUnitStock方法中调用
  443. throw new RuntimeException(buildStockShortageMessage(erpSku, originalQty, remainingQty, basicUnit, materials, false, null));
  444. }
  445. }
  446. /**
  447. * 构建库存不足提示信息(通用方法,支持基本单位和大单位)
  448. *
  449. * @param erpSku 商品SKU
  450. * @param totalQty 总需求数量
  451. * @param remainingQty 剩余未分配数量
  452. * @param orderUnit 订单单位
  453. * @param materials 库存列表
  454. * @param isLargeUnit 是否为大单位
  455. * @param material4UnitPrice 单位价格信息(大单位时需要)
  456. * @return 完整的库存不足提示信息
  457. */
  458. private String buildStockShortageMessage(String erpSku, BigDecimal totalQty, BigDecimal remainingQty, String orderUnit, List<MaterialVo4Unit> materials, boolean isLargeUnit, Material4UnitPrice material4UnitPrice) {
  459. StringBuilder sb = new StringBuilder();
  460. sb.append("商品[").append(erpSku).append("]库存不足!\n");
  461. sb.append("需求: ").append(totalQty.toPlainString()).append(" ").append(orderUnit).append("\n");
  462. sb.append("未分配: ").append(remainingQty.toPlainString()).append(" ").append(orderUnit).append("\n");
  463. sb.append("当前库存:\n");
  464. // 获取基本单位名称
  465. String basicUnit = isLargeUnit ? material4UnitPrice.getBasicUnit() : orderUnit;
  466. materials.forEach(m -> {
  467. // 计算当前批次的显示库存量
  468. String stockDisplay;
  469. if (isLargeUnit) {
  470. BigDecimal unitRatio = getUnitRatio(orderUnit, material4UnitPrice);
  471. BigDecimal stockInOrderUnit = m.getInventory().divideToIntegralValue(unitRatio);
  472. stockDisplay = String.format("%s %s (%s %s)", stockInOrderUnit.toPlainString(), orderUnit, m.getInventory().toPlainString(), basicUnit);
  473. } else {
  474. stockDisplay = m.getInventory().toPlainString() + " " + basicUnit;
  475. }
  476. sb.append(String.format("批次[%s] 生产日期[%s] - 库存: %s\n", m.getBatchNumber(), m.getProductionDate(), stockDisplay));
  477. });
  478. // 添加建议提示
  479. if (isLargeUnit) {
  480. sb.append("\n提示:").append(orderUnit).append("与").append(basicUnit).append("的换算比例为: 1").append(orderUnit).append("=").append(getUnitRatio(orderUnit, material4UnitPrice).toPlainString()).append(basicUnit);
  481. }
  482. return sb.toString();
  483. }
  484. /**
  485. * 订单项参数校验
  486. */
  487. private void validateOrderItem(ErpXsddReqVO.OrderItem item) throws Exception {
  488. if (item.getQuantity() == null || item.getQuantity() <= 0) {
  489. throw new RuntimeException("商品数量必须大于0");
  490. }
  491. if (item.getUnitPrice() == null || item.getUnitPrice().compareTo(BigDecimal.ZERO) <= 0) {
  492. throw new RuntimeException("商品单价必须大于0");
  493. }
  494. if (StringUtils.isBlank(item.getErpSku())) {
  495. throw new RuntimeException("商品SKU不能为空");
  496. }
  497. }
  498. /**
  499. * 创建订单明细项
  500. */
  501. private DepotItemXsddRequestVO createOrderItem(DepotHeadXsddRequestVO order, MaterialVo4Unit material, String unit, BigDecimal operNumber, BigDecimal basicNumber, BigDecimal unitPrice) {
  502. DepotItemXsddRequestVO item = new DepotItemXsddRequestVO();
  503. item.setHeaderId(order.getId());
  504. item.setMaterialId(material.getId());
  505. item.setMaterialExtendId(material.getMeId());
  506. item.setMaterialUnit(unit);
  507. item.setSku(material.getSku());
  508. item.setDepotId(material.getDepotId());
  509. item.setOperNumber(operNumber);
  510. item.setBasicNumber(basicNumber);
  511. item.setUnitPrice(unitPrice);
  512. item.setTaxRate(BigDecimal.ONE);
  513. item.setTaxMoney(BigDecimal.ZERO);
  514. item.setBatchNumber(material.getBatchNumber());
  515. item.setTaxLastMoney(unitPrice.multiply(operNumber));
  516. item.setAllPrice(unitPrice.multiply(operNumber));
  517. return item;
  518. }
  519. private Material4UnitPrice getMaterial4UnitPrice(MaterialVo4Unit material) throws Exception {
  520. Material4UnitPrice material4UnitPrice = new Material4UnitPrice();
  521. Unit unitInfo = new Unit(); // 查询多单位信息
  522. unitInfo.setBasicUnit(material.getCommodityUnit());
  523. if(material.getUnitId() != null){
  524. unitInfo = materialService.findUnit(material.getId());
  525. }
  526. BeanUtils.copyProperties(unitInfo, material4UnitPrice);
  527. // 设置基础单位价格、基础数量
  528. material4UnitPrice.setBasicUnitPrice(material.getWholesaleDecimal());
  529. material4UnitPrice.setBasicUnitNumber(BigDecimal.ONE);
  530. // 设置其他单位价格、其他数量
  531. if (StringUtils.isNotEmpty(material4UnitPrice.getOtherUnit())) {
  532. material4UnitPrice.setOtherUnitPrice(material.getWholesaleDecimal().multiply(material4UnitPrice.getRatio()));
  533. material4UnitPrice.setOtherUnitNumber(material4UnitPrice.getRatio());
  534. }
  535. // 设置其他单位2价格、其他数量2
  536. if (StringUtils.isNotEmpty(material4UnitPrice.getOtherUnitTwo())) {
  537. material4UnitPrice.setOtherUnitTwoPrice(material.getWholesaleDecimal().multiply(material4UnitPrice.getRatioTwo()));
  538. material4UnitPrice.setOtherUnitTwoNumber(material4UnitPrice.getRatioTwo());
  539. }
  540. // 设置其他单位3价格、其他数量3
  541. if (StringUtils.isNotEmpty(material4UnitPrice.getOtherUnitThree())) {
  542. material4UnitPrice.setOtherUnitThreePrice(material.getWholesaleDecimal().multiply(material4UnitPrice.getRatioThree()));
  543. material4UnitPrice.setOtherUnitThreeNumber(material4UnitPrice.getRatioThree());
  544. }
  545. return material4UnitPrice;
  546. }
  547. /**
  548. * 根据单价匹配单位并计算基本单位数量<br>
  549. * 如果订单商品单价和商品最小单位单价相等,则使用最小单位, remainingQuantity = quantity<br>
  550. * 如果订单商品单价和商品其他单位单价相等,则使用其他单位, remainingQuantity = quantity * material4UnitPrice.getRatio()<br>
  551. * 如果订单商品单价和商品其他单位2单价相等,则使用其他单位2, remainingQuantity = quantity * material4UnitPrice.getRatioTwo()<br>
  552. * 如果订单商品单价和商品其他单位3单价相等,则使用其他单位3, remainingQuantity = quantity * material4UnitPrice.getRatioThree()<br>
  553. */
  554. private UnitMatchResult matchUnitAndCalculateBaseQuantity(BigDecimal orderUnitPrice, BigDecimal orderQuantity, Material4UnitPrice material4UnitPrice) {
  555. // 允许的价格误差范围
  556. final BigDecimal PRICE_TOLERANCE = new BigDecimal("0.001");
  557. // 1. 检查是否匹配基本单位价格
  558. if (material4UnitPrice.getBasicUnitPrice() != null && orderUnitPrice.subtract(material4UnitPrice.getBasicUnitPrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) {
  559. return new UnitMatchResult(material4UnitPrice.getBasicUnit(), orderQuantity, orderQuantity, false);
  560. }
  561. // 2. 检查是否匹配副单位价格
  562. if (material4UnitPrice.getOtherUnitPrice() != null && orderUnitPrice.subtract(material4UnitPrice.getOtherUnitPrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) {
  563. BigDecimal baseQuantity = orderQuantity.multiply(material4UnitPrice.getRatio());
  564. return new UnitMatchResult(material4UnitPrice.getOtherUnit(), orderQuantity, baseQuantity, true);
  565. }
  566. // 3. 检查是否匹配副单位2价格
  567. if (material4UnitPrice.getOtherUnitTwoPrice() != null && orderUnitPrice.subtract(material4UnitPrice.getOtherUnitTwoPrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) {
  568. BigDecimal baseQuantity = orderQuantity.multiply(material4UnitPrice.getRatioTwo());
  569. return new UnitMatchResult(material4UnitPrice.getOtherUnitTwo(), orderQuantity, baseQuantity, true);
  570. }
  571. // 4. 检查是否匹配副单位3价格
  572. if (material4UnitPrice.getOtherUnitThreePrice() != null && orderUnitPrice.subtract(material4UnitPrice.getOtherUnitThreePrice()).abs().compareTo(PRICE_TOLERANCE) <= 0) {
  573. BigDecimal baseQuantity = orderQuantity.multiply(material4UnitPrice.getRatioThree());
  574. return new UnitMatchResult(material4UnitPrice.getOtherUnitThree(), orderQuantity, baseQuantity, true);
  575. }
  576. // 5. 默认返回基本单位(如果无法匹配)
  577. return new UnitMatchResult(material4UnitPrice.getBasicUnit(), orderQuantity, orderQuantity, false);
  578. }
  579. /**
  580. * 将基本单位数量转换为订单单位数量
  581. */
  582. private BigDecimal convertToOrderUnitQuantity(BigDecimal baseQuantity, String orderUnit, Material4UnitPrice material4UnitPrice) {
  583. if (orderUnit.equals(material4UnitPrice.getBasicUnit())) {
  584. return baseQuantity;
  585. } else if (orderUnit.equals(material4UnitPrice.getOtherUnit())) {
  586. return baseQuantity.divide(material4UnitPrice.getRatio());
  587. } else if (orderUnit.equals(material4UnitPrice.getOtherUnitTwo())) {
  588. return baseQuantity.divide(material4UnitPrice.getRatioTwo());
  589. } else if (orderUnit.equals(material4UnitPrice.getOtherUnitThree())) {
  590. return baseQuantity.divide(material4UnitPrice.getRatioThree());
  591. }
  592. return baseQuantity;
  593. }
  594. /**
  595. * 获取单位的换算比例
  596. */
  597. private BigDecimal getUnitRatio(String unit, Material4UnitPrice material4UnitPrice) {
  598. if (unit.equals(material4UnitPrice.getBasicUnit())) {
  599. return BigDecimal.ONE;
  600. } else if (unit.equals(material4UnitPrice.getOtherUnit())) {
  601. return material4UnitPrice.getRatio();
  602. } else if (unit.equals(material4UnitPrice.getOtherUnitTwo())) {
  603. return material4UnitPrice.getRatioTwo();
  604. } else if (unit.equals(material4UnitPrice.getOtherUnitThree())) {
  605. return material4UnitPrice.getRatioThree();
  606. }
  607. return BigDecimal.ONE;
  608. }
  609. /**
  610. * 单位匹配结果类
  611. */
  612. private static class UnitMatchResult {
  613. private String matchedUnit; // 匹配到的单位
  614. private BigDecimal orderUnitQuantity; // 订单单位数量
  615. private BigDecimal baseQuantity; // 基本单位数量
  616. private boolean isLargeUnit; // 是否是大单位
  617. public UnitMatchResult(String matchedUnit, BigDecimal orderUnitQuantity,
  618. BigDecimal baseQuantity, boolean isLargeUnit) {
  619. this.matchedUnit = matchedUnit;
  620. this.orderUnitQuantity = orderUnitQuantity;
  621. this.baseQuantity = baseQuantity;
  622. this.isLargeUnit = isLargeUnit;
  623. }
  624. // getter方法
  625. public String getMatchedUnit() {
  626. return matchedUnit;
  627. }
  628. public BigDecimal getBaseQuantity() {
  629. return baseQuantity;
  630. }
  631. public boolean isLargeUnit() {
  632. return isLargeUnit;
  633. }
  634. public BigDecimal getOrderUnitQuantity() {
  635. return orderUnitQuantity;
  636. }
  637. }
  638. }