| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- 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(PRD_PickMtrl)。
- */
- @Service
- public class ErpPickMaterialBillService {
- private static final Logger logger = LoggerFactory.getLogger(ErpPickMaterialBillService.class);
- private static final String FORM_ID = "PRD_PickMtrl";
- private static final int VISIBLE_MO_LIMIT = 500;
- /** BillQuery 分录字段:除分录内码外,明细字段一般不带 FEntity_ 前缀 */
- private static final String MO_BILL_NO_FIELD = "FMoBillNo";
- private static final String MATERIAL_NUMBER_FIELD = "FMaterialId.FNUMBER";
- private static final String HEADER_FIELD_KEYS = String.join(",",
- "FID",
- "FBILLNO",
- "FDate",
- "FDocumentStatus",
- "FPrdOrgId.FNUMBER",
- "FPrdOrgId.FNAME",
- "FStockOrgId.FNUMBER",
- "FStockOrgId.FNAME");
- private static final String DETAIL_FIELD_KEYS = String.join(",",
- "FID",
- "FBILLNO",
- "FDate",
- "FDocumentStatus",
- "FPrdOrgId.FNUMBER",
- "FPrdOrgId.FNAME",
- "FStockOrgId.FNUMBER",
- "FStockOrgId.FNAME",
- "FEntryWorkShopId.FNUMBER",
- "FEntryWorkShopId.FNAME",
- "FEntity_FEntryID",
- "FEntity_FSeq",
- "FMoEntrySeq",
- MATERIAL_NUMBER_FIELD,
- "FMaterialName",
- "FUnitID.FNAME",
- "FAppQty",
- "FActualQty",
- MO_BILL_NO_FIELD,
- "FPPBomBillNo",
- "FStockId.FNUMBER",
- "FStockId.FNAME");
- private final KingdeeErpClient erpClient;
- private final KingdeeErpProperties erpProperties;
- private final ErpProductionOrderService erpProductionOrderService;
- private final MesErpPickKcService mesErpPickKcService;
- public ErpPickMaterialBillService(KingdeeErpClient erpClient, KingdeeErpProperties erpProperties,
- ErpProductionOrderService erpProductionOrderService, MesErpPickKcService mesErpPickKcService) {
- this.erpClient = erpClient;
- this.erpProperties = erpProperties;
- this.erpProductionOrderService = erpProductionOrderService;
- this.mesErpPickKcService = mesErpPickKcService;
- }
- /**
- * 列表页:按领料单号聚合。
- */
- public List<ErpPickMaterialBill> findPageList(ErpPickMaterialBill query, int startRow, int limit) {
- String moFilter = buildProductionOrderFilter(query);
- if (moFilter == null) {
- return new ArrayList<>();
- }
- if (StringUtils.isNotBlank(query == null ? "" : query.getMaterialNumberQuery())) {
- return findAggregatedPageList(query, moFilter, startRow, limit);
- }
- List<ErpPickMaterialBill> headers = findHeaderList(query, moFilter, startRow, limit);
- if (headers.isEmpty()) {
- return headers;
- }
- List<String> billNos = new ArrayList<>();
- Map<String, ErpPickMaterialBill> billMap = new LinkedHashMap<>();
- for (ErpPickMaterialBill header : headers) {
- header.setEntryCount(0);
- header.setTotalAppQty(BigDecimal.ZERO);
- header.setTotalActualQty(BigDecimal.ZERO);
- billMap.put(header.getBillNo(), header);
- billNos.add(header.getBillNo());
- }
- for (ErpPickMaterialBill detail : findDetailListByBillNos(billNos, moFilter)) {
- ErpPickMaterialBill header = billMap.get(detail.getBillNo());
- if (header == null) {
- continue;
- }
- if (StringUtils.isBlank(header.getMoBillNo()) && StringUtils.isNotBlank(detail.getMoBillNo())) {
- header.setMoBillNo(detail.getMoBillNo());
- }
- if (StringUtils.isBlank(header.getWorkshopName()) && StringUtils.isNotBlank(detail.getWorkshopName())) {
- header.setWorkshopNumber(detail.getWorkshopNumber());
- header.setWorkshopName(detail.getWorkshopName());
- }
- header.setEntryCount(header.getEntryCount() + 1);
- if (detail.getAppQty() != null) {
- header.setTotalAppQty(header.getTotalAppQty().add(detail.getAppQty()));
- }
- if (detail.getActualQty() != null) {
- header.setTotalActualQty(header.getTotalActualQty().add(detail.getActualQty()));
- }
- }
- return new ArrayList<>(billMap.values());
- }
- /**
- * 生产订单下查:关联领料明细分录。
- */
- public List<ErpPickMaterialBill> findTraceListByMoBillNo(String moBillNo) {
- if (StringUtils.isBlank(moBillNo)) {
- return new ArrayList<>();
- }
- ErpPickMaterialBill query = new ErpPickMaterialBill();
- query.setMoBillNoQuery(moBillNo.trim());
- String moFilter = buildProductionOrderFilter(query);
- if (moFilter == null) {
- return new ArrayList<>();
- }
- return findDetailList(query, moFilter, 0, 2000);
- }
- /**
- * 明细页:指定领料单号的全部分录行。
- */
- public List<ErpPickMaterialBill> findDetailList(String billNo) {
- if (StringUtils.isBlank(billNo)) {
- return new ArrayList<>();
- }
- List<String> billNos = new ArrayList<>();
- billNos.add(billNo.trim());
- List<ErpPickMaterialBill> list = findDetailListByBillNos(billNos, null);
- try {
- mesErpPickKcService.increaseFromBillDetails(billNo.trim(), list);
- } catch (Exception ex) {
- logger.error("ERP pick bill detail MES kc sync failed, billNo={}", billNo, ex);
- }
- return list;
- }
- private List<ErpPickMaterialBill> findAggregatedPageList(ErpPickMaterialBill query, String moFilter,
- 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<String, ErpPickMaterialBill> billMap = new LinkedHashMap<>();
- while (billMap.size() < targetSize && fetchStart < maxFetchRows) {
- List<ErpPickMaterialBill> chunk = findDetailList(query, moFilter, fetchStart, fetchLimit);
- if (chunk.isEmpty()) {
- break;
- }
- for (ErpPickMaterialBill detail : chunk) {
- ErpPickMaterialBill header = billMap.get(detail.getBillNo());
- if (header == null) {
- header = toHeaderFromDetail(detail);
- header.setEntryCount(0);
- header.setTotalAppQty(BigDecimal.ZERO);
- header.setTotalActualQty(BigDecimal.ZERO);
- billMap.put(detail.getBillNo(), header);
- }
- header.setEntryCount(header.getEntryCount() + 1);
- if (detail.getAppQty() != null) {
- header.setTotalAppQty(header.getTotalAppQty().add(detail.getAppQty()));
- }
- if (detail.getActualQty() != null) {
- header.setTotalActualQty(header.getTotalActualQty().add(detail.getActualQty()));
- }
- }
- if (chunk.size() < fetchLimit) {
- break;
- }
- fetchStart += fetchLimit;
- }
- List<ErpPickMaterialBill> allBills = new ArrayList<>(billMap.values());
- if (startRow >= allBills.size()) {
- return new ArrayList<>();
- }
- return new ArrayList<>(allBills.subList(startRow, Math.min(startRow + limit, allBills.size())));
- }
- private List<ErpPickMaterialBill> findHeaderList(ErpPickMaterialBill query, String moFilter, int startRow, int limit) {
- JSONObject data = new JSONObject();
- data.put("FormId", FORM_ID);
- data.put("FieldKeys", HEADER_FIELD_KEYS);
- data.put("FilterString", buildFilterString(query, moFilter, 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 pick material bill header query: {}", data.toJSONString());
- return mapHeaderRows(erpClient.billQuery(data));
- }
- private List<ErpPickMaterialBill> findDetailList(ErpPickMaterialBill query, String moFilter, int startRow, int limit) {
- JSONObject data = new JSONObject();
- data.put("FormId", FORM_ID);
- data.put("FieldKeys", DETAIL_FIELD_KEYS);
- data.put("FilterString", buildFilterString(query, moFilter, true));
- data.put("OrderString", "FDate DESC,FBILLNO DESC,FEntity_FSeq");
- data.put("StartRow", Math.max(startRow, 0));
- data.put("Limit", Math.max(limit, 1));
- logger.info("ERP pick material bill detail query: {}", data.toJSONString());
- return mapDetailRows(erpClient.billQuery(data));
- }
- private List<ErpPickMaterialBill> findDetailListByBillNos(List<String> billNos, String moFilter) {
- 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, moFilter));
- data.put("OrderString", "FBILLNO DESC,FEntity_FSeq");
- data.put("StartRow", 0);
- data.put("Limit", 2000);
- logger.info("ERP pick material bill detail by billNos query: {}", data.toJSONString());
- return mapDetailRows(erpClient.billQuery(data));
- }
- private List<ErpPickMaterialBill> mapHeaderRows(JSONArray rows) {
- Map<String, ErpPickMaterialBill> billMap = new LinkedHashMap<>();
- for (int i = 0; i < rows.size(); i++) {
- ErpPickMaterialBill bill = toHeader(rows.getJSONObject(i));
- if (StringUtils.isBlank(bill.getBillNo())) {
- continue;
- }
- billMap.putIfAbsent(bill.getBillNo(), bill);
- }
- return new ArrayList<>(billMap.values());
- }
- private List<ErpPickMaterialBill> mapDetailRows(JSONArray rows) {
- List<ErpPickMaterialBill> list = new ArrayList<>();
- for (int i = 0; i < rows.size(); i++) {
- ErpPickMaterialBill bill = toDetail(rows.getJSONObject(i), i);
- if (StringUtils.isBlank(bill.getMoBillNo())) {
- continue;
- }
- list.add(bill);
- }
- return list;
- }
- private ErpPickMaterialBill toHeader(JSONObject row) {
- ErpPickMaterialBill bill = new ErpPickMaterialBill();
- bill.setFid(readString(row, "FID"));
- bill.setBillNo(readString(row, "FBILLNO", "FBillNo"));
- bill.setId(bill.getBillNo());
- fillHeaderFields(bill, row);
- return bill;
- }
- private ErpPickMaterialBill toHeaderFromDetail(ErpPickMaterialBill detail) {
- ErpPickMaterialBill bill = new ErpPickMaterialBill();
- bill.setFid(detail.getFid());
- bill.setBillNo(detail.getBillNo());
- bill.setId(detail.getBillNo());
- bill.setDocumentStatus(detail.getDocumentStatus());
- bill.setDocumentStatusName(detail.getDocumentStatusName());
- bill.setDate(detail.getDate());
- bill.setPrdOrgNumber(detail.getPrdOrgNumber());
- bill.setPrdOrgName(detail.getPrdOrgName());
- bill.setStockOrgNumber(detail.getStockOrgNumber());
- bill.setStockOrgName(detail.getStockOrgName());
- bill.setMoBillNo(detail.getMoBillNo());
- bill.setWorkshopNumber(detail.getWorkshopNumber());
- bill.setWorkshopName(detail.getWorkshopName());
- return bill;
- }
- private ErpPickMaterialBill toDetail(JSONObject row, int index) {
- ErpPickMaterialBill bill = new ErpPickMaterialBill();
- bill.setFid(readString(row, "FID"));
- bill.setBillNo(readString(row, "FBILLNO", "FBillNo"));
- bill.setEntryId(readString(row, "FEntity_FEntryID", "FEntity_FEntryId"));
- bill.setSeq(firstNonBlank(
- readString(row, "FEntity_FSeq"),
- readString(row, "FMoEntrySeq")));
- String rowId = bill.getBillNo();
- if (StringUtils.isNotBlank(bill.getEntryId())) {
- rowId = rowId + "-" + bill.getEntryId();
- } else if (index >= 0) {
- rowId = rowId + "-" + index;
- }
- bill.setId(rowId);
- fillHeaderFields(bill, row);
- bill.setWorkshopNumber(readString(row,
- "FEntryWorkShopId.FNUMBER", "FEntryWorkShopId.FNumber",
- "FWorkShopId.FNUMBER", "FWorkShopId.FNumber"));
- bill.setWorkshopName(readString(row,
- "FEntryWorkShopId.FNAME", "FEntryWorkShopId.FName",
- "FWorkShopId.FNAME", "FWorkShopId.FName"));
- bill.setMaterialNumber(readString(row,
- "FMaterialId.FNUMBER", "FMaterialId.FNumber",
- "FEntity_FMaterialId.FNUMBER", "FEntity_FMaterialId.FNumber"));
- bill.setMaterialName(readString(row,
- "FMaterialName",
- "FMaterialId.FNAME", "FMaterialId.FName",
- "FEntity_FMaterialId.FNAME", "FEntity_FMaterialId.FName"));
- bill.setUnitName(readString(row,
- "FUnitID.FNAME", "FUnitId.FNAME", "FUnitId.FName",
- "FEntity_FUnitId.FNAME", "FEntity_FUnitId.FName"));
- bill.setAppQty(firstBigDecimal(row, "FAppQty", "FEntity_FAppQty"));
- bill.setActualQty(firstBigDecimal(row, "FActualQty", "FEntity_FActualQty"));
- bill.setMoBillNo(readString(row, MO_BILL_NO_FIELD, "FEntity_FMoBillNo"));
- bill.setPpbomBillNo(readString(row, "FPPBomBillNo", "FEntity_FPPBomBillNo"));
- bill.setStockName(readString(row,
- "FStockId.FNAME", "FStockId.FName",
- "FEntity_FStockId.FNAME", "FEntity_FStockId.FName"));
- return bill;
- }
- private void fillHeaderFields(ErpPickMaterialBill bill, JSONObject row) {
- bill.setDocumentStatus(readString(row, "FDocumentStatus"));
- bill.setDocumentStatusName(documentStatusName(bill.getDocumentStatus()));
- bill.setDate(formatErpDate(readString(row, "FDate")));
- bill.setPrdOrgNumber(readString(row, "FPrdOrgId.FNUMBER", "FPrdOrgId.FNumber"));
- bill.setPrdOrgName(readString(row, "FPrdOrgId.FNAME", "FPrdOrgId.FName"));
- bill.setStockOrgNumber(readString(row, "FStockOrgId.FNUMBER", "FStockOrgId.FNumber"));
- bill.setStockOrgName(readString(row, "FStockOrgId.FNAME", "FStockOrgId.FName"));
- }
- /**
- * 按 MES 可见生产订单范围筛选;无关联生产订单的领料行不查。
- *
- * @return null 表示当前条件下无需请求 ERP(结果为空)
- */
- private String buildProductionOrderFilter(ErpPickMaterialBill query) {
- String moBillNo = query == null ? "" : StringUtils.trim(query.getMoBillNoQuery());
- if (StringUtils.isNotBlank(moBillNo)) {
- if (!erpProductionOrderService.isVisibleProductionOrder(moBillNo)) {
- logger.info("ERP pick material bill skip: production order not in MES scope, moBillNo={}", moBillNo);
- return null;
- }
- return MO_BILL_NO_FIELD + "='" + escape(moBillNo) + "'";
- }
- List<String> visibleBillNos = erpProductionOrderService.findVisibleBillNos(VISIBLE_MO_LIMIT);
- if (visibleBillNos.isEmpty()) {
- logger.info("ERP pick material bill skip: no visible production orders");
- return null;
- }
- return buildInFilter(MO_BILL_NO_FIELD, visibleBillNos);
- }
- private String buildFilterString(ErpPickMaterialBill query, String moFilter, boolean includeMaterialFilter) {
- String filter = appendAnd(buildCurrentOrgFilter(), moFilter);
- // 排除未关联生产订单的分录
- filter = appendAnd(filter, MO_BILL_NO_FIELD + "<>''");
- if (query == null) {
- return filter;
- }
- if (StringUtils.isNotBlank(query.getBillNo())) {
- filter = appendAnd(filter, "FBILLNO='" + escape(query.getBillNo()) + "'");
- }
- if (includeMaterialFilter && StringUtils.isNotBlank(query.getMaterialNumberQuery())) {
- filter = appendAnd(filter,
- MATERIAL_NUMBER_FIELD + " like '%" + escape(query.getMaterialNumberQuery()) + "%'");
- }
- return filter;
- }
- private String buildInFilterString(List<String> billNos, String moFilter) {
- StringBuilder builder = new StringBuilder(appendAnd(buildCurrentOrgFilter(), MO_BILL_NO_FIELD + "<>''"));
- if (StringUtils.isNotBlank(moFilter)) {
- builder.append(" AND ").append(moFilter);
- }
- 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 buildInFilter(String fieldName, List<String> values) {
- StringBuilder builder = new StringBuilder(fieldName).append(" IN(");
- for (int i = 0; i < values.size(); i++) {
- if (i > 0) {
- builder.append(",");
- }
- builder.append("'").append(escape(values.get(i))).append("'");
- }
- builder.append(")");
- return builder.toString();
- }
- private String buildCurrentOrgFilter() {
- if (!erpProperties.isFilterCurrentOrg()) {
- return "";
- }
- if (StringUtils.isNotBlank(erpProperties.getOrgNumber())) {
- return "FPrdOrgId.FNUMBER='" + escape(erpProperties.getOrgNumber()) + "'";
- }
- Long currentOrganizationId = erpClient.getCurrentOrganizationId();
- if (currentOrganizationId == null) {
- return "1=1";
- }
- return "FPrdOrgId=" + 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 firstNonBlank(String... values) {
- for (String value : values) {
- if (StringUtils.isNotBlank(value)) {
- return value.trim();
- }
- }
- return "";
- }
- private BigDecimal firstBigDecimal(JSONObject row, String... keys) {
- for (String key : keys) {
- BigDecimal value = row.getBigDecimal(key);
- if (value != null) {
- return ErpKingdeeParseUtils.normalizeQtyNullable(value);
- }
- }
- return null;
- }
- 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;
- }
- }
|