package com.vincent.rsf.server.manager.service; import com.vincent.rsf.framework.exception.CoolException; import com.vincent.rsf.server.api.controller.erp.params.SyncOrdersItem; import com.vincent.rsf.server.manager.entity.Matnr; import com.vincent.rsf.server.manager.enums.CusItemSyncMode; import com.vincent.rsf.server.system.constant.GlobalConfigCode; import com.vincent.rsf.server.system.entity.Config; import com.vincent.rsf.server.system.service.ConfigService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * cus_barcode_sync_view 与 man_matnr 同步(云仓通知单、无订单组托等共用)。 * 分支仅由 {@link GlobalConfigCode#CUS_ITEM_SYNC_MODE}(sys_config.val)解析为 {@link CusItemSyncMode} 决定。 * 副库读无事务({@link CusBarcodeSyncViewQueryService} {@code NOT_SUPPORTED}),主库写 {@link CusBarcodeSyncMatnrApplyService} {@code REQUIRES_NEW}。 */ @Service @Slf4j public class CusBarcodeSyncMatnrService { @Autowired private CusBarcodeSyncViewQueryService cusBarcodeSyncViewQueryService; @Autowired private MatnrService matnrService; @Autowired private ConfigService configService; @Autowired private CusBarcodeSyncMatnrApplyService cusBarcodeSyncMatnrApplyService; /** 与云仓通知单同步:按明细 matnr 查视图并写入/更新物料 */ public void syncFromOrderItems(List orderItems, Long loginUserId) { if (orderItems == null || orderItems.isEmpty()) { return; } List matnrCodes = orderItems.stream() .map(SyncOrdersItem::getMatnr) .filter(StringUtils::isNotBlank) .map(String::trim) .distinct() .collect(Collectors.toList()); if (matnrCodes.isEmpty()) { return; } syncAlignedWithBarcodeView(matnrCodes, buildOrderItemByMatnrCode(orderItems), loginUserId); } /** 无订单组托等:仅按物料号(与视图 barcode 一致)走同一套视图策略 */ public void syncFromMatnrCodes(Collection matnrCodes, Long loginUserId) { if (matnrCodes == null || matnrCodes.isEmpty()) { return; } List codes = matnrCodes.stream() .map(StringUtils::trimToNull) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (codes.isEmpty()) { return; } syncAlignedWithBarcodeView(codes, Collections.emptyMap(), loginUserId); } private static Map buildOrderItemByMatnrCode(List orderItems) { Map map = new LinkedHashMap<>(); if (orderItems == null) { return map; } for (SyncOrdersItem item : orderItems) { if (StringUtils.isBlank(item.getMatnr())) { continue; } String key = StringUtils.trimToNull(item.getMatnr().trim()); if (key == null) { continue; } map.putIfAbsent(key, item); } return map; } /** 与分支解析共用同一份 Config(来自 ConfigService 全局缓存,避免每次同步打库) */ private CusItemSyncConfigSnapshot resolveCusItemSyncConfig() { Config c = configService.getCachedOrLoad(GlobalConfigCode.CUS_ITEM_SYNC_MODE); if (c == null) { return new CusItemSyncConfigSnapshot(CusItemSyncMode.NONE, null); } return new CusItemSyncConfigSnapshot(CusItemSyncMode.fromConfig(c.getVal()), c.getVal()); } private static String formatCfgVal(String rawVal) { if (rawVal == null) { return "(无配置)"; } String t = rawVal.trim(); return t.isEmpty() ? "(空)" : t; } private void syncAlignedWithBarcodeView(List matnrCodes, Map orderItemByCode, Long loginUserId) { CusItemSyncConfigSnapshot cfg = resolveCusItemSyncConfig(); log.info("[cus_barcode_sync] 同步入口 CUS_ITEM_SYNC_MODE.val={} resolved={} ds={} matnrCount={} matnrs=[{}]", formatCfgVal(cfg.rawVal), cfg.mode, cusBarcodeSyncViewQueryService.effectiveDataSourceLabel(), matnrCodes.size(), joinCodesForLog(matnrCodes)); if (cfg.mode == CusItemSyncMode.NONE) { syncMatnrNonForceFromView(cfg, matnrCodes, orderItemByCode, loginUserId); return; } List> viewItems = cusBarcodeSyncViewQueryService.listByItemNos(matnrCodes); log.info("[cus_barcode_sync] FORCE_VIEW 分支 CUS_ITEM_SYNC_MODE.val={} viewRows={} viewBarcodes=[{}]", formatCfgVal(cfg.rawVal), viewItems == null ? 0 : viewItems.size(), summarizeViewBarcodes(viewItems)); for (String code : matnrCodes) { boolean hit = CusBarcodeSyncViewQueryService.orderMatnrHitsBarcodeView(code, viewItems); log.info("[cus_barcode_sync] 强制校验 code={} viewHit={}", code, hit); if (!hit) { throw new CoolException("物料未在视图 cus_barcode_sync_view 中:" + code); } } cusBarcodeSyncMatnrApplyService.applyFromViewRows(viewItems, orderItemByCode, loginUserId); } private void syncMatnrNonForceFromView(CusItemSyncConfigSnapshot cfg, List matnrCodes, Map orderItemByCode, Long loginUserId) { List> viewItems = null; try { viewItems = cusBarcodeSyncViewQueryService.listByItemNos(matnrCodes); } catch (Exception ex) { log.warn("[cus_barcode_sync] 查询 cus_barcode_sync_view 异常,将仅按物料表校验", ex); } log.info("[cus_barcode_sync] NONE 分支 CUS_ITEM_SYNC_MODE.val={} 副库视图 rows={} barcodesInView=[{}]", formatCfgVal(cfg.rawVal), viewItems == null ? 0 : viewItems.size(), summarizeViewBarcodes(viewItems)); if (viewItems != null && !viewItems.isEmpty()) { try { cusBarcodeSyncMatnrApplyService.applyFromViewRows(viewItems, orderItemByCode, loginUserId); } catch (Exception ex) { log.warn("[cus_barcode_sync] 批量 applyFromViewRows 失败", ex); } } // 视图有条码但本地仍无:按行补建档 for (String code : matnrCodes) { Matnr m = findLocalMatnrForOrderCode(code); if (m != null) { log.info("[cus_barcode_sync] 校验通过 code={} localMatnrId={}", code, m.getId()); continue; } boolean viewHit = viewItems != null && CusBarcodeSyncViewQueryService.orderMatnrHitsBarcodeView(code, viewItems); log.info("[cus_barcode_sync] 本地无记录 code={} viewHit={} viewRowCount={}", code, viewHit, viewItems == null ? 0 : viewItems.size()); if (viewHit && viewItems != null) { List> rowsForCode = viewItems.stream() .filter(r -> CusBarcodeSyncViewQueryService.rowMatchesOrderMatnr(code, Objects.toString(r.get("barcode"), null))) .collect(Collectors.toList()); if (!rowsForCode.isEmpty()) { try { log.info("[cus_barcode_sync] 按条码补档 apply rows={} code={}", rowsForCode.size(), code); cusBarcodeSyncMatnrApplyService.applyFromViewRows(rowsForCode, orderItemByCode, loginUserId); } catch (Exception ex) { log.warn("[cus_barcode_sync] 按视图补全物料失败 code={}", code, ex); } m = findLocalMatnrForOrderCode(code); } } if (m == null) { log.warn("[cus_barcode_sync] 仍无法落地 man_matnr code={} viewHit={} viewSample=[{}]", code, viewHit, summarizeViewBarcodes(viewItems)); throw new CoolException("物料不存在:" + code); } } } private static String joinCodesForLog(List matnrCodes) { if (matnrCodes == null || matnrCodes.isEmpty()) { return ""; } String s = String.join(",", matnrCodes); return s.length() > 1200 ? s.substring(0, 1200) + "..." : s; } private static String summarizeViewBarcodes(List> viewItems) { if (viewItems == null || viewItems.isEmpty()) { return ""; } String s = viewItems.stream() .map(r -> Objects.toString(r.get("barcode"), "")) .filter(StringUtils::isNotBlank) .distinct() .collect(Collectors.joining(",")); return s.length() > 1200 ? s.substring(0, 1200) + "..." : s; } private Matnr findLocalMatnrForOrderCode(String orderMatnr) { String t = StringUtils.trimToNull(orderMatnr); if (t == null) { return null; } return matnrService.getOneByCodeAndBatch(t, ""); } private static final class CusItemSyncConfigSnapshot { final CusItemSyncMode mode; /** sys_config.CUS_ITEM_SYNC_MODE 的 val,无配置行为 null */ final String rawVal; CusItemSyncConfigSnapshot(CusItemSyncMode mode, String rawVal) { this.mode = mode; this.rawVal = rawVal; } } }