优化落库功能

This commit is contained in:
wuweihua
2026-10-26 23:23:22 +08:00
parent 8fba64e2d6
commit 2bae431e74
9 changed files with 370 additions and 204 deletions
@@ -224,7 +224,7 @@ public class TabOrderMonitorController extends BaseController
@PostMapping("/syncByFilter") @PostMapping("/syncByFilter")
public AjaxResult syncByFilter(@RequestBody TabOrderMonitorQuery query) public AjaxResult syncByFilter(@RequestBody TabOrderMonitorQuery query)
{ {
// 与列表查询同口径:有单号即可同步;无单号时必须客户 + 提货时间 // Upstream field overwrite only (no billing). Same filters as list query.
if (!hasListFilters(query)) if (!hasListFilters(query))
{ {
return error("orderIdentifiers or (customer and pickup time) required"); return error("orderIdentifiers or (customer and pickup time) required");
@@ -257,6 +257,7 @@ public class TabOrderMonitorController extends BaseController
return success(task); return success(task);
} }
/** Selected rows: overwrite local bill fields from Oracle; does not quote/rebill. */
@PreAuthorize("@ss.hasPermi('monitor:orderSource:sync') or @ss.hasPermi('price:orderBill:rebill')") @PreAuthorize("@ss.hasPermi('monitor:orderSource:sync') or @ss.hasPermi('price:orderBill:rebill')")
@Log(title = "order source sync selected", businessType = BusinessType.UPDATE) @Log(title = "order source sync selected", businessType = BusinessType.UPDATE)
@PostMapping("/sync") @PostMapping("/sync")
@@ -2,8 +2,8 @@ package com.bms.price.enums;
/** /**
* Snapshot of prc_order_bill.order_status (aligned with upstream TAB_ORDER). * Snapshot of prc_order_bill.order_status (aligned with upstream TAB_ORDER).
* Fee refund sets {@link #REFUNDED} (9 / 已退件). * Fee refund sets {@link #REFUNDED} (9 / 已退件); rebill/bill follows Oracle and does not pin 9/10.
* Upstream 9/10 trigger billType=REFUND when billing pulls Oracle. * Upstream 9/10 trigger billType=REFUND when billing-task pulls Oracle.
*/ */
public enum BillOrderStatus public enum BillOrderStatus
{ {
@@ -35,14 +35,15 @@ public class OracleOrderBillMapper
req.setUpstreamRow(row); req.setUpstreamRow(row);
req.setCustomerCode(firstStr(row, "CUSTOMER_CODE")); req.setCustomerCode(firstStr(row, "CUSTOMER_CODE"));
req.setCustomerName(firstStr(row, "CUSTOMER_NAME")); req.setCustomerName(firstStr(row, "CUSTOMER_NAME"));
// Map LINE_CODE/LINE_NAME to product_code/product_name; fallback PRODUCT_*. // Upstream product identity: TAB_ORDER.LINE_CODE / LINE_NAME only
String product = firstStr(row, "LINE_CODE", "PRODUCT_CODE"); // (BMS product_code_mapping applied later in quote / persist).
String product = firstStr(row, "LINE_CODE");
if (StringUtils.isEmpty(product)) if (StringUtils.isEmpty(product))
{ {
product = defaultProductCode; product = defaultProductCode;
} }
req.setProductCode(product); req.setProductCode(product);
req.setProductName(firstStr(row, "LINE_NAME", "PRODUCT_NAME")); req.setProductName(firstStr(row, "LINE_NAME"));
req.setCountryCode(firstStr(row, "COUNTRY")); req.setCountryCode(firstStr(row, "COUNTRY"));
String countryName = firstStr(row, "COUNTRY_NAME", "COUNTRY_EINGTH_NAME", "COUNTRY"); String countryName = firstStr(row, "COUNTRY_NAME", "COUNTRY_EINGTH_NAME", "COUNTRY");
req.setCountryName(countryName); req.setCountryName(countryName);
@@ -24,6 +24,16 @@ public interface IPrcOrderBillService
{ {
TmsPriceQuoteResult bill(TmsPriceBillRequest request); TmsPriceQuoteResult bill(TmsPriceBillRequest request);
/**
* Order-source sync only: overwrite local {@code prc_order_bill} + {@code prc_order_bill_ext}
* from an Oracle TAB_ORDER row. No quote / credit / fee / refund / AR validation.
* Rebilling remains on the order-list local-rebill path.
*
* @param upstreamRow Oracle TAB_ORDER full row
* @return billId
*/
String syncUpstreamOverwrite(java.util.Map<String, Object> upstreamRow);
/** /**
* Persist prc_order_bill and ext when billing cannot produce a quote. * Persist prc_order_bill and ext when billing cannot produce a quote.
* Status outside 0/1/2/3 does not write prc_income_bill. * Status outside 0/1/2/3 does not write prc_income_bill.
@@ -290,6 +290,19 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
task.setCustomerCode(billReq.getCustomerCode()); task.setCustomerCode(billReq.getCustomerCode());
try try
{ {
// No hard validate on customer/product/weight: always try bill (or soft-fail snapshot)
// so upstream fields still overwrite. Quote failures land in persistFailedSnapshot.
if (StringUtils.isEmpty(billReq.getOrderNo()))
{
if (StringUtils.isNotEmpty(billReq.getTransferNo()))
{
billReq.setOrderNo(billReq.getTransferNo().trim());
}
else if (StringUtils.isNotEmpty(billReq.getTrackingNo()))
{
billReq.setOrderNo(billReq.getTrackingNo().trim());
}
}
if (StringUtils.isEmpty(billReq.getOrderNo()) && StringUtils.isEmpty(billReq.getTransferNo())) if (StringUtils.isEmpty(billReq.getOrderNo()) && StringUtils.isEmpty(billReq.getTransferNo()))
{ {
throw new ServiceException("Oracle order mapped orderNo empty"); throw new ServiceException("Oracle order mapped orderNo empty");
@@ -308,18 +321,6 @@ public class PrcBillingTaskServiceImpl implements IPrcBillingTaskService
deleteTaskAfterBillSaved(task); deleteTaskAfterBillSaved(task);
return; return;
} }
if (StringUtils.isEmpty(billReq.getCustomerCode()))
{
throw new ServiceException("Oracle order mapped customerCode empty");
}
if (StringUtils.isEmpty(billReq.getProductCode()))
{
throw new ServiceException("Oracle order mapped productCode empty");
}
if (billReq.getWeight() == null || billReq.getWeight().signum() <= 0)
{
throw new ServiceException("Oracle order mapped weight invalid");
}
billWithDbRetry(taskId, billReq); billWithDbRetry(taskId, billReq);
} }
catch (RuntimeException e) catch (RuntimeException e)
@@ -46,6 +46,7 @@ import com.bms.customer.customerinfo.domain.BmsCustomer;
import com.bms.customer.customerinfo.mapper.BmsCustomerMapper; import com.bms.customer.customerinfo.mapper.BmsCustomerMapper;
import com.bms.system.mapper.SysUserMapper; import com.bms.system.mapper.SysUserMapper;
import com.bms.system.service.IBmsBizRuleService; import com.bms.system.service.IBmsBizRuleService;
import com.bms.system.service.IBmsProductCodeMappingService;
import com.bms.price.domain.PrcIncomeBill; import com.bms.price.domain.PrcIncomeBill;
import com.bms.price.domain.PrcIncomeBillFee; import com.bms.price.domain.PrcIncomeBillFee;
import com.bms.price.domain.PrcOrderBill; import com.bms.price.domain.PrcOrderBill;
@@ -88,6 +89,7 @@ import com.bms.price.mapper.PrcOrderBillMapper;
import com.bms.price.mapper.PrcPayBillFeeMapper; import com.bms.price.mapper.PrcPayBillFeeMapper;
import com.bms.price.mapper.PrcPayBillMapper; import com.bms.price.mapper.PrcPayBillMapper;
import com.bms.price.oracle.OracleOrderBillExtFiller; import com.bms.price.oracle.OracleOrderBillExtFiller;
import com.bms.price.oracle.OracleOrderBillMapper;
import com.bms.price.search.EsBizLogSupport; import com.bms.price.search.EsBizLogSupport;
import com.bms.price.search.EsLogType; import com.bms.price.search.EsLogType;
import com.bms.price.search.IBillSearchStore; import com.bms.price.search.IBillSearchStore;
@@ -129,6 +131,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
@Autowired @Autowired
private PrcOrderBillExtMapper orderBillExtMapper; private PrcOrderBillExtMapper orderBillExtMapper;
@Autowired
private OracleOrderBillMapper oracleOrderBillMapper;
@Autowired @Autowired
private PrcIncomeBillFeeMapper incomeBillFeeMapper; private PrcIncomeBillFeeMapper incomeBillFeeMapper;
@@ -171,6 +176,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
@Autowired @Autowired
private IBmsBizRuleService bizRuleService; private IBmsBizRuleService bizRuleService;
@Autowired
private IBmsProductCodeMappingService productCodeMappingService;
private static final int BATCH_ADD_FEE_MAX = 5000; private static final int BATCH_ADD_FEE_MAX = 5000;
private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20; private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20;
private static final int CHARGE_WEIGHT_IMPORT_MAX = 5000; private static final int CHARGE_WEIGHT_IMPORT_MAX = 5000;
@@ -1322,12 +1330,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
bill.setSurchargeAmount(surchargeTotal); bill.setSurchargeAmount(surchargeTotal);
bill.setIncomeTotal(incomeTotal); bill.setIncomeTotal(incomeTotal);
String refundStatus = resolveRefundStatus(fees);
PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId()); PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId());
if (incomeBill != null) if (incomeBill != null)
{ {
incomeBill.setSalesAmount(incomeTotal); incomeBill.setSalesAmount(incomeTotal);
incomeBill.setIncomeTotal(incomeTotal); incomeBill.setIncomeTotal(incomeTotal);
incomeBill.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency()); incomeBill.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency());
incomeBill.setRefundStatus(refundStatus);
incomeBillMapper.updatePrcIncomeBill(incomeBill); incomeBillMapper.updatePrcIncomeBill(incomeBill);
} }
@@ -1335,7 +1345,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
StringUtils.isEmpty(bill.getCustomerActiveStatus()) StringUtils.isEmpty(bill.getCustomerActiveStatus())
? BillCustomerActiveStatus.ACTIVE.getCode() : bill.getCustomerActiveStatus()); ? BillCustomerActiveStatus.ACTIVE.getCode() : bill.getCustomerActiveStatus());
bill.setCreditArrearsStatus(creditArrearsStatus); bill.setCreditArrearsStatus(creditArrearsStatus);
bill.setRefundStatus(resolveRefundStatus(fees)); bill.setRefundStatus(refundStatus);
orderBillMapper.updatePrcOrderBill(bill); orderBillMapper.updatePrcOrderBill(bill);
orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId())); orderBillMapper.refreshArBillStatusByBillIds(Arrays.asList(bill.getBillId()));
return toResult(bill, fees, payBillFeeMapper.selectByBillId(bill.getBillId())); return toResult(bill, fees, payBillFeeMapper.selectByBillId(bill.getBillId()));
@@ -1660,54 +1670,49 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
} }
bill.setBillKey(billKey); bill.setBillKey(billKey);
bill.setOrderNo(orderNo); bill.setOrderNo(orderNo);
// Upstream header overwrite (no validate); charge_weight kept via resolvePersistChargeWeight below.
applyUpstreamBillHeaderFields(bill, request);
Long customerId = request.getCustomerId(); Long customerId = request.getCustomerId();
if (customerId == null && result != null) if (customerId == null && result != null)
{ {
customerId = result.getCustomerId(); customerId = result.getCustomerId();
} }
bill.setCustomerId(customerId == null ? Long.valueOf(0L) : customerId); if (customerId == null || customerId.longValue() <= 0L)
String customerCode = request.getCustomerCode();
if (StringUtils.isEmpty(customerCode) && result != null)
{ {
customerCode = result.getCustomerCode(); customerId = resolveCustomerIdByCode(request.getCustomerCode(), bill.getCustomerId());
}
bill.setCustomerId(customerId == null ? Long.valueOf(0L) : customerId);
if (StringUtils.isEmpty(bill.getCustomerCode()) && result != null)
{
bill.setCustomerCode(result.getCustomerCode());
} }
bill.setCustomerCode(customerCode);
bill.setCustomerName(request.getCustomerName());
bill.setTrackingNo(request.getTrackingNo());
bill.setTransferNo(request.getTransferNo());
// Oracle 中文人员名 -> sys_user.nick_name,*_code 写 user_name(数据权限)
resolveOrderStaffFromSysUser(request);
bill.setBelongSaler(request.getBelongSaler());
bill.setBelongSalerCode(request.getBelongSalerCode());
bill.setSettlementMan(request.getSettlementMan());
bill.setSettlementManCode(request.getSettlementManCode());
bill.setServiceMan(request.getServiceMan());
bill.setServiceManCode(request.getServiceManCode());
String customerActiveStatus = result == null ? null : result.getCustomerActiveStatus(); String customerActiveStatus = result == null ? null : result.getCustomerActiveStatus();
if (StringUtils.isEmpty(customerActiveStatus)) if (StringUtils.isEmpty(customerActiveStatus))
{ {
customerActiveStatus = BillCustomerActiveStatus.ACTIVE.getCode(); customerActiveStatus = BillCustomerActiveStatus.ACTIVE.getCode();
} }
bill.setCustomerActiveStatus(customerActiveStatus); bill.setCustomerActiveStatus(customerActiveStatus);
bill.setPieces(request.getPieces()); // Product: quote result already resolved LINE_CODE via product_code_mapping.
bill.setActualWeight(request.getActualWeight()); if (result != null && StringUtils.isNotEmpty(result.getProductCode()))
bill.setReceiveWeight(request.getReceiveWeight()); {
bill.setVolume(request.getVolume());
bill.setVolumeWeight(request.getVolumeWeight());
bill.setPkgLength(request.getPkgLength());
bill.setPkgWidth(request.getPkgWidth());
bill.setPkgHeight(request.getPkgHeight());
bill.setShipperName(request.getShipperName());
bill.setConsigneeName(request.getConsigneeName());
bill.setDestPostcode(request.getDestPostcode());
bill.setWarehouseCode(request.getWarehouseCode());
bill.setOrderTime(request.getOrderTime());
applyUpstreamOrderStatus(bill, request);
bill.setOrderRemark(request.getOrderRemark());
bill.setProductCode(result.getProductCode()); bill.setProductCode(result.getProductCode());
}
else
{
applyProductCodeMapping(bill, request);
}
if (result != null && StringUtils.isNotEmpty(result.getProductName()))
{
bill.setProductName(result.getProductName()); bill.setProductName(result.getProductName());
}
if (StringUtils.isEmpty(bill.getCountryCode()) && result != null)
{
bill.setCountryCode(result.getCountryCode()); bill.setCountryCode(result.getCountryCode());
}
if (StringUtils.isEmpty(bill.getCountryName()) && result != null)
{
bill.setCountryName(result.getCountryName()); bill.setCountryName(result.getCountryName());
}
// weight = system/original; charge_weight = effective (may be manual override) // weight = system/original; charge_weight = effective (may be manual override)
bill.setWeight(weight); bill.setWeight(weight);
bill.setBizDate(bizDate); bill.setBizDate(bizDate);
@@ -2803,6 +2808,151 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
return parsed; return parsed;
} }
/**
* Order-source sync: full upstream field overwrite onto local bill + ext.
* Does not quote, occupy credit, touch fees, or validate AR / refund.
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String syncUpstreamOverwrite(java.util.Map<String, Object> upstreamRow)
{
if (upstreamRow == null || upstreamRow.isEmpty())
{
throw new ServiceException("Oracle order row empty");
}
TmsPriceBillRequest request = oracleOrderBillMapper.toBillRequest(upstreamRow, null);
if (request == null)
{
throw new ServiceException("Oracle order mapped empty");
}
request.setUpstreamRow(upstreamRow);
String orderNo = StringUtils.trim(request.getOrderNo());
String transferNo = StringUtils.trim(request.getTransferNo());
String trackingNo = StringUtils.trim(request.getTrackingNo());
if (StringUtils.isEmpty(orderNo))
{
orderNo = transferNo;
}
if (StringUtils.isEmpty(orderNo))
{
orderNo = trackingNo;
}
if (StringUtils.isEmpty(orderNo))
{
throw new ServiceException("Oracle order mapped orderNo empty");
}
request.setOrderNo(orderNo);
PrcOrderBill existed = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo);
if (existed == null && StringUtils.isNotEmpty(transferNo))
{
existed = orderBillMapper.selectPrcOrderBillByTransferNo(transferNo);
}
if (existed == null && StringUtils.isNotEmpty(trackingNo))
{
List<PrcOrderBill> byTrack = orderBillMapper.selectPrcOrderBillByTmsIdentifier(trackingNo);
if (byTrack != null && !byTrack.isEmpty())
{
existed = byTrack.get(0);
}
}
boolean creating = existed == null;
PrcOrderBill bill = creating ? new PrcOrderBill() : existed;
if (creating)
{
bill.setBillId(UUID.randomUUID().toString());
bill.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode());
bill.setRefundStatus(BillRefundStatus.NONE.getCode());
bill.setBillingFailStatus(Integer.valueOf(BillBillingFailStatus.SUCCESS.getCode()));
bill.setFreightRate(BigDecimal.ZERO);
bill.setBaseFreightRate(BigDecimal.ZERO);
bill.setRegistrationFee(BigDecimal.ZERO);
bill.setFreightAmount(BigDecimal.ZERO);
bill.setSurchargeAmount(BigDecimal.ZERO);
bill.setIncomeTotal(BigDecimal.ZERO);
bill.setPayTotal(BigDecimal.ZERO);
bill.setCurrency("CNY");
bill.setRoundMode("HALF_UP");
bill.setCustomerActiveStatus(BillCustomerActiveStatus.ACTIVE.getCode());
bill.setCreditArrearsStatus(BillCreditArrearsStatus.NORMAL.getCode());
}
bill.setBillKey(Md5Utils.hash(orderNo));
bill.setOrderNo(orderNo);
applyUpstreamBillHeaderFields(bill, request);
applyProductCodeMapping(bill, request);
Long customerId = resolveCustomerIdByCode(request.getCustomerCode(), bill.getCustomerId());
bill.setCustomerId(customerId == null ? Long.valueOf(0L) : customerId);
// System weight overwrite; charge_weight keeps existing manual override when present.
if (request.getWeight() != null)
{
bill.setWeight(request.getWeight());
if (creating || bill.getChargeWeight() == null)
{
bill.setChargeWeight(request.getWeight());
}
}
if (creating)
{
Date bizDate = request.getPickupDate() != null ? request.getPickupDate() : new Date();
bill.setBizDate(bizDate);
}
if (creating)
{
orderBillMapper.insertPrcOrderBill(bill);
}
else
{
orderBillMapper.updatePrcOrderBill(bill);
}
persistBillExt(bill, upstreamRow);
JSONObject req = new JSONObject();
req.put("action", "UPSTREAM_SYNC");
req.put("orderNo", orderNo);
req.put("transferNo", bill.getTransferNo());
req.put("billId", bill.getBillId());
req.put("orderStatus", bill.getOrderStatus());
req.put("creating", Boolean.valueOf(creating));
billSearchStore.save(EsLogType.BILL, bill.getBillId(), req.toJSONString(),
"{\"ok\":true,\"action\":\"UPSTREAM_SYNC\"}");
return bill.getBillId();
}
/** Best-effort customer id by code; keep previous id when lookup misses. */
private Long resolveCustomerIdByCode(String customerCode, Long previousId)
{
if (StringUtils.isEmpty(customerCode))
{
return previousId;
}
try
{
BmsCustomer query = new BmsCustomer();
query.setCustomerCode(customerCode.trim());
List<BmsCustomer> list = bmsCustomerMapper.selectBmsCustomerList(query);
if (list != null)
{
for (BmsCustomer row : list)
{
if (row != null && row.getCustomerId() != null
&& customerCode.trim().equalsIgnoreCase(StringUtils.trim(row.getCustomerCode())))
{
return row.getCustomerId();
}
}
}
}
catch (RuntimeException e)
{
log.warn("resolveCustomerIdByCode skip code={} msg={}", customerCode, e.getMessage());
}
return previousId;
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void persistFailedSnapshot(TmsPriceBillRequest request, int billingFailStatus, String errorMessage) public void persistFailedSnapshot(TmsPriceBillRequest request, int billingFailStatus, String errorMessage)
@@ -2839,41 +2989,19 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
} }
bill.setBillKey(Md5Utils.hash(orderNo)); bill.setBillKey(Md5Utils.hash(orderNo));
bill.setOrderNo(orderNo); bill.setOrderNo(orderNo);
bill.setCustomerId(request.getCustomerId() == null ? Long.valueOf(0L) : request.getCustomerId()); applyUpstreamBillHeaderFields(bill, request);
bill.setCustomerCode(request.getCustomerCode()); applyProductCodeMapping(bill, request);
bill.setCustomerName(request.getCustomerName()); Long customerId = request.getCustomerId();
bill.setTrackingNo(request.getTrackingNo()); if (customerId == null || customerId.longValue() <= 0L)
bill.setTransferNo(request.getTransferNo()); {
resolveOrderStaffFromSysUser(request); customerId = resolveCustomerIdByCode(request.getCustomerCode(), bill.getCustomerId());
bill.setBelongSaler(request.getBelongSaler()); }
bill.setBelongSalerCode(request.getBelongSalerCode()); bill.setCustomerId(customerId == null ? Long.valueOf(0L) : customerId);
bill.setSettlementMan(request.getSettlementMan());
bill.setSettlementManCode(request.getSettlementManCode());
bill.setServiceMan(request.getServiceMan());
bill.setServiceManCode(request.getServiceManCode());
if (StringUtils.isEmpty(bill.getCustomerActiveStatus())) if (StringUtils.isEmpty(bill.getCustomerActiveStatus()))
{ {
bill.setCustomerActiveStatus(BillCustomerActiveStatus.ACTIVE.getCode()); bill.setCustomerActiveStatus(BillCustomerActiveStatus.ACTIVE.getCode());
} }
bill.setPieces(request.getPieces()); // Soft-fail snapshot: overwrite system weight; do not touch charge_weight (keep current logic).
bill.setActualWeight(request.getActualWeight());
bill.setReceiveWeight(request.getReceiveWeight());
bill.setVolume(request.getVolume());
bill.setVolumeWeight(request.getVolumeWeight());
bill.setPkgLength(request.getPkgLength());
bill.setPkgWidth(request.getPkgWidth());
bill.setPkgHeight(request.getPkgHeight());
bill.setShipperName(request.getShipperName());
bill.setConsigneeName(request.getConsigneeName());
bill.setDestPostcode(request.getDestPostcode());
bill.setWarehouseCode(request.getWarehouseCode());
bill.setOrderTime(request.getOrderTime());
applyUpstreamOrderStatus(bill, request);
bill.setOrderRemark(request.getOrderRemark());
bill.setProductCode(request.getProductCode());
bill.setProductName(request.getProductName());
bill.setCountryCode(request.getCountryCode());
bill.setCountryName(request.getCountryName());
bill.setWeight(request.getWeight()); bill.setWeight(request.getWeight());
Date bizDate = StringUtils.isEmpty(request.getBizDate()) ? new Date() : resolveBizDate(request.getBizDate()); Date bizDate = StringUtils.isEmpty(request.getBizDate()) ? new Date() : resolveBizDate(request.getBizDate());
bill.setBizDate(bizDate); bill.setBizDate(bizDate);
@@ -2920,22 +3048,18 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
} }
/** /**
* Locked by AR bill: write fail status + upstream ORDER_STATUS only. * Locked by AR bill: overwrite upstream header + ext; keep amounts / fees / charge_weight.
* Amounts / income fees stay untouched.
*/ */
private void persistFailedSnapshotWhenLocked(PrcOrderBill bill, TmsPriceBillRequest request, private void persistFailedSnapshotWhenLocked(PrcOrderBill bill, TmsPriceBillRequest request,
int billingFailStatus, String errorMessage) int billingFailStatus, String errorMessage)
{ {
applyUpstreamOrderStatus(bill, request);
if (request != null) if (request != null)
{ {
if (StringUtils.isNotEmpty(request.getTrackingNo())) applyUpstreamBillHeaderFields(bill, request);
applyProductCodeMapping(bill, request);
if (request.getWeight() != null)
{ {
bill.setTrackingNo(request.getTrackingNo()); bill.setWeight(request.getWeight());
}
if (StringUtils.isNotEmpty(request.getTransferNo()))
{
bill.setTransferNo(request.getTransferNo());
} }
} }
applyBillingFailText(bill, billingFailStatus, errorMessage); applyBillingFailText(bill, billingFailStatus, errorMessage);
@@ -2946,19 +3070,92 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
} }
} }
/** Prefer request status fields; never clear existing order_status with null. */ /**
* Overwrite local bill header fields from upstream request (no validation).
* Does not set weight / charge_weight / bizDate / amounts / customerId.
*/
private void applyUpstreamBillHeaderFields(PrcOrderBill bill, TmsPriceBillRequest request)
{
if (bill == null || request == null)
{
return;
}
resolveOrderStaffFromSysUser(request);
bill.setCustomerCode(request.getCustomerCode());
bill.setCustomerName(request.getCustomerName());
bill.setTrackingNo(request.getTrackingNo());
bill.setTransferNo(request.getTransferNo());
bill.setBelongSaler(request.getBelongSaler());
bill.setBelongSalerCode(request.getBelongSalerCode());
bill.setSettlementMan(request.getSettlementMan());
bill.setSettlementManCode(request.getSettlementManCode());
bill.setServiceMan(request.getServiceMan());
bill.setServiceManCode(request.getServiceManCode());
bill.setPieces(request.getPieces());
bill.setActualWeight(request.getActualWeight());
bill.setReceiveWeight(request.getReceiveWeight());
bill.setVolume(request.getVolume());
bill.setVolumeWeight(request.getVolumeWeight());
bill.setPkgLength(request.getPkgLength());
bill.setPkgWidth(request.getPkgWidth());
bill.setPkgHeight(request.getPkgHeight());
bill.setShipperName(request.getShipperName());
bill.setConsigneeName(request.getConsigneeName());
bill.setDestPostcode(request.getDestPostcode());
bill.setWarehouseCode(request.getWarehouseCode());
bill.setOrderTime(request.getOrderTime());
applyUpstreamOrderStatus(bill, request);
bill.setOrderRemark(request.getOrderRemark());
// Source = TAB_ORDER.LINE_CODE / LINE_NAME (mapping applied by caller after quote / sync).
bill.setProductCode(request.getProductCode());
bill.setProductName(request.getProductName());
bill.setCountryCode(request.getCountryCode());
bill.setCountryName(request.getCountryName());
}
/**
* LINE_CODE (on request/bill) -> BMS product via product_code_mapping when configured.
*/
private void applyProductCodeMapping(PrcOrderBill bill, TmsPriceBillRequest request)
{
if (bill == null || productCodeMappingService == null)
{
return;
}
String upstream = bill.getProductCode();
if (StringUtils.isEmpty(upstream) && request != null)
{
upstream = request.getProductCode();
}
if (StringUtils.isEmpty(upstream))
{
return;
}
String mapped = productCodeMappingService.resolveBmsProductCode(upstream.trim());
if (StringUtils.isEmpty(mapped))
{
return;
}
mapped = mapped.trim();
bill.setProductCode(mapped);
if (request != null)
{
request.setProductCode(mapped);
}
}
/**
* Prefer request status fields; never clear existing order_status with null.
* Do not pin order_status to 9/10 when refundStatus=REFUNDED — rebill / upstream
* bill must follow Oracle ORDER_STATUS. Actual fee refund still sets 9 in {@link #refund}.
*/
private static void applyUpstreamOrderStatus(PrcOrderBill bill, TmsPriceBillRequest request) private static void applyUpstreamOrderStatus(PrcOrderBill bill, TmsPriceBillRequest request)
{ {
if (bill == null || request == null) if (bill == null || request == null)
{ {
return; return;
} }
// Fee refund marks order_status=9 (已退件); do not let Oracle sync overwrite it. if (request.getOrderStatus() != null)
if (BillRefundStatus.REFUNDED.getCode().equalsIgnoreCase(bill.getRefundStatus()))
{
bill.setOrderStatus(BillOrderStatus.REFUNDED.getCodeBoxed());
}
else if (request.getOrderStatus() != null)
{ {
bill.setOrderStatus(request.getOrderStatus()); bill.setOrderStatus(request.getOrderStatus());
} }
@@ -22,21 +22,17 @@ import org.springframework.stereotype.Component;
import com.bms.common.core.redis.RedisCache; import com.bms.common.core.redis.RedisCache;
import com.bms.common.utils.StringUtils; import com.bms.common.utils.StringUtils;
import com.bms.price.config.BillingProperties; import com.bms.price.config.BillingProperties;
import com.bms.price.domain.PrcBillingTask;
import com.bms.price.domain.PrcBillingTaskRetryTask; import com.bms.price.domain.PrcBillingTaskRetryTask;
import com.bms.price.domain.vo.BillingTaskSubmitRequest;
import com.bms.price.domain.vo.TabOrderMonitorQuery; import com.bms.price.domain.vo.TabOrderMonitorQuery;
import com.bms.price.domain.vo.TabOrderMonitorVo; import com.bms.price.domain.vo.TabOrderMonitorVo;
import com.bms.price.enums.BillOrderStatus; import com.bms.price.oracle.OracleUpstreamOrderService;
import com.bms.price.enums.BillTaskSuccessStatus;
import com.bms.price.enums.BillTaskType;
import com.bms.price.oracle.TabOrderMonitorService; import com.bms.price.oracle.TabOrderMonitorService;
import com.bms.price.search.IBillSearchStore; import com.bms.price.search.IBillSearchStore;
import com.bms.price.search.TabOrderSyncEsLog; import com.bms.price.search.TabOrderSyncEsLog;
import com.bms.price.service.IPrcBillingTaskService; import com.bms.price.service.IPrcOrderBillService;
/** /**
* Page Oracle TAB_ORDER then multi-thread submitAndProcess each row. * Page Oracle TAB_ORDER then multi-thread overwrite local bill fields (no billing).
*/ */
@Component @Component
public class TabOrderSyncAsyncRunner public class TabOrderSyncAsyncRunner
@@ -59,7 +55,10 @@ public class TabOrderSyncAsyncRunner
@Autowired @Autowired
@Lazy @Lazy
private IPrcBillingTaskService billingTaskService; private IPrcOrderBillService prcOrderBillService;
@Autowired
private OracleUpstreamOrderService oracleUpstreamOrderService;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
@@ -201,8 +200,7 @@ public class TabOrderSyncAsyncRunner
} }
/** /**
* Sync or refund selected rows. * Sync selected rows (upstream field overwrite only; billType ignored).
* @param billType optional PENDING/REBILL/REFUND; null = auto by orderStatus / bill existence
*/ */
public Map<String, Object> syncRowsParallel(List<TabOrderMonitorVo> rows, String billType) public Map<String, Object> syncRowsParallel(List<TabOrderMonitorVo> rows, String billType)
{ {
@@ -220,8 +218,8 @@ public class TabOrderSyncAsyncRunner
} }
/** /**
* Sync or refund selected rows carrying the upstream Oracle full row (skip re-query). * Sync selected rows carrying the upstream Oracle full row when present (skip re-query).
* @param billType optional PENDING/REBILL/REFUND; null = auto by orderStatus / bill existence * Overwrites local bill fields only; does not bill / refund.
*/ */
public Map<String, Object> syncRowsParallelRows(List<com.bms.price.oracle.TabOrderRow> rows, String billType) public Map<String, Object> syncRowsParallelRows(List<com.bms.price.oracle.TabOrderRow> rows, String billType)
{ {
@@ -234,7 +232,6 @@ public class TabOrderSyncAsyncRunner
data.put("details", details); data.put("details", details);
return data; return data;
} }
final String fixedBillType = StringUtils.isEmpty(billType) ? null : billType.trim();
int threads = Math.min(clampThreads(billingProperties == null ? null : billingProperties.getOrderSourceSyncThreads()), int threads = Math.min(clampThreads(billingProperties == null ? null : billingProperties.getOrderSourceSyncThreads()),
Math.max(1, rows.size())); Math.max(1, rows.size()));
ExecutorService pool = newWorkerPool(threads, "order-source-sel"); ExecutorService pool = newWorkerPool(threads, "order-source-sel");
@@ -253,26 +250,16 @@ public class TabOrderSyncAsyncRunner
item.put("transferOrderId", vo == null ? null : vo.getTransferOrderId()); item.put("transferOrderId", vo == null ? null : vo.getTransferOrderId());
try try
{ {
PrcBillingTask task = billingTaskService.submitAndProcess( String billId = overwriteOne(one);
toSubmit(vo, fixedBillType), one == null ? null : one.getRaw()); item.put("successStatus", "SUCCESS");
item.put("successStatus", task == null ? null : task.getSuccessStatus()); item.put("billId", billId);
item.put("errorMessage", task == null ? null : task.getErrorMessage());
item.put("billType", task == null ? fixedBillType : task.getBillType());
if (task != null && BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()))
{
ok.incrementAndGet(); ok.incrementAndGet();
} }
else
{
fail.incrementAndGet();
}
}
catch (RuntimeException e) catch (RuntimeException e)
{ {
fail.incrementAndGet(); fail.incrementAndGet();
item.put("successStatus", "ERROR"); item.put("successStatus", "ERROR");
item.put("errorMessage", e.getMessage()); item.put("errorMessage", e.getMessage());
item.put("billType", fixedBillType);
} }
return item; return item;
}); });
@@ -315,15 +302,8 @@ public class TabOrderSyncAsyncRunner
TabOrderMonitorVo vo = row == null ? null : row.getVo(); TabOrderMonitorVo vo = row == null ? null : row.getVo();
try try
{ {
PrcBillingTask task = billingTaskService.submitAndProcess( overwriteOne(row);
toSubmit(vo), row == null ? null : row.getRaw()); return true;
boolean success = task != null
&& BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus());
if (!success)
{
collectFailDetail(failDetails, vo, task, null);
}
return success;
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
@@ -331,15 +311,34 @@ public class TabOrderSyncAsyncRunner
vo == null ? null : vo.getOrderId(), vo == null ? null : vo.getOrderId(),
vo == null ? null : vo.getTransferOrderId(), vo == null ? null : vo.getTransferOrderId(),
e.getMessage()); e.getMessage());
collectFailDetail(failDetails, vo, null, e.getMessage()); collectFailDetail(failDetails, vo, e.getMessage());
return false; return false;
} }
} }
/** Resolve Oracle row then overwrite local bill + ext (no billing). */
private String overwriteOne(com.bms.price.oracle.TabOrderRow row)
{
Map<String, Object> raw = row == null ? null : row.getRaw();
TabOrderMonitorVo vo = row == null ? null : row.getVo();
if (raw == null || raw.isEmpty())
{
String transferNo = vo == null ? null : vo.getTransferOrderId();
String orderNo = vo == null ? null : vo.getOrderId();
String trackingNo = vo == null ? null : vo.getOrgOrderId();
raw = oracleUpstreamOrderService.findMainOrder(transferNo, orderNo, trackingNo);
}
if (raw == null || raw.isEmpty())
{
throw new com.bms.common.exception.ServiceException("Oracle main order not found");
}
return prcOrderBillService.syncUpstreamOverwrite(raw);
}
private static final int FAIL_DETAIL_MAX = 200; private static final int FAIL_DETAIL_MAX = 200;
private static void collectFailDetail(List<Map<String, Object>> failDetails, TabOrderMonitorVo row, private static void collectFailDetail(List<Map<String, Object>> failDetails, TabOrderMonitorVo row,
PrcBillingTask task, String exceptionMessage) String exceptionMessage)
{ {
if (failDetails == null) if (failDetails == null)
{ {
@@ -348,22 +347,7 @@ public class TabOrderSyncAsyncRunner
Map<String, Object> item = new LinkedHashMap<String, Object>(); Map<String, Object> item = new LinkedHashMap<String, Object>();
item.put("orderId", row == null ? null : row.getOrderId()); item.put("orderId", row == null ? null : row.getOrderId());
item.put("transferOrderId", row == null ? null : row.getTransferOrderId()); item.put("transferOrderId", row == null ? null : row.getTransferOrderId());
String err = exceptionMessage; item.put("errorMessage", StringUtils.isEmpty(exceptionMessage) ? "sync failed" : exceptionMessage);
if (task != null)
{
item.put("successStatus", task.getSuccessStatus());
item.put("billType", task.getBillType());
item.put("taskId", task.getTaskId());
if (StringUtils.isEmpty(err))
{
err = task.getErrorMessage();
}
}
if (StringUtils.isEmpty(err))
{
err = "sync failed";
}
item.put("errorMessage", err);
synchronized (failDetails) synchronized (failDetails)
{ {
if (failDetails.size() >= FAIL_DETAIL_MAX) if (failDetails.size() >= FAIL_DETAIL_MAX)
@@ -490,32 +474,4 @@ public class TabOrderSyncAsyncRunner
return n; return n;
} }
public static BillingTaskSubmitRequest toSubmit(TabOrderMonitorVo row)
{
return toSubmit(row, null);
}
public static BillingTaskSubmitRequest toSubmit(TabOrderMonitorVo row, String billType)
{
if (row == null)
{
throw new com.bms.common.exception.ServiceException("order row required");
}
BillingTaskSubmitRequest req = new BillingTaskSubmitRequest();
req.setTransferNo(row.getTransferOrderId());
req.setTrackingNo(row.getOrgOrderId());
req.setOrderNo(row.getOrderId());
req.setCustomerCode(row.getCustomerCode());
req.setBillTime(row.getPickupDate());
if (StringUtils.isNotEmpty(billType))
{
req.setBillType(billType.trim());
}
else if (BillOrderStatus.requiresFeeRefund(row.getOrderStatus()))
{
// Upstream 已退件(9) / 已取消(10) -> fee refund task
req.setBillType(BillTaskType.REFUND.getCode());
}
return req;
}
} }
@@ -24,7 +24,7 @@
type="info" type="info"
:closable="false" :closable="false"
show-icon show-icon
title="按当前筛选分页查询上游订单并逐单同步计费,避免一次性加载全量。" title="按当前筛选分页查询上游订单并逐单覆盖本地字段(不计费;计费请到订单列表重新计费)。"
/> />
</el-form> </el-form>
@@ -44,13 +44,13 @@
/ 失败 {{ progress.fail || 0 }} / 失败 {{ progress.fail || 0 }}
</div> </div>
<div v-if="progress.error" class="filter-retry-progress__error">{{ progress.error }}</div> <div v-if="progress.error" class="filter-retry-progress__error">{{ progress.error }}</div>
<p v-if="loading" class="filter-retry-progress__hint">请勿关闭页面,大量订单同步计费可能需要一些时间</p> <p v-if="loading" class="filter-retry-progress__hint">请勿关闭页面,大量订单同步落库可能需要一些时间</p>
</div> </div>
<template #footer> <template #footer>
<template v-if="!loading && !finished"> <template v-if="!loading && !finished">
<el-button @click="visible = false">取消</el-button> <el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submit">开始同步计费</el-button> <el-button type="primary" @click="submit">开始同步落库</el-button>
</template> </template>
<template v-else-if="finished"> <template v-else-if="finished">
<el-button type="primary" @click="visible = false">关闭</el-button> <el-button type="primary" @click="visible = false">关闭</el-button>
@@ -101,9 +101,9 @@ const filterText = computed(() => {
}) })
const dialogTitle = computed(() => { const dialogTitle = computed(() => {
if (loading.value) return '异步同步计费进行中' if (loading.value) return '异步同步落库进行中'
if (finished.value) return '异步同步计费完成' if (finished.value) return '异步同步落库完成'
return '条件批量同步计费' return '条件批量同步落库'
}) })
const progressPercent = computed(() => Math.min(100, Math.max(0, Number(progress.value?.percent) || 0))) const progressPercent = computed(() => Math.min(100, Math.max(0, Number(progress.value?.percent) || 0)))
@@ -124,7 +124,7 @@ function mapProgressMessage(message, data) {
if (m === 'no tasks') return '无匹配订单' if (m === 'no tasks') return '无匹配订单'
if (m === 'failed') return '执行失败' if (m === 'failed') return '执行失败'
if (m.startsWith('processing ')) { if (m.startsWith('processing ')) {
return '正在同步计费 ' + m.slice('processing '.length) return '正在同步落库 ' + m.slice('processing '.length)
} }
if (m.startsWith('done ')) { if (m.startsWith('done ')) {
return '完成:成功 ' + (data?.ok || 0) + ' / 失败 ' + (data?.fail || 0) return '完成:成功 ' + (data?.ok || 0) + ' / 失败 ' + (data?.fail || 0)
@@ -236,7 +236,7 @@ function submit(skipConfirm) {
status: 'FAIL', status: 'FAIL',
percent: 100, percent: 100,
message: 'failed', message: 'failed',
error: progress.value?.error || '批量同步计费失败' error: progress.value?.error || '批量同步落库失败'
} }
} }
} }
@@ -250,7 +250,7 @@ function submit(skipConfirm) {
return return
} }
proxy.$modal.confirm( proxy.$modal.confirm(
'确认按当前筛选分页查询并异步同步计费?', '确认按当前筛选分页查询并异步同步落库?',
'二次确认' '二次确认'
).then(() => start()).catch(() => {}) ).then(() => start()).catch(() => {})
} }
@@ -278,7 +278,7 @@ function poll(asyncTaskId) {
return return
} }
if (st === 'FAIL') { if (st === 'FAIL') {
proxy.$modal.msgError(data.error || data.message || '批量同步计费失败') proxy.$modal.msgError(data.error || data.message || '批量同步落库失败')
reject(new Error(data.error || 'fail')) reject(new Error(data.error || 'fail'))
return return
} }
@@ -36,7 +36,7 @@
icon="Refresh" icon="Refresh"
v-hasPermi="['monitor:orderSource:sync']" v-hasPermi="['monitor:orderSource:sync']"
@click="openFilterSync()" @click="openFilterSync()"
>同步计费</el-button> >同步落库</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
@@ -128,7 +128,7 @@
:loading="syncingKey === rowKey(row)" :loading="syncingKey === rowKey(row)"
v-hasPermi="['monitor:orderSource:sync']" v-hasPermi="['monitor:orderSource:sync']"
@click="handleSyncOne(row)" @click="handleSyncOne(row)"
>同步计费</el-button> >同步落库</el-button>
</template> --> </template> -->
</basic-table> </basic-table>
@@ -273,10 +273,10 @@ const syncProgressDegree = computed(() => Math.round(((syncFloat.value?.percent
const syncProgressText = computed(() => (syncFloat.value?.percent || 0) + '%') const syncProgressText = computed(() => (syncFloat.value?.percent || 0) + '%')
const syncFloatTitle = computed(() => { const syncFloatTitle = computed(() => {
const st = (syncFloat.value?.status || '').toUpperCase() const st = (syncFloat.value?.status || '').toUpperCase()
if (syncFloat.value?.loading) return '异步同步计费中' if (syncFloat.value?.loading) return '异步同步落库中'
if (st === 'SUCCESS') return '同步计费完成' if (st === 'SUCCESS') return '同步落库完成'
if (st === 'FAIL') return '同步计费失败' if (st === 'FAIL') return '同步落库失败'
return '异步同步计费' return '异步同步落库'
}) })
const syncFloatSummary = computed(() => { const syncFloatSummary = computed(() => {
const p = syncFloat.value || {} const p = syncFloat.value || {}
@@ -466,7 +466,7 @@ function openFilterSync() {
const hasTime = !!q.beginTime && !!q.endTime const hasTime = !!q.beginTime && !!q.endTime
// 与查询同口径:有单号即可同步;无单号时必须客户+提货时间 // 与查询同口径:有单号即可同步;无单号时必须客户+提货时间
if (!hasIdentifier && !(hasCustomer && hasTime)) { if (!hasIdentifier && !(hasCustomer && hasTime)) {
ElMessage.warning('请输入单号,或选择客户和提货时间后再同步计费') ElMessage.warning('请输入单号,或选择客户和提货时间后再同步落库')
return return
} }
if (identifierCount > MAX_ORDER_IDENTIFIERS) { if (identifierCount > MAX_ORDER_IDENTIFIERS) {
@@ -474,8 +474,8 @@ function openFilterSync() {
return return
} }
const tip = hasIdentifier && !(hasCustomer && hasTime) const tip = hasIdentifier && !(hasCustomer && hasTime)
? `将按 ${identifierCount} 个单号查询匹配订单并异步同步计费,确认继续?` ? `将按 ${identifierCount} 个单号查询匹配订单并异步覆盖上游字段(不计费),确认继续?`
: '确认按当前筛选分页查询并异步同步计费?' : '确认按当前筛选分页查询并异步覆盖上游字段(不计费)?'
proxy.$modal.confirm(tip, '二次确认').then(() => { proxy.$modal.confirm(tip, '二次确认').then(() => {
syncDialogRef.value?.open(q, true) syncDialogRef.value?.open(q, true)
}).catch(() => {}) }).catch(() => {})
@@ -509,7 +509,7 @@ function toSyncRow(row) {
function handleSyncOne(row) { function handleSyncOne(row) {
proxy.$modal.confirm('确认同步计费该订单?', '二次确认').then(() => { proxy.$modal.confirm('确认同步落库该订单(仅覆盖上游字段,不计费)?', '二次确认').then(() => {
syncingKey.value = rowKey(row) syncingKey.value = rowKey(row)
return syncOrderSourceSelected([toSyncRow(row)]) return syncOrderSourceSelected([toSyncRow(row)])
}).then(res => { }).then(res => {
@@ -544,7 +544,7 @@ function handleCheckProducts() {
function handleBatchSync(selection) { function handleBatchSync(selection) {
if (!selection || !selection.length) return if (!selection || !selection.length) return
proxy.$modal.confirm('确认同步选中的 ' + selection.length + ' 条订单?', '二次确认').then(() => { proxy.$modal.confirm('确认同步选中的 ' + selection.length + ' 条订单(仅覆盖上游字段,不计费)?', '二次确认').then(() => {
batchLoading.value = true batchLoading.value = true
return syncOrderSourceSelected(selection.map(toSyncRow)) return syncOrderSourceSelected(selection.map(toSyncRow))
}).then(res => { }).then(res => {