ErpPurchaseOrderService.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package com.jeesite.modules.mes.erp;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.jeesite.common.lang.StringUtils;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.stereotype.Service;
  8. import java.math.BigDecimal;
  9. import java.util.ArrayList;
  10. import java.util.LinkedHashMap;
  11. import java.util.List;
  12. import java.util.Map;
  13. /**
  14. * ERP 采购订单 BillQuery(PUR_PurchaseOrder),默认仅查标准采购订单。
  15. */
  16. @Service
  17. public class ErpPurchaseOrderService {
  18. private static final Logger logger = LoggerFactory.getLogger(ErpPurchaseOrderService.class);
  19. private static final String FORM_ID = "PUR_PurchaseOrder";
  20. private static final String BILL_TYPE_FIELD = "FBillTypeID.FNUMBER";
  21. private static final String MATERIAL_NUMBER_FIELD = "FMaterialId.FNUMBER";
  22. private static final String SUPPLIER_NUMBER_FIELD = "FSupplierId.FNUMBER";
  23. private static final String SUPPLIER_NAME_FIELD = "FSupplierId.FNAME";
  24. private static final String HEADER_FIELD_KEYS = String.join(",",
  25. "FID",
  26. "FBILLNO",
  27. "FDate",
  28. "FDocumentStatus",
  29. BILL_TYPE_FIELD,
  30. "FBillTypeID.FNAME",
  31. "FBusinessType",
  32. "FPurchaseOrgId.FNAME",
  33. SUPPLIER_NUMBER_FIELD,
  34. "FSupplierId.FNAME");
  35. private static final String DETAIL_FIELD_KEYS = String.join(",",
  36. "FID",
  37. "FBILLNO",
  38. "FDate",
  39. "FDocumentStatus",
  40. BILL_TYPE_FIELD,
  41. "FBillTypeID.FNAME",
  42. "FBusinessType",
  43. "FPurchaseOrgId.FNAME",
  44. SUPPLIER_NUMBER_FIELD,
  45. "FSupplierId.FNAME",
  46. "FSourceBillNo",
  47. "FPOOrderEntry_FEntryID",
  48. "FPOOrderEntry_FSeq",
  49. MATERIAL_NUMBER_FIELD,
  50. "FMaterialName",
  51. "FUnitID.FNAME",
  52. "FUnitID.FNUMBER",
  53. "FQty",
  54. "FDeliveryDate",
  55. "FPrice",
  56. "FTaxPrice",
  57. "FEntryTaxRate",
  58. "FAmount");
  59. private final KingdeeErpClient erpClient;
  60. private final KingdeeErpProperties erpProperties;
  61. public ErpPurchaseOrderService(KingdeeErpClient erpClient, KingdeeErpProperties erpProperties) {
  62. this.erpClient = erpClient;
  63. this.erpProperties = erpProperties;
  64. }
  65. public List<ErpPurchaseOrder> findPageList(ErpPurchaseOrder query, int startRow, int limit) {
  66. if (StringUtils.isNotBlank(query == null ? "" : query.getMaterialNumberQuery())) {
  67. return findAggregatedPageList(query, startRow, limit);
  68. }
  69. List<ErpPurchaseOrder> headers = findHeaderList(query, startRow, limit);
  70. if (headers.isEmpty()) {
  71. return headers;
  72. }
  73. List<String> billNos = new ArrayList<>();
  74. Map<String, ErpPurchaseOrder> orderMap = new LinkedHashMap<>();
  75. for (ErpPurchaseOrder header : headers) {
  76. header.setEntryCount(0);
  77. header.setTotalQty(BigDecimal.ZERO);
  78. orderMap.put(header.getBillNo(), header);
  79. billNos.add(header.getBillNo());
  80. }
  81. for (ErpPurchaseOrder detail : findDetailListByBillNos(billNos)) {
  82. ErpPurchaseOrder header = orderMap.get(detail.getBillNo());
  83. if (header == null) {
  84. continue;
  85. }
  86. header.setEntryCount(header.getEntryCount() + 1);
  87. if (detail.getQty() != null) {
  88. header.setTotalQty(header.getTotalQty().add(detail.getQty()));
  89. }
  90. }
  91. return new ArrayList<>(orderMap.values());
  92. }
  93. public List<ErpPurchaseOrder> findDetailList(String billNo) {
  94. if (StringUtils.isBlank(billNo)) {
  95. return new ArrayList<>();
  96. }
  97. List<String> billNos = new ArrayList<>();
  98. billNos.add(billNo.trim());
  99. return findDetailListByBillNos(billNos);
  100. }
  101. private List<ErpPurchaseOrder> findAggregatedPageList(ErpPurchaseOrder query, int startRow, int limit) {
  102. int targetSize = startRow + limit;
  103. int fetchStart = 0;
  104. int fetchLimit = Math.max(limit * 3, 50);
  105. int maxFetchRows = Math.max(targetSize * 5, 200);
  106. Map<String, ErpPurchaseOrder> orderMap = new LinkedHashMap<>();
  107. while (orderMap.size() < targetSize && fetchStart < maxFetchRows) {
  108. List<ErpPurchaseOrder> chunk = findDetailList(query, fetchStart, fetchLimit);
  109. if (chunk.isEmpty()) {
  110. break;
  111. }
  112. for (ErpPurchaseOrder detail : chunk) {
  113. ErpPurchaseOrder header = orderMap.get(detail.getBillNo());
  114. if (header == null) {
  115. header = toHeaderFromDetail(detail);
  116. header.setEntryCount(0);
  117. header.setTotalQty(BigDecimal.ZERO);
  118. orderMap.put(detail.getBillNo(), header);
  119. }
  120. header.setEntryCount(header.getEntryCount() + 1);
  121. if (detail.getQty() != null) {
  122. header.setTotalQty(header.getTotalQty().add(detail.getQty()));
  123. }
  124. }
  125. if (chunk.size() < fetchLimit) {
  126. break;
  127. }
  128. fetchStart += fetchLimit;
  129. }
  130. List<ErpPurchaseOrder> allOrders = new ArrayList<>(orderMap.values());
  131. if (startRow >= allOrders.size()) {
  132. return new ArrayList<>();
  133. }
  134. return new ArrayList<>(allOrders.subList(startRow, Math.min(startRow + limit, allOrders.size())));
  135. }
  136. private List<ErpPurchaseOrder> findHeaderList(ErpPurchaseOrder query, int startRow, int limit) {
  137. JSONObject data = new JSONObject();
  138. data.put("FormId", FORM_ID);
  139. data.put("FieldKeys", HEADER_FIELD_KEYS);
  140. data.put("FilterString", buildFilterString(query, false));
  141. data.put("OrderString", "FDate DESC,FBILLNO DESC");
  142. data.put("StartRow", Math.max(startRow, 0));
  143. data.put("Limit", Math.max(limit, 1));
  144. logger.info("ERP purchase order header query: {}", data.toJSONString());
  145. return mapRows(erpClient.billQuery(data), false);
  146. }
  147. private List<ErpPurchaseOrder> findDetailList(ErpPurchaseOrder query, int startRow, int limit) {
  148. JSONObject data = new JSONObject();
  149. data.put("FormId", FORM_ID);
  150. data.put("FieldKeys", DETAIL_FIELD_KEYS);
  151. data.put("FilterString", buildFilterString(query, true));
  152. data.put("OrderString", "FDate DESC,FBILLNO DESC,FPOOrderEntry_FSeq");
  153. data.put("StartRow", Math.max(startRow, 0));
  154. data.put("Limit", Math.max(limit, 1));
  155. logger.info("ERP purchase order detail query: {}", data.toJSONString());
  156. return mapRows(erpClient.billQuery(data), true);
  157. }
  158. private List<ErpPurchaseOrder> findDetailListByBillNos(List<String> billNos) {
  159. if (billNos == null || billNos.isEmpty()) {
  160. return new ArrayList<>();
  161. }
  162. JSONObject data = new JSONObject();
  163. data.put("FormId", FORM_ID);
  164. data.put("FieldKeys", DETAIL_FIELD_KEYS);
  165. data.put("FilterString", buildInFilterString(billNos));
  166. data.put("OrderString", "FBILLNO DESC,FPOOrderEntry_FSeq");
  167. data.put("StartRow", 0);
  168. data.put("Limit", 2000);
  169. logger.info("ERP purchase order detail by billNos query: {}", data.toJSONString());
  170. return mapRows(erpClient.billQuery(data), true);
  171. }
  172. private List<ErpPurchaseOrder> mapRows(JSONArray rows, boolean detail) {
  173. List<ErpPurchaseOrder> list = new ArrayList<>();
  174. for (int i = 0; i < rows.size(); i++) {
  175. list.add(detail ? toDetail(rows.getJSONObject(i), i) : toHeader(rows.getJSONObject(i)));
  176. }
  177. return list;
  178. }
  179. private ErpPurchaseOrder toHeader(JSONObject row) {
  180. ErpPurchaseOrder order = new ErpPurchaseOrder();
  181. order.setFid(readString(row, "FID"));
  182. order.setBillNo(readString(row, "FBILLNO", "FBillNo"));
  183. order.setId(order.getBillNo());
  184. fillHeaderFields(order, row);
  185. return order;
  186. }
  187. private ErpPurchaseOrder toHeaderFromDetail(ErpPurchaseOrder detail) {
  188. ErpPurchaseOrder order = new ErpPurchaseOrder();
  189. order.setFid(detail.getFid());
  190. order.setBillNo(detail.getBillNo());
  191. order.setId(detail.getBillNo());
  192. order.setDocumentStatus(detail.getDocumentStatus());
  193. order.setDocumentStatusName(detail.getDocumentStatusName());
  194. order.setDate(detail.getDate());
  195. order.setBillTypeNumber(detail.getBillTypeNumber());
  196. order.setBillTypeName(detail.getBillTypeName());
  197. order.setBusinessType(detail.getBusinessType());
  198. order.setBusinessTypeName(detail.getBusinessTypeName());
  199. order.setPurchaseOrgName(detail.getPurchaseOrgName());
  200. order.setSupplierNumber(detail.getSupplierNumber());
  201. order.setSupplierName(detail.getSupplierName());
  202. return order;
  203. }
  204. private ErpPurchaseOrder toDetail(JSONObject row, int index) {
  205. ErpPurchaseOrder order = new ErpPurchaseOrder();
  206. order.setFid(readString(row, "FID"));
  207. order.setBillNo(readString(row, "FBILLNO", "FBillNo"));
  208. order.setEntryId(readString(row, "FPOOrderEntry_FEntryID", "FPOOrderEntry_FEntryId"));
  209. order.setSeq(readString(row, "FPOOrderEntry_FSeq"));
  210. String rowId = order.getBillNo();
  211. if (StringUtils.isNotBlank(order.getEntryId())) {
  212. rowId = rowId + "-" + order.getEntryId();
  213. } else if (index >= 0) {
  214. rowId = rowId + "-" + index;
  215. }
  216. order.setId(rowId);
  217. fillHeaderFields(order, row);
  218. order.setSourceBillNo(readString(row, "FSourceBillNo"));
  219. order.setMaterialNumber(readString(row, MATERIAL_NUMBER_FIELD, "FMaterialId.FNumber"));
  220. order.setMaterialName(readString(row, "FMaterialName", "FMaterialId.FNAME", "FMaterialId.FName"));
  221. order.setUnitName(readString(row, "FUnitID.FNAME", "FUnitId.FNAME", "FUnitId.FName"));
  222. order.setUnitNumber(readString(row, "FUnitID.FNUMBER", "FUnitId.FNUMBER", "FUnitId.FNumber"));
  223. order.setQty(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FQty")));
  224. order.setDeliveryDate(formatErpDate(readString(row, "FDeliveryDate")));
  225. order.setPrice(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FPrice")));
  226. order.setTaxPrice(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FTaxPrice")));
  227. order.setEntryTaxRate(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FEntryTaxRate")));
  228. order.setAmount(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FAmount")));
  229. return order;
  230. }
  231. private void fillHeaderFields(ErpPurchaseOrder order, JSONObject row) {
  232. order.setDocumentStatus(readString(row, "FDocumentStatus"));
  233. order.setDocumentStatusName(documentStatusName(order.getDocumentStatus()));
  234. order.setDate(formatErpDate(readString(row, "FDate")));
  235. order.setBillTypeNumber(readString(row, BILL_TYPE_FIELD, "FBillTypeID.FNumber"));
  236. order.setBillTypeName(readString(row, "FBillTypeID.FNAME", "FBillTypeID.FName"));
  237. order.setBusinessType(readString(row, "FBusinessType"));
  238. order.setBusinessTypeName(businessTypeName(order.getBusinessType()));
  239. order.setPurchaseOrgName(readString(row, "FPurchaseOrgId.FNAME", "FPurchaseOrgId.FName"));
  240. order.setSupplierNumber(readString(row, SUPPLIER_NUMBER_FIELD, "FSupplierId.FNumber"));
  241. order.setSupplierName(readString(row, "FSupplierId.FNAME", "FSupplierId.FName"));
  242. }
  243. private String buildFilterString(ErpPurchaseOrder query, boolean includeMaterialFilter) {
  244. String filter = buildBaseFilter();
  245. if (query == null) {
  246. return filter;
  247. }
  248. if (StringUtils.isNotBlank(query.getBillNo())) {
  249. filter = appendAnd(filter, "FBILLNO like '%" + escape(query.getBillNo()) + "%'");
  250. }
  251. if (StringUtils.isNotBlank(query.getSupplierNumberQuery())) {
  252. filter = appendAnd(filter,
  253. SUPPLIER_NUMBER_FIELD + " like '%" + escape(query.getSupplierNumberQuery()) + "%'");
  254. }
  255. if (StringUtils.isNotBlank(query.getSupplierNameQuery())) {
  256. filter = appendAnd(filter,
  257. SUPPLIER_NAME_FIELD + " like '%" + escape(query.getSupplierNameQuery()) + "%'");
  258. }
  259. if (StringUtils.isNotBlank(query.getDocumentStatusQuery())) {
  260. filter = appendAnd(filter, "FDocumentStatus='" + escape(query.getDocumentStatusQuery()) + "'");
  261. }
  262. if (includeMaterialFilter && StringUtils.isNotBlank(query.getMaterialNumberQuery())) {
  263. filter = appendAnd(filter,
  264. MATERIAL_NUMBER_FIELD + " like '%" + escape(query.getMaterialNumberQuery()) + "%'");
  265. }
  266. return filter;
  267. }
  268. private String buildInFilterString(List<String> billNos) {
  269. StringBuilder builder = new StringBuilder(buildBaseFilter());
  270. builder.append(" AND FBILLNO IN(");
  271. for (int i = 0; i < billNos.size(); i++) {
  272. if (i > 0) {
  273. builder.append(",");
  274. }
  275. builder.append("'").append(escape(billNos.get(i))).append("'");
  276. }
  277. builder.append(")");
  278. return builder.toString();
  279. }
  280. private String buildBaseFilter() {
  281. String filter = BILL_TYPE_FIELD + "='" + escape(erpProperties.getPurchaseOrderBillType()) + "'";
  282. return appendAnd(filter, buildPurchaseOrgFilter());
  283. }
  284. private String buildPurchaseOrgFilter() {
  285. if (!erpProperties.isFilterCurrentOrg()) {
  286. return "";
  287. }
  288. if (StringUtils.isNotBlank(erpProperties.getOrgNumber())) {
  289. return "FPurchaseOrgId.FNUMBER='" + escape(erpProperties.getOrgNumber()) + "'";
  290. }
  291. Long currentOrganizationId = erpClient.getCurrentOrganizationId();
  292. if (currentOrganizationId == null) {
  293. return "1=1";
  294. }
  295. return "FPurchaseOrgId=" + currentOrganizationId;
  296. }
  297. private String appendAnd(String left, String right) {
  298. if (StringUtils.isBlank(left)) {
  299. return right;
  300. }
  301. if (StringUtils.isBlank(right)) {
  302. return left;
  303. }
  304. return left + " AND " + right;
  305. }
  306. private String escape(String value) {
  307. return value == null ? "" : value.trim().replace("'", "''");
  308. }
  309. private String readString(JSONObject row, String... keys) {
  310. for (String key : keys) {
  311. String value = row.getString(key);
  312. if (StringUtils.isNotBlank(value)) {
  313. return value.trim();
  314. }
  315. }
  316. return "";
  317. }
  318. private String formatErpDate(String rawValue) {
  319. if (StringUtils.isBlank(rawValue)) {
  320. return "";
  321. }
  322. String trimmed = rawValue.trim();
  323. return trimmed.length() >= 10 ? trimmed.substring(0, 10) : trimmed;
  324. }
  325. private String documentStatusName(String status) {
  326. if ("A".equals(status)) {
  327. return "创建";
  328. }
  329. if ("B".equals(status)) {
  330. return "审核中";
  331. }
  332. if ("C".equals(status)) {
  333. return "已审核";
  334. }
  335. if ("D".equals(status)) {
  336. return "重新审核";
  337. }
  338. return status;
  339. }
  340. private String businessTypeName(String businessType) {
  341. if ("CG".equals(businessType)) {
  342. return "标准采购";
  343. }
  344. if ("WW".equals(businessType)) {
  345. return "标准委外";
  346. }
  347. if ("ZCCG".equals(businessType)) {
  348. return "资产采购";
  349. }
  350. if ("ZYCG".equals(businessType)) {
  351. return "直运采购";
  352. }
  353. return businessType;
  354. }
  355. }