package com.jeesite.modules.mes.erp; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jeesite.common.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * ERP 采购订单 BillQuery(PUR_PurchaseOrder),默认仅查标准采购订单。 */ @Service public class ErpPurchaseOrderService { private static final Logger logger = LoggerFactory.getLogger(ErpPurchaseOrderService.class); private static final String FORM_ID = "PUR_PurchaseOrder"; private static final String BILL_TYPE_FIELD = "FBillTypeID.FNUMBER"; private static final String MATERIAL_NUMBER_FIELD = "FMaterialId.FNUMBER"; private static final String SUPPLIER_NUMBER_FIELD = "FSupplierId.FNUMBER"; private static final String SUPPLIER_NAME_FIELD = "FSupplierId.FNAME"; private static final String HEADER_FIELD_KEYS = String.join(",", "FID", "FBILLNO", "FDate", "FDocumentStatus", BILL_TYPE_FIELD, "FBillTypeID.FNAME", "FBusinessType", "FPurchaseOrgId.FNAME", SUPPLIER_NUMBER_FIELD, "FSupplierId.FNAME"); private static final String DETAIL_FIELD_KEYS = String.join(",", "FID", "FBILLNO", "FDate", "FDocumentStatus", BILL_TYPE_FIELD, "FBillTypeID.FNAME", "FBusinessType", "FPurchaseOrgId.FNAME", SUPPLIER_NUMBER_FIELD, "FSupplierId.FNAME", "FSourceBillNo", "FPOOrderEntry_FEntryID", "FPOOrderEntry_FSeq", MATERIAL_NUMBER_FIELD, "FMaterialName", "FUnitID.FNAME", "FUnitID.FNUMBER", "FQty", "FDeliveryDate", "FPrice", "FTaxPrice", "FEntryTaxRate", "FAmount"); private final KingdeeErpClient erpClient; private final KingdeeErpProperties erpProperties; public ErpPurchaseOrderService(KingdeeErpClient erpClient, KingdeeErpProperties erpProperties) { this.erpClient = erpClient; this.erpProperties = erpProperties; } public List findPageList(ErpPurchaseOrder query, int startRow, int limit) { if (StringUtils.isNotBlank(query == null ? "" : query.getMaterialNumberQuery())) { return findAggregatedPageList(query, startRow, limit); } List headers = findHeaderList(query, startRow, limit); if (headers.isEmpty()) { return headers; } List billNos = new ArrayList<>(); Map orderMap = new LinkedHashMap<>(); for (ErpPurchaseOrder header : headers) { header.setEntryCount(0); header.setTotalQty(BigDecimal.ZERO); orderMap.put(header.getBillNo(), header); billNos.add(header.getBillNo()); } for (ErpPurchaseOrder detail : findDetailListByBillNos(billNos)) { ErpPurchaseOrder header = orderMap.get(detail.getBillNo()); if (header == null) { continue; } header.setEntryCount(header.getEntryCount() + 1); if (detail.getQty() != null) { header.setTotalQty(header.getTotalQty().add(detail.getQty())); } } return new ArrayList<>(orderMap.values()); } public List findDetailList(String billNo) { if (StringUtils.isBlank(billNo)) { return new ArrayList<>(); } List billNos = new ArrayList<>(); billNos.add(billNo.trim()); return findDetailListByBillNos(billNos); } private List findAggregatedPageList(ErpPurchaseOrder query, int startRow, int limit) { int targetSize = startRow + limit; int fetchStart = 0; int fetchLimit = Math.max(limit * 3, 50); int maxFetchRows = Math.max(targetSize * 5, 200); Map orderMap = new LinkedHashMap<>(); while (orderMap.size() < targetSize && fetchStart < maxFetchRows) { List chunk = findDetailList(query, fetchStart, fetchLimit); if (chunk.isEmpty()) { break; } for (ErpPurchaseOrder detail : chunk) { ErpPurchaseOrder header = orderMap.get(detail.getBillNo()); if (header == null) { header = toHeaderFromDetail(detail); header.setEntryCount(0); header.setTotalQty(BigDecimal.ZERO); orderMap.put(detail.getBillNo(), header); } header.setEntryCount(header.getEntryCount() + 1); if (detail.getQty() != null) { header.setTotalQty(header.getTotalQty().add(detail.getQty())); } } if (chunk.size() < fetchLimit) { break; } fetchStart += fetchLimit; } List allOrders = new ArrayList<>(orderMap.values()); if (startRow >= allOrders.size()) { return new ArrayList<>(); } return new ArrayList<>(allOrders.subList(startRow, Math.min(startRow + limit, allOrders.size()))); } private List findHeaderList(ErpPurchaseOrder query, int startRow, int limit) { JSONObject data = new JSONObject(); data.put("FormId", FORM_ID); data.put("FieldKeys", HEADER_FIELD_KEYS); data.put("FilterString", buildFilterString(query, false)); data.put("OrderString", "FDate DESC,FBILLNO DESC"); data.put("StartRow", Math.max(startRow, 0)); data.put("Limit", Math.max(limit, 1)); logger.info("ERP purchase order header query: {}", data.toJSONString()); return mapRows(erpClient.billQuery(data), false); } private List findDetailList(ErpPurchaseOrder query, int startRow, int limit) { JSONObject data = new JSONObject(); data.put("FormId", FORM_ID); data.put("FieldKeys", DETAIL_FIELD_KEYS); data.put("FilterString", buildFilterString(query, true)); data.put("OrderString", "FDate DESC,FBILLNO DESC,FPOOrderEntry_FSeq"); data.put("StartRow", Math.max(startRow, 0)); data.put("Limit", Math.max(limit, 1)); logger.info("ERP purchase order detail query: {}", data.toJSONString()); return mapRows(erpClient.billQuery(data), true); } private List findDetailListByBillNos(List billNos) { if (billNos == null || billNos.isEmpty()) { return new ArrayList<>(); } JSONObject data = new JSONObject(); data.put("FormId", FORM_ID); data.put("FieldKeys", DETAIL_FIELD_KEYS); data.put("FilterString", buildInFilterString(billNos)); data.put("OrderString", "FBILLNO DESC,FPOOrderEntry_FSeq"); data.put("StartRow", 0); data.put("Limit", 2000); logger.info("ERP purchase order detail by billNos query: {}", data.toJSONString()); return mapRows(erpClient.billQuery(data), true); } private List mapRows(JSONArray rows, boolean detail) { List list = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { list.add(detail ? toDetail(rows.getJSONObject(i), i) : toHeader(rows.getJSONObject(i))); } return list; } private ErpPurchaseOrder toHeader(JSONObject row) { ErpPurchaseOrder order = new ErpPurchaseOrder(); order.setFid(readString(row, "FID")); order.setBillNo(readString(row, "FBILLNO", "FBillNo")); order.setId(order.getBillNo()); fillHeaderFields(order, row); return order; } private ErpPurchaseOrder toHeaderFromDetail(ErpPurchaseOrder detail) { ErpPurchaseOrder order = new ErpPurchaseOrder(); order.setFid(detail.getFid()); order.setBillNo(detail.getBillNo()); order.setId(detail.getBillNo()); order.setDocumentStatus(detail.getDocumentStatus()); order.setDocumentStatusName(detail.getDocumentStatusName()); order.setDate(detail.getDate()); order.setBillTypeNumber(detail.getBillTypeNumber()); order.setBillTypeName(detail.getBillTypeName()); order.setBusinessType(detail.getBusinessType()); order.setBusinessTypeName(detail.getBusinessTypeName()); order.setPurchaseOrgName(detail.getPurchaseOrgName()); order.setSupplierNumber(detail.getSupplierNumber()); order.setSupplierName(detail.getSupplierName()); return order; } private ErpPurchaseOrder toDetail(JSONObject row, int index) { ErpPurchaseOrder order = new ErpPurchaseOrder(); order.setFid(readString(row, "FID")); order.setBillNo(readString(row, "FBILLNO", "FBillNo")); order.setEntryId(readString(row, "FPOOrderEntry_FEntryID", "FPOOrderEntry_FEntryId")); order.setSeq(readString(row, "FPOOrderEntry_FSeq")); String rowId = order.getBillNo(); if (StringUtils.isNotBlank(order.getEntryId())) { rowId = rowId + "-" + order.getEntryId(); } else if (index >= 0) { rowId = rowId + "-" + index; } order.setId(rowId); fillHeaderFields(order, row); order.setSourceBillNo(readString(row, "FSourceBillNo")); order.setMaterialNumber(readString(row, MATERIAL_NUMBER_FIELD, "FMaterialId.FNumber")); order.setMaterialName(readString(row, "FMaterialName", "FMaterialId.FNAME", "FMaterialId.FName")); order.setUnitName(readString(row, "FUnitID.FNAME", "FUnitId.FNAME", "FUnitId.FName")); order.setUnitNumber(readString(row, "FUnitID.FNUMBER", "FUnitId.FNUMBER", "FUnitId.FNumber")); order.setQty(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FQty"))); order.setDeliveryDate(formatErpDate(readString(row, "FDeliveryDate"))); order.setPrice(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FPrice"))); order.setTaxPrice(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FTaxPrice"))); order.setEntryTaxRate(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FEntryTaxRate"))); order.setAmount(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FAmount"))); return order; } private void fillHeaderFields(ErpPurchaseOrder order, JSONObject row) { order.setDocumentStatus(readString(row, "FDocumentStatus")); order.setDocumentStatusName(documentStatusName(order.getDocumentStatus())); order.setDate(formatErpDate(readString(row, "FDate"))); order.setBillTypeNumber(readString(row, BILL_TYPE_FIELD, "FBillTypeID.FNumber")); order.setBillTypeName(readString(row, "FBillTypeID.FNAME", "FBillTypeID.FName")); order.setBusinessType(readString(row, "FBusinessType")); order.setBusinessTypeName(businessTypeName(order.getBusinessType())); order.setPurchaseOrgName(readString(row, "FPurchaseOrgId.FNAME", "FPurchaseOrgId.FName")); order.setSupplierNumber(readString(row, SUPPLIER_NUMBER_FIELD, "FSupplierId.FNumber")); order.setSupplierName(readString(row, "FSupplierId.FNAME", "FSupplierId.FName")); } private String buildFilterString(ErpPurchaseOrder query, boolean includeMaterialFilter) { String filter = buildBaseFilter(); if (query == null) { return filter; } if (StringUtils.isNotBlank(query.getBillNo())) { filter = appendAnd(filter, "FBILLNO like '%" + escape(query.getBillNo()) + "%'"); } if (StringUtils.isNotBlank(query.getSupplierNumberQuery())) { filter = appendAnd(filter, SUPPLIER_NUMBER_FIELD + " like '%" + escape(query.getSupplierNumberQuery()) + "%'"); } if (StringUtils.isNotBlank(query.getSupplierNameQuery())) { filter = appendAnd(filter, SUPPLIER_NAME_FIELD + " like '%" + escape(query.getSupplierNameQuery()) + "%'"); } if (StringUtils.isNotBlank(query.getDocumentStatusQuery())) { filter = appendAnd(filter, "FDocumentStatus='" + escape(query.getDocumentStatusQuery()) + "'"); } if (includeMaterialFilter && StringUtils.isNotBlank(query.getMaterialNumberQuery())) { filter = appendAnd(filter, MATERIAL_NUMBER_FIELD + " like '%" + escape(query.getMaterialNumberQuery()) + "%'"); } return filter; } private String buildInFilterString(List billNos) { StringBuilder builder = new StringBuilder(buildBaseFilter()); builder.append(" AND FBILLNO IN("); for (int i = 0; i < billNos.size(); i++) { if (i > 0) { builder.append(","); } builder.append("'").append(escape(billNos.get(i))).append("'"); } builder.append(")"); return builder.toString(); } private String buildBaseFilter() { String filter = BILL_TYPE_FIELD + "='" + escape(erpProperties.getPurchaseOrderBillType()) + "'"; return appendAnd(filter, buildPurchaseOrgFilter()); } private String buildPurchaseOrgFilter() { if (!erpProperties.isFilterCurrentOrg()) { return ""; } if (StringUtils.isNotBlank(erpProperties.getOrgNumber())) { return "FPurchaseOrgId.FNUMBER='" + escape(erpProperties.getOrgNumber()) + "'"; } Long currentOrganizationId = erpClient.getCurrentOrganizationId(); if (currentOrganizationId == null) { return "1=1"; } return "FPurchaseOrgId=" + currentOrganizationId; } private String appendAnd(String left, String right) { if (StringUtils.isBlank(left)) { return right; } if (StringUtils.isBlank(right)) { return left; } return left + " AND " + right; } private String escape(String value) { return value == null ? "" : value.trim().replace("'", "''"); } private String readString(JSONObject row, String... keys) { for (String key : keys) { String value = row.getString(key); if (StringUtils.isNotBlank(value)) { return value.trim(); } } return ""; } private String formatErpDate(String rawValue) { if (StringUtils.isBlank(rawValue)) { return ""; } String trimmed = rawValue.trim(); return trimmed.length() >= 10 ? trimmed.substring(0, 10) : trimmed; } private String documentStatusName(String status) { if ("A".equals(status)) { return "创建"; } if ("B".equals(status)) { return "审核中"; } if ("C".equals(status)) { return "已审核"; } if ("D".equals(status)) { return "重新审核"; } return status; } private String businessTypeName(String businessType) { if ("CG".equals(businessType)) { return "标准采购"; } if ("WW".equals(businessType)) { return "标准委外"; } if ("ZCCG".equals(businessType)) { return "资产采购"; } if ("ZYCG".equals(businessType)) { return "直运采购"; } return businessType; } }