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.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * ERP 直接调拨单 BillQuery(STK_TransferDirect)。 * 固定仅查 HEV / 115 两种物料编码。 */ @Service public class ErpTransferDirectService { private static final Logger logger = LoggerFactory.getLogger(ErpTransferDirectService.class); private static final String FORM_ID = "STK_TransferDirect"; private static final String BILL_TYPE_FIELD = "FBillTypeID.FNUMBER"; private static final String MATERIAL_NUMBER_FIELD = "FMaterialId.FNUMBER"; private static final String DETAIL_FIELD_KEYS = String.join(",", "FID", "FBILLNO", "FDate", "FDocumentStatus", BILL_TYPE_FIELD, "FBillTypeID.FNAME", "FStockOrgId.FNUMBER", "FStockOrgId.FNAME", "FStockOutOrgId.FNUMBER", "FStockOutOrgId.FNAME", "FBillEntry_FEntryID", "FBillEntry_FSeq", MATERIAL_NUMBER_FIELD, "FMaterialName", "FUnitID.FNAME", "FQty", "FSrcStockId.FNUMBER", "FSrcStockId.FNAME", "FDestStockId.FNUMBER", "FDestStockId.FNAME", "FLot.FNUMBER", "FSrcBillNo", "FOrderNo", "FNote"); private final KingdeeErpClient erpClient; private final KingdeeErpProperties erpProperties; public ErpTransferDirectService(KingdeeErpClient erpClient, KingdeeErpProperties erpProperties) { this.erpClient = erpClient; this.erpProperties = erpProperties; } /** * 分页查询调拨明细分录(仅 HEV / 115 物料)。 */ public List findPageList(ErpTransferDirect 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)); data.put("OrderString", "FDate DESC,FBILLNO DESC,FBillEntry_FSeq"); data.put("StartRow", Math.max(startRow, 0)); data.put("Limit", Math.max(limit, 1)); logger.info("ERP transfer direct query: {}", data.toJSONString()); return mapRows(erpClient.billQuery(data)); } /** * 指定调拨单号的全部分录(仍限定 HEV / 115 物料)。 */ public List findDetailList(String billNo) { if (StringUtils.isBlank(billNo)) { return new ArrayList<>(); } ErpTransferDirect query = new ErpTransferDirect(); query.setBillNoQuery(billNo.trim()); return findPageList(query, 0, 2000); } /** * 生产订单下查:优先按源单/订单号/关联内码,再按生产入库批号关联调拨单。 * ERP 直接调拨单无 FMoBillNo,与生产订单的关联主要通过生产入库批号(FLot)建立。 */ public List findTraceListByMoBillNo(String moBillNo, String moFid, List instockLots) { if (StringUtils.isBlank(moBillNo)) { return new ArrayList<>(); } Map merged = new LinkedHashMap<>(); appendTraceRows(merged, findTraceByDirectLink(moBillNo.trim(), moFid)); appendTraceRows(merged, findTraceByInstockLots(instockLots)); return new ArrayList<>(merged.values()); } private void appendTraceRows(Map merged, List rows) { if (rows == null || rows.isEmpty()) { return; } for (ErpTransferDirect row : rows) { if (row == null || StringUtils.isBlank(row.getId())) { continue; } merged.putIfAbsent(row.getId(), row); } } private List findTraceByDirectLink(String moBillNo, String moFid) { String escapedMo = escape(moBillNo); StringBuilder linkFilter = new StringBuilder("(FSrcBillNo='").append(escapedMo).append("' OR FOrderNo='") .append(escapedMo).append("')"); if (StringUtils.isNotBlank(moFid)) { linkFilter.append(" OR FBillEntry_Link_FSBillId=").append(moFid.trim()); } return queryTraceList("(" + linkFilter + ")"); } private List findTraceByInstockLots(List instockLots) { if (instockLots == null || instockLots.isEmpty()) { return new ArrayList<>(); } List normalizedLots = new ArrayList<>(); for (String lot : instockLots) { if (StringUtils.isBlank(lot)) { continue; } String trimmed = lot.trim(); if (!normalizedLots.contains(trimmed)) { normalizedLots.add(trimmed); } } if (normalizedLots.isEmpty()) { return new ArrayList<>(); } StringBuilder builder = new StringBuilder("FLot.FNUMBER IN("); for (int i = 0; i < normalizedLots.size(); i++) { if (i > 0) { builder.append(","); } builder.append("'").append(escape(normalizedLots.get(i))).append("'"); } builder.append(")"); return queryTraceList(builder.toString()); } private List queryTraceList(String traceFilter) { if (StringUtils.isBlank(traceFilter)) { return new ArrayList<>(); } JSONObject data = new JSONObject(); data.put("FormId", FORM_ID); data.put("FieldKeys", DETAIL_FIELD_KEYS); data.put("FilterString", appendAnd(buildTraceBaseFilter(), traceFilter)); data.put("OrderString", "FDate DESC,FBILLNO DESC,FBillEntry_FSeq"); data.put("StartRow", 0); data.put("Limit", 2000); logger.info("ERP transfer direct trace query: {}", data.toJSONString()); return mapRows(erpClient.billQuery(data)); } private String buildTraceBaseFilter() { return buildBaseFilter(); } private List mapRows(JSONArray rows) { List list = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { list.add(toDetail(rows.getJSONObject(i), i)); } return list; } private ErpTransferDirect toDetail(JSONObject row, int index) { ErpTransferDirect item = new ErpTransferDirect(); item.setFid(readString(row, "FID")); item.setBillNo(readString(row, "FBILLNO", "FBillNo")); item.setEntryId(readString(row, "FBillEntry_FEntryID", "FBillEntry_FEntryId")); item.setSeq(readString(row, "FBillEntry_FSeq")); String rowId = item.getBillNo(); if (StringUtils.isNotBlank(item.getEntryId())) { rowId = rowId + "-" + item.getEntryId(); } else if (index >= 0) { rowId = rowId + "-" + index; } item.setId(rowId); item.setDocumentStatus(readString(row, "FDocumentStatus")); item.setDocumentStatusName(documentStatusName(item.getDocumentStatus())); item.setDate(formatErpDate(readString(row, "FDate"))); item.setBillTypeNumber(readString(row, BILL_TYPE_FIELD, "FBillTypeID.FNumber")); item.setBillTypeName(readString(row, "FBillTypeID.FNAME", "FBillTypeID.FName")); item.setStockOrgNumber(readString(row, "FStockOrgId.FNUMBER", "FStockOrgId.FNumber")); item.setStockOrgName(readString(row, "FStockOrgId.FNAME", "FStockOrgId.FName")); item.setStockOutOrgNumber(readString(row, "FStockOutOrgId.FNUMBER", "FStockOutOrgId.FNumber")); item.setStockOutOrgName(readString(row, "FStockOutOrgId.FNAME", "FStockOutOrgId.FName")); item.setMaterialNumber(readString(row, MATERIAL_NUMBER_FIELD, "FMaterialId.FNumber")); item.setMaterialName(readString(row, "FMaterialName", "FMaterialId.FNAME", "FMaterialId.FName")); item.setUnitName(readString(row, "FUnitID.FNAME", "FUnitId.FNAME", "FUnitId.FName")); item.setQty(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FQty"))); item.setSrcStockNumber(readString(row, "FSrcStockId.FNUMBER", "FSrcStockId.FNumber")); item.setSrcStockName(readString(row, "FSrcStockId.FNAME", "FSrcStockId.FName")); item.setDestStockNumber(readString(row, "FDestStockId.FNUMBER", "FDestStockId.FNumber")); item.setDestStockName(readString(row, "FDestStockId.FNAME", "FDestStockId.FName")); item.setLotNumber(readString(row, "FLot.FNUMBER", "FLot.FNumber")); item.setSrcBillNo(trimToEmpty(readString(row, "FSrcBillNo"))); item.setOrderNo(trimToEmpty(readString(row, "FOrderNo"))); item.setNote(readString(row, "FNote")); return item; } private String trimToEmpty(String value) { return value == null ? "" : value.trim(); } private String buildFilterString(ErpTransferDirect query) { String filter = buildBaseFilter(); if (query == null) { return filter; } if (StringUtils.isNotBlank(query.getBillNoQuery())) { filter = appendAnd(filter, "FBILLNO like '%" + escape(query.getBillNoQuery()) + "%'"); } else if (StringUtils.isNotBlank(query.getBillNo())) { filter = appendAnd(filter, "FBILLNO='" + escape(query.getBillNo()) + "'"); } if (StringUtils.isNotBlank(query.getDocumentStatusQuery())) { filter = appendAnd(filter, "FDocumentStatus='" + escape(query.getDocumentStatusQuery()) + "'"); } String materialFilter = buildMaterialFilter(query.getMaterialNumberQuery()); if (StringUtils.isNotBlank(materialFilter)) { filter = appendAnd(filter, materialFilter); } return filter; } /** * 物料范围:默认固定 HEV + 115;页面可选缩小到其中一个。 */ private String buildMaterialFilter(String materialNumberQuery) { if (StringUtils.isNotBlank(materialNumberQuery)) { String trimmed = materialNumberQuery.trim(); if (erpProperties.isAllowedTransferDirectMaterial(trimmed)) { return MATERIAL_NUMBER_FIELD + "='" + escape(trimmed) + "'"; } } return buildAllowedMaterialInFilter(); } private String buildAllowedMaterialInFilter() { List materials = erpProperties.getTransferDirectMaterialNumbers(); if (materials == null || materials.isEmpty()) { return MATERIAL_NUMBER_FIELD + "='8.10.HEV.10.047696'"; } StringBuilder builder = new StringBuilder(MATERIAL_NUMBER_FIELD + " IN("); for (int i = 0; i < materials.size(); i++) { if (i > 0) { builder.append(","); } builder.append("'").append(escape(materials.get(i))).append("'"); } builder.append(")"); return builder.toString(); } private String buildBaseFilter() { String filter = ""; if (StringUtils.isNotBlank(erpProperties.getTransferDirectBillType())) { filter = BILL_TYPE_FIELD + "='" + escape(erpProperties.getTransferDirectBillType()) + "'"; } return appendAnd(filter, buildStockOrgFilter()); } private String buildStockOrgFilter() { if (!erpProperties.isFilterCurrentOrg()) { return ""; } if (StringUtils.isNotBlank(erpProperties.getOrgNumber())) { return "FStockOrgId.FNUMBER='" + escape(erpProperties.getOrgNumber()) + "'"; } Long currentOrganizationId = erpClient.getCurrentOrganizationId(); if (currentOrganizationId == null) { return "1=1"; } return "FStockOrgId=" + 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) { return ErpKingdeeParseUtils.readString(row, keys); } 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; } }