ErpTransferDirectService.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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.util.ArrayList;
  9. import java.util.LinkedHashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12. /**
  13. * ERP 直接调拨单 BillQuery(STK_TransferDirect)。
  14. * 固定仅查 HEV / 115 两种物料编码。
  15. */
  16. @Service
  17. public class ErpTransferDirectService {
  18. private static final Logger logger = LoggerFactory.getLogger(ErpTransferDirectService.class);
  19. private static final String FORM_ID = "STK_TransferDirect";
  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 DETAIL_FIELD_KEYS = String.join(",",
  23. "FID",
  24. "FBILLNO",
  25. "FDate",
  26. "FDocumentStatus",
  27. BILL_TYPE_FIELD,
  28. "FBillTypeID.FNAME",
  29. "FStockOrgId.FNUMBER",
  30. "FStockOrgId.FNAME",
  31. "FStockOutOrgId.FNUMBER",
  32. "FStockOutOrgId.FNAME",
  33. "FBillEntry_FEntryID",
  34. "FBillEntry_FSeq",
  35. MATERIAL_NUMBER_FIELD,
  36. "FMaterialName",
  37. "FUnitID.FNAME",
  38. "FQty",
  39. "FSrcStockId.FNUMBER",
  40. "FSrcStockId.FNAME",
  41. "FDestStockId.FNUMBER",
  42. "FDestStockId.FNAME",
  43. "FLot.FNUMBER",
  44. "FSrcBillNo",
  45. "FOrderNo",
  46. "FNote");
  47. private final KingdeeErpClient erpClient;
  48. private final KingdeeErpProperties erpProperties;
  49. public ErpTransferDirectService(KingdeeErpClient erpClient, KingdeeErpProperties erpProperties) {
  50. this.erpClient = erpClient;
  51. this.erpProperties = erpProperties;
  52. }
  53. /**
  54. * 分页查询调拨明细分录(仅 HEV / 115 物料)。
  55. */
  56. public List<ErpTransferDirect> findPageList(ErpTransferDirect query, int startRow, int limit) {
  57. JSONObject data = new JSONObject();
  58. data.put("FormId", FORM_ID);
  59. data.put("FieldKeys", DETAIL_FIELD_KEYS);
  60. data.put("FilterString", buildFilterString(query));
  61. data.put("OrderString", "FDate DESC,FBILLNO DESC,FBillEntry_FSeq");
  62. data.put("StartRow", Math.max(startRow, 0));
  63. data.put("Limit", Math.max(limit, 1));
  64. logger.info("ERP transfer direct query: {}", data.toJSONString());
  65. return mapRows(erpClient.billQuery(data));
  66. }
  67. /**
  68. * 指定调拨单号的全部分录(仍限定 HEV / 115 物料)。
  69. */
  70. public List<ErpTransferDirect> findDetailList(String billNo) {
  71. if (StringUtils.isBlank(billNo)) {
  72. return new ArrayList<>();
  73. }
  74. ErpTransferDirect query = new ErpTransferDirect();
  75. query.setBillNoQuery(billNo.trim());
  76. return findPageList(query, 0, 2000);
  77. }
  78. /**
  79. * 生产订单下查:优先按源单/订单号/关联内码,再按生产入库批号关联调拨单。
  80. * ERP 直接调拨单无 FMoBillNo,与生产订单的关联主要通过生产入库批号(FLot)建立。
  81. */
  82. public List<ErpTransferDirect> findTraceListByMoBillNo(String moBillNo, String moFid, List<String> instockLots) {
  83. if (StringUtils.isBlank(moBillNo)) {
  84. return new ArrayList<>();
  85. }
  86. Map<String, ErpTransferDirect> merged = new LinkedHashMap<>();
  87. appendTraceRows(merged, findTraceByDirectLink(moBillNo.trim(), moFid));
  88. appendTraceRows(merged, findTraceByInstockLots(instockLots));
  89. return new ArrayList<>(merged.values());
  90. }
  91. private void appendTraceRows(Map<String, ErpTransferDirect> merged, List<ErpTransferDirect> rows) {
  92. if (rows == null || rows.isEmpty()) {
  93. return;
  94. }
  95. for (ErpTransferDirect row : rows) {
  96. if (row == null || StringUtils.isBlank(row.getId())) {
  97. continue;
  98. }
  99. merged.putIfAbsent(row.getId(), row);
  100. }
  101. }
  102. private List<ErpTransferDirect> findTraceByDirectLink(String moBillNo, String moFid) {
  103. String escapedMo = escape(moBillNo);
  104. StringBuilder linkFilter = new StringBuilder("(FSrcBillNo='").append(escapedMo).append("' OR FOrderNo='")
  105. .append(escapedMo).append("')");
  106. if (StringUtils.isNotBlank(moFid)) {
  107. linkFilter.append(" OR FBillEntry_Link_FSBillId=").append(moFid.trim());
  108. }
  109. return queryTraceList("(" + linkFilter + ")");
  110. }
  111. private List<ErpTransferDirect> findTraceByInstockLots(List<String> instockLots) {
  112. if (instockLots == null || instockLots.isEmpty()) {
  113. return new ArrayList<>();
  114. }
  115. List<String> normalizedLots = new ArrayList<>();
  116. for (String lot : instockLots) {
  117. if (StringUtils.isBlank(lot)) {
  118. continue;
  119. }
  120. String trimmed = lot.trim();
  121. if (!normalizedLots.contains(trimmed)) {
  122. normalizedLots.add(trimmed);
  123. }
  124. }
  125. if (normalizedLots.isEmpty()) {
  126. return new ArrayList<>();
  127. }
  128. StringBuilder builder = new StringBuilder("FLot.FNUMBER IN(");
  129. for (int i = 0; i < normalizedLots.size(); i++) {
  130. if (i > 0) {
  131. builder.append(",");
  132. }
  133. builder.append("'").append(escape(normalizedLots.get(i))).append("'");
  134. }
  135. builder.append(")");
  136. return queryTraceList(builder.toString());
  137. }
  138. private List<ErpTransferDirect> queryTraceList(String traceFilter) {
  139. if (StringUtils.isBlank(traceFilter)) {
  140. return new ArrayList<>();
  141. }
  142. JSONObject data = new JSONObject();
  143. data.put("FormId", FORM_ID);
  144. data.put("FieldKeys", DETAIL_FIELD_KEYS);
  145. data.put("FilterString", appendAnd(buildTraceBaseFilter(), traceFilter));
  146. data.put("OrderString", "FDate DESC,FBILLNO DESC,FBillEntry_FSeq");
  147. data.put("StartRow", 0);
  148. data.put("Limit", 2000);
  149. logger.info("ERP transfer direct trace query: {}", data.toJSONString());
  150. return mapRows(erpClient.billQuery(data));
  151. }
  152. private String buildTraceBaseFilter() {
  153. return buildBaseFilter();
  154. }
  155. private List<ErpTransferDirect> mapRows(JSONArray rows) {
  156. List<ErpTransferDirect> list = new ArrayList<>();
  157. for (int i = 0; i < rows.size(); i++) {
  158. list.add(toDetail(rows.getJSONObject(i), i));
  159. }
  160. return list;
  161. }
  162. private ErpTransferDirect toDetail(JSONObject row, int index) {
  163. ErpTransferDirect item = new ErpTransferDirect();
  164. item.setFid(readString(row, "FID"));
  165. item.setBillNo(readString(row, "FBILLNO", "FBillNo"));
  166. item.setEntryId(readString(row, "FBillEntry_FEntryID", "FBillEntry_FEntryId"));
  167. item.setSeq(readString(row, "FBillEntry_FSeq"));
  168. String rowId = item.getBillNo();
  169. if (StringUtils.isNotBlank(item.getEntryId())) {
  170. rowId = rowId + "-" + item.getEntryId();
  171. } else if (index >= 0) {
  172. rowId = rowId + "-" + index;
  173. }
  174. item.setId(rowId);
  175. item.setDocumentStatus(readString(row, "FDocumentStatus"));
  176. item.setDocumentStatusName(documentStatusName(item.getDocumentStatus()));
  177. item.setDate(formatErpDate(readString(row, "FDate")));
  178. item.setBillTypeNumber(readString(row, BILL_TYPE_FIELD, "FBillTypeID.FNumber"));
  179. item.setBillTypeName(readString(row, "FBillTypeID.FNAME", "FBillTypeID.FName"));
  180. item.setStockOrgNumber(readString(row, "FStockOrgId.FNUMBER", "FStockOrgId.FNumber"));
  181. item.setStockOrgName(readString(row, "FStockOrgId.FNAME", "FStockOrgId.FName"));
  182. item.setStockOutOrgNumber(readString(row, "FStockOutOrgId.FNUMBER", "FStockOutOrgId.FNumber"));
  183. item.setStockOutOrgName(readString(row, "FStockOutOrgId.FNAME", "FStockOutOrgId.FName"));
  184. item.setMaterialNumber(readString(row, MATERIAL_NUMBER_FIELD, "FMaterialId.FNumber"));
  185. item.setMaterialName(readString(row, "FMaterialName", "FMaterialId.FNAME", "FMaterialId.FName"));
  186. item.setUnitName(readString(row, "FUnitID.FNAME", "FUnitId.FNAME", "FUnitId.FName"));
  187. item.setQty(ErpKingdeeParseUtils.normalizeQtyNullable(row.getBigDecimal("FQty")));
  188. item.setSrcStockNumber(readString(row, "FSrcStockId.FNUMBER", "FSrcStockId.FNumber"));
  189. item.setSrcStockName(readString(row, "FSrcStockId.FNAME", "FSrcStockId.FName"));
  190. item.setDestStockNumber(readString(row, "FDestStockId.FNUMBER", "FDestStockId.FNumber"));
  191. item.setDestStockName(readString(row, "FDestStockId.FNAME", "FDestStockId.FName"));
  192. item.setLotNumber(readString(row, "FLot.FNUMBER", "FLot.FNumber"));
  193. item.setSrcBillNo(trimToEmpty(readString(row, "FSrcBillNo")));
  194. item.setOrderNo(trimToEmpty(readString(row, "FOrderNo")));
  195. item.setNote(readString(row, "FNote"));
  196. return item;
  197. }
  198. private String trimToEmpty(String value) {
  199. return value == null ? "" : value.trim();
  200. }
  201. private String buildFilterString(ErpTransferDirect query) {
  202. String filter = buildBaseFilter();
  203. if (query == null) {
  204. return filter;
  205. }
  206. if (StringUtils.isNotBlank(query.getBillNoQuery())) {
  207. filter = appendAnd(filter, "FBILLNO like '%" + escape(query.getBillNoQuery()) + "%'");
  208. } else if (StringUtils.isNotBlank(query.getBillNo())) {
  209. filter = appendAnd(filter, "FBILLNO='" + escape(query.getBillNo()) + "'");
  210. }
  211. if (StringUtils.isNotBlank(query.getDocumentStatusQuery())) {
  212. filter = appendAnd(filter, "FDocumentStatus='" + escape(query.getDocumentStatusQuery()) + "'");
  213. }
  214. String materialFilter = buildMaterialFilter(query.getMaterialNumberQuery());
  215. if (StringUtils.isNotBlank(materialFilter)) {
  216. filter = appendAnd(filter, materialFilter);
  217. }
  218. return filter;
  219. }
  220. /**
  221. * 物料范围:默认固定 HEV + 115;页面可选缩小到其中一个。
  222. */
  223. private String buildMaterialFilter(String materialNumberQuery) {
  224. if (StringUtils.isNotBlank(materialNumberQuery)) {
  225. String trimmed = materialNumberQuery.trim();
  226. if (erpProperties.isAllowedTransferDirectMaterial(trimmed)) {
  227. return MATERIAL_NUMBER_FIELD + "='" + escape(trimmed) + "'";
  228. }
  229. }
  230. return buildAllowedMaterialInFilter();
  231. }
  232. private String buildAllowedMaterialInFilter() {
  233. List<String> materials = erpProperties.getTransferDirectMaterialNumbers();
  234. if (materials == null || materials.isEmpty()) {
  235. return MATERIAL_NUMBER_FIELD + "='8.10.HEV.10.047696'";
  236. }
  237. StringBuilder builder = new StringBuilder(MATERIAL_NUMBER_FIELD + " IN(");
  238. for (int i = 0; i < materials.size(); i++) {
  239. if (i > 0) {
  240. builder.append(",");
  241. }
  242. builder.append("'").append(escape(materials.get(i))).append("'");
  243. }
  244. builder.append(")");
  245. return builder.toString();
  246. }
  247. private String buildBaseFilter() {
  248. String filter = "";
  249. if (StringUtils.isNotBlank(erpProperties.getTransferDirectBillType())) {
  250. filter = BILL_TYPE_FIELD + "='" + escape(erpProperties.getTransferDirectBillType()) + "'";
  251. }
  252. return appendAnd(filter, buildStockOrgFilter());
  253. }
  254. private String buildStockOrgFilter() {
  255. if (!erpProperties.isFilterCurrentOrg()) {
  256. return "";
  257. }
  258. if (StringUtils.isNotBlank(erpProperties.getOrgNumber())) {
  259. return "FStockOrgId.FNUMBER='" + escape(erpProperties.getOrgNumber()) + "'";
  260. }
  261. Long currentOrganizationId = erpClient.getCurrentOrganizationId();
  262. if (currentOrganizationId == null) {
  263. return "1=1";
  264. }
  265. return "FStockOrgId=" + currentOrganizationId;
  266. }
  267. private String appendAnd(String left, String right) {
  268. if (StringUtils.isBlank(left)) {
  269. return right;
  270. }
  271. if (StringUtils.isBlank(right)) {
  272. return left;
  273. }
  274. return left + " AND " + right;
  275. }
  276. private String escape(String value) {
  277. return value == null ? "" : value.trim().replace("'", "''");
  278. }
  279. private String readString(JSONObject row, String... keys) {
  280. return ErpKingdeeParseUtils.readString(row, keys);
  281. }
  282. private String formatErpDate(String rawValue) {
  283. if (StringUtils.isBlank(rawValue)) {
  284. return "";
  285. }
  286. String trimmed = rawValue.trim();
  287. return trimmed.length() >= 10 ? trimmed.substring(0, 10) : trimmed;
  288. }
  289. private String documentStatusName(String status) {
  290. if ("A".equals(status)) {
  291. return "创建";
  292. }
  293. if ("B".equals(status)) {
  294. return "审核中";
  295. }
  296. if ("C".equals(status)) {
  297. return "已审核";
  298. }
  299. if ("D".equals(status)) {
  300. return "重新审核";
  301. }
  302. return status;
  303. }
  304. }