Merge branch 'test' into feauture-20261104
# Conflicts: # bms-front/src/views/price/billing/components/BillDetail.vue
This commit is contained in:
+5
@@ -450,6 +450,11 @@ public class BmsCustomerLogServiceImpl implements IBmsCustomerLogService
|
||||
realChanges.add(c);
|
||||
}
|
||||
}
|
||||
// Rebind/update with no field diff: skip empty UPDATE noise (full package save upsert)
|
||||
if (existing != null && realChanges.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
saveLog(bind.getCustomerId(), customer == null ? null : customer.getCustomerCode(),
|
||||
customer == null ? null : customer.getCustomerName(),
|
||||
BmsCustomerLog.MODULE_PRICE_SET, newSet.getPriceSetName(),
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 空运提单费用明细 srv_bill_air_fee。
|
||||
*/
|
||||
@Data
|
||||
public class SrvBillAirFee
|
||||
{
|
||||
private Long id;
|
||||
|
||||
private String billCode;
|
||||
|
||||
/** 字典 bms_fee_type */
|
||||
private String feeType;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
/** 字典 bms_currency */
|
||||
private String currency;
|
||||
|
||||
private String remark;
|
||||
|
||||
private String createBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
|
||||
private String updateBy;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date updateTime;
|
||||
|
||||
/** 0未删除 1已删除 */
|
||||
private String delFlag;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 空运提单费用操作日志。
|
||||
*/
|
||||
@Data
|
||||
public class SrvBillAirFeeLog
|
||||
{
|
||||
public static final String INSERT = "1";
|
||||
|
||||
public static final String UPDATE = "2";
|
||||
|
||||
public static final String DELETE = "3";
|
||||
|
||||
private Long logId;
|
||||
|
||||
private String billCode;
|
||||
|
||||
private Long feeId;
|
||||
|
||||
private String operationType;
|
||||
|
||||
private String summary;
|
||||
|
||||
private String changeContent;
|
||||
|
||||
private String createBy;
|
||||
|
||||
private String createByName;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime;
|
||||
|
||||
private List<FieldChange> changes;
|
||||
|
||||
@Data
|
||||
public static class FieldChange
|
||||
{
|
||||
private String field;
|
||||
|
||||
private String fieldLabel;
|
||||
|
||||
private String oldValue;
|
||||
|
||||
private String newValue;
|
||||
|
||||
public FieldChange()
|
||||
{
|
||||
}
|
||||
|
||||
public FieldChange(String field, String fieldLabel, String oldValue, String newValue)
|
||||
{
|
||||
this.field = field;
|
||||
this.fieldLabel = fieldLabel;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 保存提单费用时只带有变动的行。未出现的费用保持原样。
|
||||
*/
|
||||
@Data
|
||||
public class SrvBillAirFeeSave
|
||||
{
|
||||
private String billCode;
|
||||
|
||||
private List<SrvBillAirFee> inserts = new ArrayList<>();
|
||||
|
||||
private List<SrvBillAirFee> updates = new ArrayList<>();
|
||||
|
||||
private List<Long> deletes = new ArrayList<>();
|
||||
}
|
||||
@@ -20,6 +20,11 @@ public class BillingProperties
|
||||
private int consumerThreads = 8;
|
||||
/** Concurrent threads for monitor order-source sync */
|
||||
private int orderSourceSyncThreads = 8;
|
||||
/**
|
||||
* Concurrent threads for order-list local rebill (2~24).
|
||||
* Keep below Druid maxActive with headroom for other traffic.
|
||||
*/
|
||||
private int localRebillThreads = 16;
|
||||
/** Night window auto-retry of prc_billing_task (all statuses) */
|
||||
private NightRetry nightRetry = new NightRetry();
|
||||
private Oracle oracle = new Oracle();
|
||||
@@ -84,6 +89,16 @@ public class BillingProperties
|
||||
this.orderSourceSyncThreads = orderSourceSyncThreads;
|
||||
}
|
||||
|
||||
public int getLocalRebillThreads()
|
||||
{
|
||||
return localRebillThreads;
|
||||
}
|
||||
|
||||
public void setLocalRebillThreads(int localRebillThreads)
|
||||
{
|
||||
this.localRebillThreads = localRebillThreads;
|
||||
}
|
||||
|
||||
public NightRetry getNightRetry()
|
||||
{
|
||||
return nightRetry;
|
||||
|
||||
+4
@@ -40,6 +40,10 @@ public class BatchAddIncomeBillFeeRequest implements Serializable
|
||||
private String beginBizDate;
|
||||
private String endBizDate;
|
||||
|
||||
/** Receive/pickup time range (pickup_date), inclusive. */
|
||||
private String beginPickupDate;
|
||||
private String endPickupDate;
|
||||
|
||||
/** Surcharge config id (prc_surcharge.sc_id). */
|
||||
@NotNull(message = "scId required")
|
||||
private Long scId;
|
||||
|
||||
+3
@@ -25,4 +25,7 @@ public class ChargeWeightTemplateRequest implements Serializable
|
||||
private List<Integer> orderStatuses;
|
||||
private String beginBizDate;
|
||||
private String endBizDate;
|
||||
/** Receive/pickup time range (pickup_date), inclusive. */
|
||||
private String beginPickupDate;
|
||||
private String endPickupDate;
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class PrcBillingBillListExportRow implements Serializable
|
||||
@Excel(name = "计费时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date bizDate;
|
||||
@Excel(name = "提货时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@Excel(name = "收货时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date pickupDate;
|
||||
@Excel(name = "销售员", width = 12)
|
||||
|
||||
@@ -31,4 +31,7 @@ public class RecalcBillPriceRequest implements Serializable
|
||||
private List<Integer> billingFailStatuses;
|
||||
private String beginBizDate;
|
||||
private String endBizDate;
|
||||
/** Receive/pickup time range (pickup_date), inclusive. */
|
||||
private String beginPickupDate;
|
||||
private String endPickupDate;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ public interface PrcPriceSetDetailMapper
|
||||
|
||||
public int countByPsvId(Long psvId);
|
||||
|
||||
/** Count detail rows including soft-deleted (for deleted-version tab). */
|
||||
public int countByPsvIdAll(Long psvId);
|
||||
|
||||
public int copyDetailsFromVersion(@Param("srcPsvId") Long srcPsvId, @Param("newPsvId") Long newPsvId);
|
||||
|
||||
/** Copy version effective time, expire time and status onto catalog detail rows. */
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
|
||||
/**
|
||||
* 空运提单费用及操作日志。
|
||||
*/
|
||||
public interface SrvBillAirFeeMapper
|
||||
{
|
||||
List<SrvBillAirFee> selectByBillCode(String billCode);
|
||||
|
||||
/** 只取这一票提单上仍有效的费用,供修改和删除前核对。 */
|
||||
SrvBillAirFee selectLive(@Param("id") Long id, @Param("billCode") String billCode);
|
||||
|
||||
int insertFee(SrvBillAirFee fee);
|
||||
|
||||
int updateFee(SrvBillAirFee fee);
|
||||
|
||||
int deleteFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
int insertLog(SrvBillAirFeeLog log);
|
||||
|
||||
List<SrvBillAirFeeLog> selectLogs(String billCode);
|
||||
}
|
||||
@@ -1232,14 +1232,10 @@ public class TabOrderMonitorService
|
||||
query.setOrderStatuses(cleaned.isEmpty() ? new ArrayList<Integer>(DEFAULT_ORDER_STATUSES) : cleaned);
|
||||
}
|
||||
|
||||
/** Empty or anything other than 0/1 falls back to 1(已收货). */
|
||||
/** Always force BL_PICKUP=1; UI no longer exposes this filter. */
|
||||
private static void normalizeBlPickup(TabOrderMonitorQuery query)
|
||||
{
|
||||
Integer blPickup = query.getBlPickup();
|
||||
if (blPickup == null || (blPickup.intValue() != 0 && blPickup.intValue() != 1))
|
||||
{
|
||||
query.setBlPickup(Integer.valueOf(1));
|
||||
}
|
||||
query.setBlPickup(Integer.valueOf(1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,11 @@ package com.bms.price.search;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -148,6 +152,36 @@ public class EsBillSearchStore implements IBillSearchStore
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, null, null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, psvId, priceKind, null, null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, psvId, priceKind, productCode, updateTime, null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, String updateTimePrefix, int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, psvId, priceKind, productCode, updateTime, updateTimePrefix,
|
||||
null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, String updateTimePrefix, String countryKeyword,
|
||||
int pageNum, int pageSize)
|
||||
{
|
||||
BillSearchPage page = new BillSearchPage();
|
||||
if (pageNum < 1)
|
||||
@@ -171,7 +205,8 @@ public class EsBillSearchStore implements IBillSearchStore
|
||||
sortItem.put("updatedAt.keyword", sortOpt);
|
||||
sort.add(sortItem);
|
||||
body.put("sort", sort);
|
||||
body.put("query", buildQuery(logType, bizId, status));
|
||||
body.put("query", buildQuery(logType, bizId, status, psvId, priceKind, productCode, updateTime,
|
||||
updateTimePrefix, countryKeyword));
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> entity = new HttpEntity<String>(body.toJSONString(), headers);
|
||||
@@ -217,6 +252,108 @@ public class EsBillSearchStore implements IBillSearchStore
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriceChangeGroupPage searchPriceChangeGroups(Long psvId, String priceKind, String status,
|
||||
int pageNum, int pageSize)
|
||||
{
|
||||
PriceChangeGroupPage page = new PriceChangeGroupPage();
|
||||
if (psvId == null)
|
||||
{
|
||||
return page;
|
||||
}
|
||||
if (pageNum < 1)
|
||||
{
|
||||
pageNum = 1;
|
||||
}
|
||||
if (pageSize < 1)
|
||||
{
|
||||
pageSize = 10;
|
||||
}
|
||||
List<BillSyncSnapshot> all = loadAllPriceChanges(psvId, priceKind, status, 10000);
|
||||
Map<String, PriceChangeGroup> grouped = new LinkedHashMap<String, PriceChangeGroup>();
|
||||
for (BillSyncSnapshot snap : all)
|
||||
{
|
||||
if (snap == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
JSONObject req = parseRequestObject(snap.getRequestJson());
|
||||
JSONObject keys = req == null ? null : req.getJSONObject("bizKeys");
|
||||
String productCode = keys == null ? "" : StringUtils.nvl(keys.getString("productCode"), "");
|
||||
String time = StringUtils.nvl(snap.getUpdateTime(), "");
|
||||
String key = productCode + "|" + time;
|
||||
PriceChangeGroup group = grouped.get(key);
|
||||
if (group == null)
|
||||
{
|
||||
group = new PriceChangeGroup();
|
||||
group.setProductCode(StringUtils.isEmpty(productCode) ? "-" : productCode);
|
||||
group.setUpdateTime(time);
|
||||
group.setAction(req == null ? null : req.getString("action"));
|
||||
group.setOperator(req == null ? null : req.getString("operator"));
|
||||
group.setStatus(snap.getStatus());
|
||||
group.setChangeCount(0);
|
||||
grouped.put(key, group);
|
||||
}
|
||||
group.setChangeCount(group.getChangeCount() + 1);
|
||||
if (EsBizLogSupport.STATUS_FAIL.equals(snap.getStatus()))
|
||||
{
|
||||
group.setStatus(EsBizLogSupport.STATUS_FAIL);
|
||||
}
|
||||
}
|
||||
List<PriceChangeGroup> list = new ArrayList<PriceChangeGroup>(grouped.values());
|
||||
page.setTotal(list.size());
|
||||
int from = (pageNum - 1) * pageSize;
|
||||
if (from >= list.size())
|
||||
{
|
||||
return page;
|
||||
}
|
||||
int to = Math.min(from + pageSize, list.size());
|
||||
page.setRows(new ArrayList<PriceChangeGroup>(list.subList(from, to)));
|
||||
return page;
|
||||
}
|
||||
|
||||
private List<BillSyncSnapshot> loadAllPriceChanges(Long psvId, String priceKind, String status, int max)
|
||||
{
|
||||
List<BillSyncSnapshot> all = new ArrayList<BillSyncSnapshot>();
|
||||
int batch = 1000;
|
||||
int pageNum = 1;
|
||||
while (all.size() < max)
|
||||
{
|
||||
int size = Math.min(batch, max - all.size());
|
||||
BillSearchPage chunk = search(EsLogType.PRICE_CHANGE, null, status, psvId, priceKind, null, null,
|
||||
pageNum, size);
|
||||
List<BillSyncSnapshot> rows = chunk.getRows();
|
||||
if (rows == null || rows.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
all.addAll(rows);
|
||||
if (rows.size() < size || all.size() >= chunk.getTotal())
|
||||
{
|
||||
break;
|
||||
}
|
||||
pageNum++;
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
private JSONObject parseRequestObject(String requestJson)
|
||||
{
|
||||
if (StringUtils.isEmpty(requestJson))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
Object parsed = JSON.parse(requestJson);
|
||||
return parsed instanceof JSONObject ? (JSONObject) parsed : null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private BillSyncSnapshot toSnapshot(JSONObject source, String id)
|
||||
{
|
||||
if (source == null)
|
||||
@@ -249,12 +386,19 @@ public class EsBillSearchStore implements IBillSearchStore
|
||||
return snap;
|
||||
}
|
||||
|
||||
private JSONObject buildQuery(String logType, String bizId, String status)
|
||||
private JSONObject buildQuery(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, String updateTimePrefix, String countryKeyword)
|
||||
{
|
||||
JSONArray filters = new JSONArray();
|
||||
addTerm(filters, "logType", logType);
|
||||
addWildcard(filters, "bizId", bizId);
|
||||
addTerm(filters, "status", status);
|
||||
addPsvIdFilter(filters, psvId);
|
||||
addTerm(filters, "request.priceKind", priceKind);
|
||||
addProductCodeFilter(filters, productCode);
|
||||
addCountryKeywordFilter(filters, countryKeyword);
|
||||
addUpdateTimeFilter(filters, updateTime);
|
||||
addUpdateTimePrefixFilter(filters, updateTimePrefix);
|
||||
if (filters.isEmpty())
|
||||
{
|
||||
JSONObject matchAll = new JSONObject();
|
||||
@@ -268,6 +412,195 @@ public class EsBillSearchStore implements IBillSearchStore
|
||||
return query;
|
||||
}
|
||||
|
||||
private void addProductCodeFilter(JSONArray filters, String productCode)
|
||||
{
|
||||
if (StringUtils.isEmpty(productCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String code = productCode.trim();
|
||||
JSONArray should = new JSONArray();
|
||||
JSONObject termKw = new JSONObject();
|
||||
termKw.put("request.bizKeys.productCode.keyword", code);
|
||||
JSONObject wrapKw = new JSONObject();
|
||||
wrapKw.put("term", termKw);
|
||||
should.add(wrapKw);
|
||||
|
||||
JSONObject term = new JSONObject();
|
||||
term.put("request.bizKeys.productCode", code);
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("term", term);
|
||||
should.add(wrap);
|
||||
|
||||
JSONObject inner = new JSONObject();
|
||||
inner.put("should", should);
|
||||
inner.put("minimum_should_match", Integer.valueOf(1));
|
||||
JSONObject boolWrap = new JSONObject();
|
||||
boolWrap.put("bool", inner);
|
||||
filters.add(boolWrap);
|
||||
}
|
||||
|
||||
/** Match country code / name (exact or contains). */
|
||||
private void addCountryKeywordFilter(JSONArray filters, String countryKeyword)
|
||||
{
|
||||
if (StringUtils.isEmpty(countryKeyword))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String kw = countryKeyword.trim();
|
||||
String wild = "*" + escapeWildcard(kw) + "*";
|
||||
JSONArray should = new JSONArray();
|
||||
addShouldTerm(should, "request.bizKeys.countryCode.keyword", kw);
|
||||
addShouldTerm(should, "request.bizKeys.countryCode", kw);
|
||||
addShouldWildcard(should, "request.bizKeys.countryCode.keyword", wild);
|
||||
addShouldWildcard(should, "request.bizKeys.countryCode", wild);
|
||||
addShouldWildcard(should, "request.bizKeys.countryName.keyword", wild);
|
||||
addShouldWildcard(should, "request.bizKeys.countryName", wild);
|
||||
JSONObject matchName = new JSONObject();
|
||||
JSONObject matchField = new JSONObject();
|
||||
matchField.put("query", kw);
|
||||
matchName.put("request.bizKeys.countryName", matchField);
|
||||
JSONObject matchWrap = new JSONObject();
|
||||
matchWrap.put("match", matchName);
|
||||
should.add(matchWrap);
|
||||
|
||||
JSONObject inner = new JSONObject();
|
||||
inner.put("should", should);
|
||||
inner.put("minimum_should_match", Integer.valueOf(1));
|
||||
JSONObject boolWrap = new JSONObject();
|
||||
boolWrap.put("bool", inner);
|
||||
filters.add(boolWrap);
|
||||
}
|
||||
|
||||
private void addShouldTerm(JSONArray should, String field, String value)
|
||||
{
|
||||
JSONObject term = new JSONObject();
|
||||
term.put(field, value);
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("term", term);
|
||||
should.add(wrap);
|
||||
}
|
||||
|
||||
private void addShouldWildcard(JSONArray should, String field, String value)
|
||||
{
|
||||
JSONObject wild = new JSONObject();
|
||||
wild.put(field, value);
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("wildcard", wild);
|
||||
should.add(wrap);
|
||||
}
|
||||
|
||||
private static String escapeWildcard(String raw)
|
||||
{
|
||||
if (raw == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return raw.replace("\\", "\\\\").replace("*", "\\*").replace("?", "\\?");
|
||||
}
|
||||
|
||||
private void addUpdateTimeFilter(JSONArray filters, String updateTime)
|
||||
{
|
||||
if (StringUtils.isEmpty(updateTime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String time = updateTime.trim();
|
||||
JSONArray should = new JSONArray();
|
||||
JSONObject t1 = new JSONObject();
|
||||
t1.put("updateTime.keyword", time);
|
||||
JSONObject w1 = new JSONObject();
|
||||
w1.put("term", t1);
|
||||
should.add(w1);
|
||||
|
||||
JSONObject t2 = new JSONObject();
|
||||
t2.put("updatedAt.keyword", time);
|
||||
JSONObject w2 = new JSONObject();
|
||||
w2.put("term", t2);
|
||||
should.add(w2);
|
||||
|
||||
JSONObject t3 = new JSONObject();
|
||||
t3.put("updateTime", time);
|
||||
JSONObject w3 = new JSONObject();
|
||||
w3.put("term", t3);
|
||||
should.add(w3);
|
||||
|
||||
JSONObject t4 = new JSONObject();
|
||||
t4.put("updatedAt", time);
|
||||
JSONObject w4 = new JSONObject();
|
||||
w4.put("term", t4);
|
||||
should.add(w4);
|
||||
|
||||
JSONObject inner = new JSONObject();
|
||||
inner.put("should", should);
|
||||
inner.put("minimum_should_match", Integer.valueOf(1));
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("bool", inner);
|
||||
filters.add(wrap);
|
||||
}
|
||||
|
||||
/** Match write-time by minute prefix, e.g. 2026-11-05 13:27. */
|
||||
private void addUpdateTimePrefixFilter(JSONArray filters, String updateTimePrefix)
|
||||
{
|
||||
if (StringUtils.isEmpty(updateTimePrefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
String prefix = escapeWildcard(updateTimePrefix.trim()) + "*";
|
||||
JSONArray should = new JSONArray();
|
||||
for (String field : new String[] { "updateTime.keyword", "updatedAt.keyword", "updateTime", "updatedAt" })
|
||||
{
|
||||
JSONObject opts = new JSONObject();
|
||||
opts.put("value", prefix);
|
||||
opts.put("case_insensitive", Boolean.TRUE);
|
||||
JSONObject wildcard = new JSONObject();
|
||||
wildcard.put(field, opts);
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("wildcard", wildcard);
|
||||
should.add(wrap);
|
||||
}
|
||||
JSONObject inner = new JSONObject();
|
||||
inner.put("should", should);
|
||||
inner.put("minimum_should_match", Integer.valueOf(1));
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("bool", inner);
|
||||
filters.add(wrap);
|
||||
}
|
||||
|
||||
/** Prefer numeric term; also accept keyword/string mapping of bizKeys.psvId. */
|
||||
private void addPsvIdFilter(JSONArray filters, Long psvId)
|
||||
{
|
||||
if (psvId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
JSONArray should = new JSONArray();
|
||||
JSONObject termNum = new JSONObject();
|
||||
termNum.put("request.bizKeys.psvId", psvId);
|
||||
JSONObject wrapNum = new JSONObject();
|
||||
wrapNum.put("term", termNum);
|
||||
should.add(wrapNum);
|
||||
|
||||
JSONObject termKw = new JSONObject();
|
||||
termKw.put("request.bizKeys.psvId.keyword", String.valueOf(psvId));
|
||||
JSONObject wrapKw = new JSONObject();
|
||||
wrapKw.put("term", termKw);
|
||||
should.add(wrapKw);
|
||||
|
||||
JSONObject termStr = new JSONObject();
|
||||
termStr.put("request.bizKeys.psvId", String.valueOf(psvId));
|
||||
JSONObject wrapStr = new JSONObject();
|
||||
wrapStr.put("term", termStr);
|
||||
should.add(wrapStr);
|
||||
|
||||
JSONObject inner = new JSONObject();
|
||||
inner.put("should", should);
|
||||
inner.put("minimum_should_match", Integer.valueOf(1));
|
||||
JSONObject wrap = new JSONObject();
|
||||
wrap.put("bool", inner);
|
||||
filters.add(wrap);
|
||||
}
|
||||
|
||||
private void addTerm(JSONArray filters, String field, String value)
|
||||
{
|
||||
if (StringUtils.isEmpty(value))
|
||||
@@ -297,11 +630,6 @@ public class EsBillSearchStore implements IBillSearchStore
|
||||
filters.add(wrap);
|
||||
}
|
||||
|
||||
private String escapeWildcard(String value)
|
||||
{
|
||||
return value.replace("\\", "\\\\").replace("*", "\\*").replace("?", "\\?");
|
||||
}
|
||||
|
||||
private long readTotal(Object total)
|
||||
{
|
||||
if (total instanceof JSONObject)
|
||||
|
||||
@@ -17,4 +17,48 @@ public interface IBillSearchStore
|
||||
BillSyncSnapshot getByDocId(String docId);
|
||||
|
||||
BillSearchPage search(String logType, String bizId, String status, int pageNum, int pageSize);
|
||||
|
||||
/**
|
||||
* Search with optional sales-version / priceKind filters (request.bizKeys.psvId, request.priceKind).
|
||||
*/
|
||||
default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, psvId, priceKind, null, null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search with optional productCode + updateTime (exact) / updateTimePrefix (e.g. yyyy-MM-dd HH:mm) filters.
|
||||
*/
|
||||
default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, psvId, priceKind, productCode, updateTime, null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, String updateTimePrefix, int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, psvId, priceKind, productCode, updateTime, updateTimePrefix,
|
||||
null, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search with optional countryKeyword (matches bizKeys.countryCode / countryName).
|
||||
*/
|
||||
default BillSearchPage search(String logType, String bizId, String status, Long psvId, String priceKind,
|
||||
String productCode, String updateTime, String updateTimePrefix, String countryKeyword,
|
||||
int pageNum, int pageSize)
|
||||
{
|
||||
return search(logType, bizId, status, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group PRICE_CHANGE logs by productCode + updateTime for one package version.
|
||||
*/
|
||||
default PriceChangeGroupPage searchPriceChangeGroups(Long psvId, String priceKind, String status,
|
||||
int pageNum, int pageSize)
|
||||
{
|
||||
return new PriceChangeGroupPage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,33 @@ public final class PriceChangeEsLog
|
||||
public static final String ACTION_DELETE = "DELETE";
|
||||
public static final String ACTION_IMPORT = "IMPORT";
|
||||
|
||||
/** Async import workers have no SecurityContext; pass operName via this override. */
|
||||
private static final ThreadLocal<String> OPERATOR_OVERRIDE = new ThreadLocal<String>();
|
||||
|
||||
private PriceChangeEsLog()
|
||||
{
|
||||
}
|
||||
|
||||
public static void beginOperator(String operName)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(operName))
|
||||
{
|
||||
OPERATOR_OVERRIDE.set(operName.trim());
|
||||
}
|
||||
}
|
||||
|
||||
public static void endOperator()
|
||||
{
|
||||
OPERATOR_OVERRIDE.remove();
|
||||
}
|
||||
|
||||
public static String operator()
|
||||
{
|
||||
String override = OPERATOR_OVERRIDE.get();
|
||||
if (StringUtils.isNotEmpty(override))
|
||||
{
|
||||
return override;
|
||||
}
|
||||
try
|
||||
{
|
||||
String name = SecurityUtils.getUsername();
|
||||
@@ -131,11 +152,31 @@ public final class PriceChangeEsLog
|
||||
keys.put("countryCode", d.getCountryCode());
|
||||
keys.put("countryName", d.getCountryName());
|
||||
keys.put("zoneCode", d.getZoneCode());
|
||||
keys.put("zoneName", d.getZoneName());
|
||||
keys.put("weightMin", d.getWeightMin());
|
||||
keys.put("weightMax", d.getWeightMax());
|
||||
keys.put("weightLabel", weightBandOf(d));
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** Display weight band for sales detail logs (e.g. 0<W≤0.5). */
|
||||
public static String weightBandOf(PrcPriceSetDetail d)
|
||||
{
|
||||
if (d == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (StringUtils.isNotEmpty(d.getWeightLabel()))
|
||||
{
|
||||
return d.getWeightLabel();
|
||||
}
|
||||
if (d.getWeightMin() == null && d.getWeightMax() == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return stringify(d.getWeightMin()) + "<W\u2264" + stringify(d.getWeightMax());
|
||||
}
|
||||
|
||||
public static void save(IBillSearchStore store, String bizId, String priceKind, String action,
|
||||
Map<String, Object> bizKeys, List<Map<String, String>> changes)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.bms.price.search;
|
||||
|
||||
/**
|
||||
* Price-change log group key: productCode + write time.
|
||||
*/
|
||||
public class PriceChangeGroup
|
||||
{
|
||||
private String productCode;
|
||||
private String updateTime;
|
||||
private String action;
|
||||
private String operator;
|
||||
private String status;
|
||||
private int changeCount;
|
||||
|
||||
public String getProductCode()
|
||||
{
|
||||
return productCode;
|
||||
}
|
||||
|
||||
public void setProductCode(String productCode)
|
||||
{
|
||||
this.productCode = productCode;
|
||||
}
|
||||
|
||||
public String getUpdateTime()
|
||||
{
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(String updateTime)
|
||||
{
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
public String getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getOperator()
|
||||
{
|
||||
return operator;
|
||||
}
|
||||
|
||||
public void setOperator(String operator)
|
||||
{
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getChangeCount()
|
||||
{
|
||||
return changeCount;
|
||||
}
|
||||
|
||||
public void setChangeCount(int changeCount)
|
||||
{
|
||||
this.changeCount = changeCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.bms.price.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Paginated product+time groups of price-change logs. */
|
||||
public class PriceChangeGroupPage
|
||||
{
|
||||
private long total;
|
||||
private List<PriceChangeGroup> rows = new ArrayList<PriceChangeGroup>();
|
||||
|
||||
public long getTotal()
|
||||
{
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total)
|
||||
{
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<PriceChangeGroup> getRows()
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<PriceChangeGroup> rows)
|
||||
{
|
||||
this.rows = rows == null ? new ArrayList<PriceChangeGroup>() : rows;
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ public interface IPrcOrderBillService
|
||||
|
||||
/**
|
||||
* Async local rebill by selected billIds or list filters. Returns taskId for progress poll.
|
||||
* Large sets: only bill_id loaded, then processed in pages of 500 with parallel workers.
|
||||
* Large sets: only bill_id loaded, then processed by continuous parallel workers.
|
||||
*/
|
||||
String submitLocalRebill(LocalRebillRequest request);
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||
|
||||
/**
|
||||
* 空运提单费用维护。
|
||||
*/
|
||||
public interface ISrvBillAirFeeService
|
||||
{
|
||||
List<SrvBillAirFee> listFees(String billCode);
|
||||
|
||||
List<SrvBillAirFeeLog> listLogs(String billCode);
|
||||
|
||||
/** 只写入有变动的行,并按行记录修改前后。返回给页面的提示。 */
|
||||
String save(SrvBillAirFeeSave command);
|
||||
}
|
||||
+33
-4
@@ -183,8 +183,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
private static final int BATCH_ADD_FEE_PREVIEW_LIMIT = 20;
|
||||
private static final int CHARGE_WEIGHT_IMPORT_MAX = 5000;
|
||||
private static final int RECALC_PRICE_MAX = 5000;
|
||||
/** Soft max for one local-rebill job (id-only load + paged parallel). */
|
||||
private static final int LOCAL_REBILL_MAX = 100000;
|
||||
/** Soft max for one local-rebill job (id-only load + continuous workers). */
|
||||
private static final int LOCAL_REBILL_MAX = 200000;
|
||||
/** Soft max for one order/billing list Excel export. */
|
||||
private static final int ORDER_BILL_EXPORT_MAX = 50000;
|
||||
|
||||
@@ -681,6 +681,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(),
|
||||
request.getCreditArrearsStatus(), request.getOrderStatuses(), request.getBillingFailStatuses(),
|
||||
request.getBeginBizDate(), request.getEndBizDate(),
|
||||
request.getBeginPickupDate(), request.getEndPickupDate(),
|
||||
"price.bill.recalc.filterRequired", "price.bill.recalc.scopeRequired",
|
||||
"price.bill.recalc.selectedRequired");
|
||||
if (bills.isEmpty())
|
||||
@@ -1276,6 +1277,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
request.getCustomerCodes(), request.getProductCodes(), request.getCountryCode(),
|
||||
request.getCreditArrearsStatus(), request.getOrderStatuses(), request.getBillingFailStatuses(),
|
||||
request.getBeginBizDate(), request.getEndBizDate(),
|
||||
request.getBeginPickupDate(), request.getEndPickupDate(),
|
||||
"price.bill.fee.batch.filterRequired", "price.bill.fee.batch.scopeRequired",
|
||||
"price.bill.fee.batch.selectedRequired");
|
||||
}
|
||||
@@ -1290,6 +1292,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
List<String> customerCodesRaw, List<String> productCodesRaw, String countryCodeRaw,
|
||||
String creditArrearsStatusRaw, List<Integer> orderStatusesRaw, List<Integer> billingFailStatusesRaw,
|
||||
String beginBizDateRaw, String endBizDateRaw,
|
||||
String beginPickupDateRaw, String endPickupDateRaw,
|
||||
String msgFilter, String msgScope, String msgSelected)
|
||||
{
|
||||
String scopeMode = StringUtils.nvl(scopeModeRaw, "").trim().toUpperCase();
|
||||
@@ -1311,6 +1314,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
: countryCodeRaw.trim().toUpperCase();
|
||||
String beginBizDate = StringUtils.isEmpty(beginBizDateRaw) ? null : beginBizDateRaw.trim();
|
||||
String endBizDate = StringUtils.isEmpty(endBizDateRaw) ? null : endBizDateRaw.trim();
|
||||
String beginPickupDate = StringUtils.isEmpty(beginPickupDateRaw) ? null : beginPickupDateRaw.trim();
|
||||
String endPickupDate = StringUtils.isEmpty(endPickupDateRaw) ? null : endPickupDateRaw.trim();
|
||||
boolean hasFilter = (orderNos != null && !orderNos.isEmpty())
|
||||
|| (trackingNos != null && !trackingNos.isEmpty())
|
||||
|| (transferNos != null && !transferNos.isEmpty())
|
||||
@@ -1321,7 +1326,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
|| StringUtils.isNotEmpty(creditArrearsStatus)
|
||||
|| StringUtils.isNotEmpty(countryCode)
|
||||
|| StringUtils.isNotEmpty(beginBizDate)
|
||||
|| StringUtils.isNotEmpty(endBizDate);
|
||||
|| StringUtils.isNotEmpty(endBizDate)
|
||||
|| StringUtils.isNotEmpty(beginPickupDate)
|
||||
|| StringUtils.isNotEmpty(endPickupDate);
|
||||
if (!hasFilter)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message(msgFilter));
|
||||
@@ -1356,6 +1363,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
query.getParams().put("endBizDate", endBizDate);
|
||||
}
|
||||
if (beginPickupDate != null)
|
||||
{
|
||||
query.getParams().put("beginPickupDate", beginPickupDate);
|
||||
}
|
||||
if (endPickupDate != null)
|
||||
{
|
||||
query.getParams().put("endPickupDate", endPickupDate);
|
||||
}
|
||||
}
|
||||
List<PrcOrderBill> list = self.selectBillList(query);
|
||||
return list == null ? new ArrayList<PrcOrderBill>() : list;
|
||||
@@ -2754,6 +2769,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
: request.getBeginBizDate().trim();
|
||||
String endBizDate = StringUtils.isEmpty(request.getEndBizDate()) ? null
|
||||
: request.getEndBizDate().trim();
|
||||
String beginPickupDate = StringUtils.isEmpty(request.getBeginPickupDate()) ? null
|
||||
: request.getBeginPickupDate().trim();
|
||||
String endPickupDate = StringUtils.isEmpty(request.getEndPickupDate()) ? null
|
||||
: request.getEndPickupDate().trim();
|
||||
boolean hasFilter = (orderNos != null && !orderNos.isEmpty())
|
||||
|| (trackingNos != null && !trackingNos.isEmpty())
|
||||
|| (transferNos != null && !transferNos.isEmpty())
|
||||
@@ -2762,7 +2781,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
|| (productCodes != null && !productCodes.isEmpty())
|
||||
|| StringUtils.isNotEmpty(creditArrearsStatus)
|
||||
|| StringUtils.isNotEmpty(beginBizDate)
|
||||
|| StringUtils.isNotEmpty(endBizDate);
|
||||
|| StringUtils.isNotEmpty(endBizDate)
|
||||
|| StringUtils.isNotEmpty(beginPickupDate)
|
||||
|| StringUtils.isNotEmpty(endPickupDate);
|
||||
if (!hasFilter)
|
||||
{
|
||||
throw new ServiceException("请先设置列表筛选条件");
|
||||
@@ -2784,6 +2805,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
query.getParams().put("endBizDate", endBizDate);
|
||||
}
|
||||
if (beginPickupDate != null)
|
||||
{
|
||||
query.getParams().put("beginPickupDate", beginPickupDate);
|
||||
}
|
||||
if (endPickupDate != null)
|
||||
{
|
||||
query.getParams().put("endPickupDate", endPickupDate);
|
||||
}
|
||||
List<PrcOrderBill> list = self.selectBillList(query);
|
||||
if (list == null || list.isEmpty())
|
||||
{
|
||||
|
||||
+182
-4
@@ -53,6 +53,11 @@ import com.bms.price.search.PriceChangeEsLog;
|
||||
import com.bms.price.service.IPrcPriceExcelFileService;
|
||||
import com.bms.price.service.IPrcPriceSetService;
|
||||
import com.bms.price.task.PrcPriceSetSaveAsyncRunner;
|
||||
import com.bms.customer.customerinfo.domain.BmsCustomer;
|
||||
import com.bms.customer.customerinfo.domain.BmsCustomerPriceSet;
|
||||
import com.bms.customer.customerinfo.domain.BmsPriceSetOption;
|
||||
import com.bms.customer.customerinfo.service.IBmsCustomerLogService;
|
||||
import com.bms.customer.customerinfo.service.IBmsCustomerService;
|
||||
|
||||
/**
|
||||
* 价格套餐 服务层实现
|
||||
@@ -130,6 +135,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@Autowired
|
||||
private IBmsCustomerLogService customerLogService;
|
||||
|
||||
@Autowired
|
||||
private IBmsCustomerService bmsCustomerService;
|
||||
|
||||
@Override
|
||||
public PrcPriceSet selectPrcPriceSetById(Long priceSetId)
|
||||
{
|
||||
@@ -1582,6 +1593,23 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName,
|
||||
String taskId, boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception
|
||||
{
|
||||
// Async import has no SecurityContext; bind operName for PriceChangeEsLog.operator().
|
||||
PriceChangeEsLog.beginOperator(operName);
|
||||
try
|
||||
{
|
||||
return importDetailPricesInOperator(priceSetId, psvId, bytes, filename, operName, taskId, updateOnly,
|
||||
replaceByProductCountry, countryCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PriceChangeEsLog.endOperator();
|
||||
}
|
||||
}
|
||||
|
||||
private String importDetailPricesInOperator(Long priceSetId, Long psvId, byte[] bytes, String filename,
|
||||
String operName, String taskId, boolean updateOnly, boolean replaceByProductCountry, String countryCode)
|
||||
throws Exception
|
||||
{
|
||||
PrcPriceSet db = requireSet(priceSetId);
|
||||
assertChildEditable(db);
|
||||
@@ -2494,7 +2522,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
}
|
||||
try
|
||||
{
|
||||
return customerMapper.insertPrcCustomerPriceSet(customer);
|
||||
int rows = customerMapper.insertPrcCustomerPriceSet(customer);
|
||||
if (rows > 0)
|
||||
{
|
||||
logCustomerPriceSetBind(null, customer, db);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -2593,6 +2626,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
}
|
||||
validateCustomer(row, db);
|
||||
customerMapper.updatePrcCustomerPriceSet(row);
|
||||
logCustomerPriceSetBind(existing, row, db);
|
||||
replaced++;
|
||||
}
|
||||
else
|
||||
@@ -2613,6 +2647,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
logCustomerPriceSetBind(null, row, db);
|
||||
inserted++;
|
||||
}
|
||||
}
|
||||
@@ -2640,12 +2675,30 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
assertChildEditable(db);
|
||||
customer.setPriceSetId(old.getPriceSetId());
|
||||
validateCustomer(customer, db);
|
||||
return customerMapper.updatePrcCustomerPriceSet(customer);
|
||||
int rows = customerMapper.updatePrcCustomerPriceSet(customer);
|
||||
if (rows > 0)
|
||||
{
|
||||
if (customer.getCustomerId() == null)
|
||||
{
|
||||
customer.setCustomerId(old.getCustomerId());
|
||||
}
|
||||
if (StringUtils.isEmpty(customer.getCustomerCode()))
|
||||
{
|
||||
customer.setCustomerCode(old.getCustomerCode());
|
||||
}
|
||||
if (StringUtils.isEmpty(customer.getCustomerName()))
|
||||
{
|
||||
customer.setCustomerName(old.getCustomerName());
|
||||
}
|
||||
logCustomerPriceSetBind(old, customer, db);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteCustomerByIds(Long[] customerPricesetIds)
|
||||
{
|
||||
List<PrcCustomerPriceSet> snapshots = new ArrayList<PrcCustomerPriceSet>();
|
||||
for (Long id : customerPricesetIds)
|
||||
{
|
||||
PrcCustomerPriceSet old = customerMapper.selectPrcCustomerPriceSetById(id);
|
||||
@@ -2658,8 +2711,17 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.set.customer.unbindAudit"));
|
||||
}
|
||||
snapshots.add(old);
|
||||
}
|
||||
return customerMapper.deletePrcCustomerPriceSetByIds(customerPricesetIds);
|
||||
int rows = customerMapper.deletePrcCustomerPriceSetByIds(customerPricesetIds);
|
||||
if (rows > 0)
|
||||
{
|
||||
for (PrcCustomerPriceSet snapshot : snapshots)
|
||||
{
|
||||
logCustomerPriceSetUnbind(snapshot);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void replaceChildren(PrcPriceSet set)
|
||||
@@ -2730,6 +2792,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
c.setUpdateBy(set.getUpdateBy());
|
||||
customerMapper.updatePrcCustomerPriceSet(c);
|
||||
keepIds.add(hit.getCustomerPricesetId());
|
||||
logCustomerPriceSetBind(hit, c, set);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2740,6 +2803,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
{
|
||||
keepIds.add(c.getCustomerPricesetId());
|
||||
}
|
||||
logCustomerPriceSetBind(null, c, set);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -2761,10 +2825,99 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
}
|
||||
if (!orphanIds.isEmpty())
|
||||
{
|
||||
for (Long orphanId : orphanIds)
|
||||
{
|
||||
PrcCustomerPriceSet snapshot = byId.get(orphanId);
|
||||
if (snapshot != null)
|
||||
{
|
||||
logCustomerPriceSetUnbind(snapshot);
|
||||
}
|
||||
}
|
||||
customerMapper.deletePrcCustomerPriceSetByIds(orphanIds.toArray(new Long[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/** Write customer biz log for package bind / rebind (visible in customer info Logs). */
|
||||
private void logCustomerPriceSetBind(PrcCustomerPriceSet existing, PrcCustomerPriceSet bind, PrcPriceSet set)
|
||||
{
|
||||
if (customerLogService == null || bind == null || set == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BmsCustomer customer = resolveCustomerForLog(bind);
|
||||
if (customer == null && bind.getCustomerId() != null)
|
||||
{
|
||||
customer = new BmsCustomer();
|
||||
customer.setCustomerId(bind.getCustomerId());
|
||||
customer.setCustomerCode(bind.getCustomerCode());
|
||||
customer.setCustomerName(bind.getCustomerName());
|
||||
}
|
||||
BmsCustomerPriceSet existingLog = toBmsCustomerPriceSet(existing, resolvePriceSetName(existing));
|
||||
BmsCustomerPriceSet bindLog = toBmsCustomerPriceSet(bind, set.getPriceSetName());
|
||||
if (bindLog.getCustomerId() == null && customer != null)
|
||||
{
|
||||
bindLog.setCustomerId(customer.getCustomerId());
|
||||
}
|
||||
BmsPriceSetOption option = new BmsPriceSetOption();
|
||||
option.setPriceSetId(set.getPriceSetId());
|
||||
option.setPriceSetCode(set.getPriceSetCode());
|
||||
option.setPriceSetName(set.getPriceSetName());
|
||||
customerLogService.recordPriceSetBind(customer, existingLog, bindLog, option);
|
||||
}
|
||||
|
||||
private void logCustomerPriceSetUnbind(PrcCustomerPriceSet bind)
|
||||
{
|
||||
if (customerLogService == null || bind == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BmsCustomerPriceSet snapshot = toBmsCustomerPriceSet(bind, resolvePriceSetName(bind));
|
||||
customerLogService.recordPriceSetUnbind(snapshot);
|
||||
}
|
||||
|
||||
private BmsCustomer resolveCustomerForLog(PrcCustomerPriceSet bind)
|
||||
{
|
||||
if (bind == null || bmsCustomerService == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (bind.getCustomerId() != null)
|
||||
{
|
||||
return bmsCustomerService.selectBmsCustomerByCustomerId(bind.getCustomerId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolvePriceSetName(PrcCustomerPriceSet bind)
|
||||
{
|
||||
if (bind == null || bind.getPriceSetId() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
PrcPriceSet set = setMapper.selectPrcPriceSetById(bind.getPriceSetId());
|
||||
return set == null ? null : set.getPriceSetName();
|
||||
}
|
||||
|
||||
private BmsCustomerPriceSet toBmsCustomerPriceSet(PrcCustomerPriceSet src, String priceSetName)
|
||||
{
|
||||
if (src == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
BmsCustomerPriceSet dest = new BmsCustomerPriceSet();
|
||||
dest.setCustomerPricesetId(src.getCustomerPricesetId());
|
||||
dest.setPriceSetId(src.getPriceSetId());
|
||||
dest.setCustomerId(src.getCustomerId());
|
||||
dest.setCustomerCode(src.getCustomerCode());
|
||||
dest.setCustomerName(src.getCustomerName());
|
||||
dest.setBindPriority(src.getBindPriority());
|
||||
dest.setPrBegindate(src.getPrBegindate());
|
||||
dest.setPrEnddate(src.getPrEnddate());
|
||||
dest.setPriceSetNote(src.getPriceSetNote());
|
||||
dest.setPriceSetName(priceSetName);
|
||||
return dest;
|
||||
}
|
||||
|
||||
private void reportSaveProgress(PrcPriceSet set, int percent, String message)
|
||||
{
|
||||
if (set == null || StringUtils.isEmpty(set.getSaveTaskId()) || saveAsyncRunner == null)
|
||||
@@ -3465,6 +3618,22 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
{
|
||||
fillVersionChildren(version);
|
||||
}
|
||||
// Soft-deleted EFFECT versions for the "deleted" tab (status may still be EFFECT)
|
||||
PrcPriceSetVersion dq = new PrcPriceSetVersion();
|
||||
dq.setPriceSetId(priceSetId);
|
||||
dq.setPriceType(com.bms.price.domain.PriceType.SALES);
|
||||
dq.setIsDel("1");
|
||||
List<PrcPriceSetVersion> deleted = versionMapper.selectPrcPriceSetVersionList(dq);
|
||||
if (deleted != null)
|
||||
{
|
||||
for (PrcPriceSetVersion version : deleted)
|
||||
{
|
||||
version.setProducts(new ArrayList<PrcPriceSetProduct>());
|
||||
version.setDetails(new ArrayList<PrcPriceSetDetail>());
|
||||
version.setDetailCount(Integer.valueOf(detailMapper.countByPsvIdAll(version.getPsvId())));
|
||||
versions.add(version);
|
||||
}
|
||||
}
|
||||
set.setVersions(versions);
|
||||
PrcPriceSetVersion current = pickCurrentVersion(versions);
|
||||
set.setVersion(current);
|
||||
@@ -3491,14 +3660,23 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
||||
{
|
||||
return null;
|
||||
}
|
||||
PrcPriceSetVersion firstAlive = null;
|
||||
for (PrcPriceSetVersion v : versions)
|
||||
{
|
||||
if (v == null || "1".equals(v.getIsDel()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (firstAlive == null)
|
||||
{
|
||||
firstAlive = v;
|
||||
}
|
||||
if (VER_EFFECT.equals(v.getStatus()))
|
||||
{
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return versions.get(0);
|
||||
return firstAlive;
|
||||
}
|
||||
|
||||
private void saveOrUpdateVersion(PrcPriceSet set)
|
||||
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bms.common.core.domain.entity.SysDictData;
|
||||
import com.bms.common.core.domain.entity.SysUser;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.DateUtils;
|
||||
import com.bms.common.utils.PageUtils;
|
||||
import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirMapper;
|
||||
import com.bms.price.service.ISrvBillAirFeeService;
|
||||
import com.bms.system.service.ISysDictTypeService;
|
||||
|
||||
/**
|
||||
* 提单费用只处理本次新增、修改、删除的行。没有变化的费用不更新。
|
||||
*/
|
||||
@Service
|
||||
public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
private static final String FEE_DICT = "bms_fee_type";
|
||||
|
||||
private static final String CURRENCY_DICT = "bms_currency";
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirFeeMapper feeMapper;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirMapper billMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysDictTypeService dictTypeService;
|
||||
|
||||
@Override
|
||||
public List<SrvBillAirFee> listFees(String billCode)
|
||||
{
|
||||
requireBill(billCode);
|
||||
return feeMapper.selectByBillCode(billCode.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SrvBillAirFeeLog> listLogs(String billCode)
|
||||
{
|
||||
requireBill(billCode);
|
||||
PageUtils.startPage();
|
||||
List<SrvBillAirFeeLog> logs = feeMapper.selectLogs(billCode.trim());
|
||||
for (SrvBillAirFeeLog log : logs)
|
||||
{
|
||||
if (log.getChangeContent() != null && !log.getChangeContent().isBlank())
|
||||
{
|
||||
log.setChanges(JSON.parseArray(log.getChangeContent(), SrvBillAirFeeLog.FieldChange.class));
|
||||
}
|
||||
}
|
||||
return logs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String save(SrvBillAirFeeSave command)
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
throw new ServiceException("请提交要保存的费用");
|
||||
}
|
||||
String billCode = requireBill(command.getBillCode());
|
||||
List<SrvBillAirFee> inserts = command.getInserts() == null ? List.of() : command.getInserts();
|
||||
List<SrvBillAirFee> updates = command.getUpdates() == null ? List.of() : command.getUpdates();
|
||||
List<Long> deletes = command.getDeletes() == null ? List.of() : command.getDeletes();
|
||||
if (inserts.isEmpty() && updates.isEmpty() && deletes.isEmpty())
|
||||
{
|
||||
throw new ServiceException("没有需要保存的变更");
|
||||
}
|
||||
Map<String, String> feeLabels = dictLabels(FEE_DICT, "费用类型");
|
||||
Map<String, String> currencyLabels = dictLabels(CURRENCY_DICT, "币种");
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
Set<Long> seen = new HashSet<>();
|
||||
int inserted = 0;
|
||||
int updated = 0;
|
||||
int removed = 0;
|
||||
for (Long id : deletes)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ServiceException("要删除的费用缺少编号");
|
||||
}
|
||||
if (!seen.add(id))
|
||||
{
|
||||
throw new ServiceException("同一条费用不能重复提交");
|
||||
}
|
||||
SrvBillAirFee current = liveFee(id, billCode);
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
|
||||
if (rows > 0)
|
||||
{
|
||||
writeLog(billCode, id, SrvBillAirFeeLog.DELETE, current, null, feeLabels, currencyLabels, operator, now);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
for (SrvBillAirFee incoming : updates)
|
||||
{
|
||||
if (incoming == null || incoming.getId() == null)
|
||||
{
|
||||
throw new ServiceException("要修改的费用缺少编号");
|
||||
}
|
||||
if (!seen.add(incoming.getId()))
|
||||
{
|
||||
throw new ServiceException("同一条费用不能重复提交");
|
||||
}
|
||||
SrvBillAirFee current = liveFee(incoming.getId(), billCode);
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, feeLabels, currencyLabels);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(current, next, feeLabels, currencyLabels);
|
||||
if (changes.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
next.setId(current.getId());
|
||||
next.setBillCode(billCode);
|
||||
next.setUpdateBy(operator.account);
|
||||
next.setUpdateTime(now);
|
||||
if (feeMapper.updateFee(next) > 0)
|
||||
{
|
||||
writeLog(billCode, current.getId(), SrvBillAirFeeLog.UPDATE, current, next, feeLabels, currencyLabels, operator, now);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
for (SrvBillAirFee incoming : inserts)
|
||||
{
|
||||
if (incoming == null || blank(incoming.getFeeType()) && incoming.getPrice() == null && blank(incoming.getCurrency()) && blank(incoming.getRemark()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, feeLabels, currencyLabels);
|
||||
next.setId(null);
|
||||
next.setBillCode(billCode);
|
||||
next.setCreateBy(operator.account);
|
||||
next.setCreateTime(now);
|
||||
next.setUpdateBy(operator.account);
|
||||
next.setUpdateTime(now);
|
||||
feeMapper.insertFee(next);
|
||||
writeLog(billCode, next.getId(), SrvBillAirFeeLog.INSERT, null, next, feeLabels, currencyLabels, operator, now);
|
||||
inserted++;
|
||||
}
|
||||
if (inserted + updated + removed == 0)
|
||||
{
|
||||
throw new ServiceException("没有需要保存的变更");
|
||||
}
|
||||
return "已保存:新增 " + inserted + " 条,修改 " + updated + " 条,删除 " + removed + " 条";
|
||||
}
|
||||
|
||||
private SrvBillAirFee liveFee(Long id, String billCode)
|
||||
{
|
||||
return feeMapper.selectLive(id, billCode);
|
||||
}
|
||||
|
||||
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> feeLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
String feeType = trim(raw.getFeeType());
|
||||
String currency = trim(raw.getCurrency());
|
||||
String remark = trim(raw.getRemark());
|
||||
if (feeType.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请选择费用项");
|
||||
}
|
||||
if (feeType.length() > 32 || !feeLabels.containsKey(feeType))
|
||||
{
|
||||
throw new ServiceException("费用项不在费用类型字典中");
|
||||
}
|
||||
if (raw.getPrice() == null)
|
||||
{
|
||||
throw new ServiceException("请填写价格");
|
||||
}
|
||||
if (raw.getPrice().signum() < 0)
|
||||
{
|
||||
throw new ServiceException("价格不能小于0");
|
||||
}
|
||||
if (raw.getPrice().precision() - raw.getPrice().scale() > 16)
|
||||
{
|
||||
throw new ServiceException("价格整数部分不能超过16位");
|
||||
}
|
||||
if (currency.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请选择收费币种");
|
||||
}
|
||||
if (currency.length() > 16 || !currencyLabels.containsKey(currency))
|
||||
{
|
||||
throw new ServiceException("收费币种不在币种字典中");
|
||||
}
|
||||
if (remark.length() > 500)
|
||||
{
|
||||
throw new ServiceException("备注不能超过500字");
|
||||
}
|
||||
SrvBillAirFee fee = new SrvBillAirFee();
|
||||
fee.setFeeType(feeType);
|
||||
fee.setPrice(raw.getPrice());
|
||||
fee.setCurrency(currency);
|
||||
fee.setRemark(remark);
|
||||
return fee;
|
||||
}
|
||||
|
||||
private void writeLog(String billCode, Long feeId, String operation, SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> currencyLabels, Operator operator, Date now)
|
||||
{
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(before, after, feeLabels, currencyLabels);
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(billCode);
|
||||
log.setFeeId(feeId);
|
||||
log.setOperationType(operation);
|
||||
log.setSummary(cut(summary(operation, changes), 1000));
|
||||
log.setChangeContent(JSON.toJSONString(changes));
|
||||
log.setCreateBy(operator.account);
|
||||
log.setCreateByName(operator.name);
|
||||
log.setCreateTime(now);
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
|
||||
private static List<SrvBillAirFeeLog.FieldChange> diff(SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = new ArrayList<>();
|
||||
addChange(changes, "feeType", "费用类型", label(feeLabels, value(before, SrvBillAirFee::getFeeType)),
|
||||
label(feeLabels, value(after, SrvBillAirFee::getFeeType)));
|
||||
addChange(changes, "price", "价格", priceText(before), priceText(after));
|
||||
addChange(changes, "currency", "收费币种", label(currencyLabels, value(before, SrvBillAirFee::getCurrency)),
|
||||
label(currencyLabels, value(after, SrvBillAirFee::getCurrency)));
|
||||
addChange(changes, "remark", "备注", text(before == null ? null : before.getRemark()), text(after == null ? null : after.getRemark()));
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static void addChange(List<SrvBillAirFeeLog.FieldChange> changes, String field, String fieldLabel, String oldValue, String newValue)
|
||||
{
|
||||
String left = oldValue == null ? "" : oldValue;
|
||||
String right = newValue == null ? "" : newValue;
|
||||
if (left.equals(right))
|
||||
{
|
||||
return;
|
||||
}
|
||||
changes.add(new SrvBillAirFeeLog.FieldChange(field, fieldLabel, left, right));
|
||||
}
|
||||
|
||||
private static String summary(String operation, List<SrvBillAirFeeLog.FieldChange> changes)
|
||||
{
|
||||
String action = SrvBillAirFeeLog.INSERT.equals(operation) ? "新增费用"
|
||||
: SrvBillAirFeeLog.DELETE.equals(operation) ? "删除费用" : "修改费用";
|
||||
StringBuilder text = new StringBuilder(action);
|
||||
for (SrvBillAirFeeLog.FieldChange change : changes)
|
||||
{
|
||||
text.append(";").append(change.getFieldLabel()).append(":")
|
||||
.append(show(change.getOldValue())).append(" → ").append(show(change.getNewValue()));
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private Map<String, String> dictLabels(String dictType, String name)
|
||||
{
|
||||
List<SysDictData> rows = dictTypeService.selectDictDataByType(dictType);
|
||||
Map<String, String> labels = new LinkedHashMap<>();
|
||||
if (rows != null)
|
||||
{
|
||||
for (SysDictData row : rows)
|
||||
{
|
||||
if (row != null && row.getDictValue() != null && !row.getDictValue().isBlank())
|
||||
{
|
||||
labels.put(row.getDictValue().trim(), row.getDictLabel() == null ? row.getDictValue().trim() : row.getDictLabel().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (labels.isEmpty())
|
||||
{
|
||||
throw new ServiceException(name + "字典还没有数据");
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private String requireBill(String billCode)
|
||||
{
|
||||
if (billCode == null || billCode.isBlank())
|
||||
{
|
||||
throw new ServiceException("提单号不能为空");
|
||||
}
|
||||
String code = billCode.trim();
|
||||
if (billMapper.selectSrvBillAirByBillCode(code) == null)
|
||||
{
|
||||
throw new ServiceException("提单不存在");
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
private static Operator operator()
|
||||
{
|
||||
SysUser user = SecurityUtils.getLoginUser().getUser();
|
||||
String account = user.getUserName() == null ? "" : user.getUserName();
|
||||
String name = user.getNickName() == null || user.getNickName().isBlank() ? account : user.getNickName();
|
||||
return new Operator(account, name);
|
||||
}
|
||||
|
||||
private static String value(SrvBillAirFee fee, java.util.function.Function<SrvBillAirFee, String> getter)
|
||||
{
|
||||
return fee == null ? "" : text(getter.apply(fee));
|
||||
}
|
||||
|
||||
private static String priceText(SrvBillAirFee fee)
|
||||
{
|
||||
if (fee == null || fee.getPrice() == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return fee.getPrice().stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private static String label(Map<String, String> labels, String value)
|
||||
{
|
||||
if (value == null || value.isBlank())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return labels.getOrDefault(value, value);
|
||||
}
|
||||
|
||||
private static String show(String value)
|
||||
{
|
||||
return value == null || value.isBlank() ? "空" : value;
|
||||
}
|
||||
|
||||
private static String text(String value)
|
||||
{
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static String trim(String value)
|
||||
{
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static boolean blank(String value)
|
||||
{
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
|
||||
private static String cut(String value, int max)
|
||||
{
|
||||
if (value == null || value.length() <= max)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return value.substring(0, max);
|
||||
}
|
||||
|
||||
private static final class Operator
|
||||
{
|
||||
private final String account;
|
||||
|
||||
private final String name;
|
||||
|
||||
private Operator(String account, String name)
|
||||
{
|
||||
this.account = account;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-42
@@ -36,8 +36,7 @@ import com.bms.price.service.IPrcOrderBillService;
|
||||
|
||||
/**
|
||||
* Async local rebill from prc_order_bill only (no Oracle / MQ).
|
||||
* Large jobs page by {@link #PAGE_SIZE}; each page runs in a short-lived pool.
|
||||
* Cancel is cooperative: current in-flight bills finish, remaining bills are skipped.
|
||||
* Continuous worker pull (no page barrier); cancel skips remaining billIds.
|
||||
*/
|
||||
@Component
|
||||
public class PrcOrderBillLocalRebillAsyncRunner
|
||||
@@ -46,13 +45,11 @@ public class PrcOrderBillLocalRebillAsyncRunner
|
||||
|
||||
public static final String CACHE_KEY_PREFIX = "price:billing:localRebill:task:";
|
||||
public static final int TTL_MINUTES = 120;
|
||||
/** Bill this many, wait for page done, then next page. */
|
||||
public static final int PAGE_SIZE = 500;
|
||||
public static final int MIN_THREADS = 2;
|
||||
public static final int MAX_THREADS = 16;
|
||||
public static final int MAX_THREADS = 24;
|
||||
private static final int FAIL_DETAIL_MAX = 200;
|
||||
/** Publish Redis progress at most this often while a page is running. */
|
||||
private static final int PROGRESS_EVERY_N = 20;
|
||||
/** Publish Redis progress at most this often while workers run. */
|
||||
private static final int PROGRESS_EVERY_N = 50;
|
||||
private static final long PROGRESS_MIN_INTERVAL_MS = 500L;
|
||||
|
||||
private final ConcurrentHashMap<String, AtomicBoolean> cancelFlags = new ConcurrentHashMap<String, AtomicBoolean>();
|
||||
@@ -86,10 +83,12 @@ public class PrcOrderBillLocalRebillAsyncRunner
|
||||
AtomicInteger success = new AtomicInteger(0);
|
||||
AtomicInteger fail = new AtomicInteger(0);
|
||||
AtomicInteger done = new AtomicInteger(0);
|
||||
AtomicInteger nextIndex = new AtomicInteger(0);
|
||||
AtomicLong lastProgressAt = new AtomicLong(0L);
|
||||
StringBuilder failTips = new StringBuilder();
|
||||
List<Map<String, Object>> failDetails = Collections.synchronizedList(new ArrayList<Map<String, Object>>());
|
||||
int threads = clampThreads(billingProperties == null ? null : billingProperties.getOrderSourceSyncThreads());
|
||||
int configured = billingProperties == null ? 16 : billingProperties.getLocalRebillThreads();
|
||||
int threads = clampThreads(Integer.valueOf(configured));
|
||||
cancelFlags.computeIfAbsent(taskId, k -> new AtomicBoolean(false));
|
||||
ExecutorService pool = newWorkerPool(threads, "local-rebill");
|
||||
try
|
||||
@@ -107,30 +106,24 @@ public class PrcOrderBillLocalRebillAsyncRunner
|
||||
finishCancelled(taskId, request, total, success.get(), fail.get(), threads, failDetails);
|
||||
return;
|
||||
}
|
||||
boolean cancelled = false;
|
||||
for (int from = 0; from < total; from += PAGE_SIZE)
|
||||
// Continuous pull: N workers grab next billId — no page barrier idle wait.
|
||||
final List<String> ids = billIds;
|
||||
List<Callable<Boolean>> workers = new ArrayList<Callable<Boolean>>(threads);
|
||||
for (int w = 0; w < threads; w++)
|
||||
{
|
||||
if (isCancelRequested(taskId))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
int to = Math.min(from + PAGE_SIZE, total);
|
||||
List<String> page = billIds.subList(from, to);
|
||||
List<Callable<Boolean>> jobs = new ArrayList<Callable<Boolean>>(page.size());
|
||||
for (String billId : page)
|
||||
{
|
||||
final String one = billId;
|
||||
jobs.add(() -> {
|
||||
if (isCancelRequested(taskId))
|
||||
workers.add(() -> {
|
||||
while (!isCancelRequested(taskId))
|
||||
{
|
||||
int i = nextIndex.getAndIncrement();
|
||||
if (i >= total)
|
||||
{
|
||||
return Boolean.FALSE;
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
String one = ids.get(i);
|
||||
try
|
||||
{
|
||||
orderBillService.rebillLocalByBillId(one);
|
||||
success.incrementAndGet();
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -139,26 +132,20 @@ public class PrcOrderBillLocalRebillAsyncRunner
|
||||
collectFailDetail(failDetails, one, e);
|
||||
log.warn("local rebill failed taskId={} billId={} msg={}",
|
||||
taskId, one, e.getMessage());
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
finally
|
||||
{
|
||||
int finished = done.incrementAndGet();
|
||||
maybePublishProgress(taskId, total, finished, success.get(), fail.get(), lastProgressAt);
|
||||
maybePublishProgress(taskId, total, finished, success.get(), fail.get(),
|
||||
lastProgressAt);
|
||||
}
|
||||
});
|
||||
}
|
||||
invokeAllQuiet(pool, jobs);
|
||||
if (isCancelRequested(taskId))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
// Page boundary: always flush latest counts (even if throttle skipped mid-page).
|
||||
publishProgress(taskId, total, done.get(), success.get(), fail.get());
|
||||
lastProgressAt.set(System.currentTimeMillis());
|
||||
}
|
||||
return Boolean.FALSE;
|
||||
});
|
||||
}
|
||||
if (cancelled || isCancelRequested(taskId))
|
||||
invokeAllQuiet(pool, workers);
|
||||
publishProgress(taskId, total, done.get(), success.get(), fail.get());
|
||||
if (isCancelRequested(taskId))
|
||||
{
|
||||
finishCancelled(taskId, request, total, success.get(), fail.get(), threads, failDetails);
|
||||
return;
|
||||
@@ -170,8 +157,8 @@ public class PrcOrderBillLocalRebillAsyncRunner
|
||||
String doneStatus = fail.get() > 0 ? EsBizLogSupport.STATUS_FAIL : EsBizLogSupport.STATUS_SUCCESS;
|
||||
String doneErr = fail.get() > 0 ? ("fail=" + fail.get() + ", ok=" + success.get()) : null;
|
||||
saveDoneLog(taskId, request, total, success.get(), fail.get(), threads, failDetails, doneStatus, doneErr);
|
||||
log.info("local rebill done taskId={} total={} success={} fail={} threads={} pageSize={}",
|
||||
taskId, total, success.get(), fail.get(), threads, PAGE_SIZE);
|
||||
log.info("local rebill done taskId={} total={} success={} fail={} threads={}",
|
||||
taskId, total, success.get(), fail.get(), threads);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -255,7 +242,6 @@ public class PrcOrderBillLocalRebillAsyncRunner
|
||||
resp.put("fail", Integer.valueOf(fail));
|
||||
resp.put("success", Integer.valueOf(ok));
|
||||
resp.put("threads", Integer.valueOf(threads));
|
||||
resp.put("pageSize", Integer.valueOf(PAGE_SIZE));
|
||||
if (failDetails != null && !failDetails.isEmpty())
|
||||
{
|
||||
resp.put("failDetails", new ArrayList<Map<String, Object>>(failDetails));
|
||||
|
||||
@@ -66,14 +66,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="query.endTime != null and query.endTime != ''">
|
||||
AND o.PICKUP_DATE <= TO_DATE(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
</if>
|
||||
<choose>
|
||||
<when test="query.blPickup != null">
|
||||
AND o.BL_PICKUP = #{query.blPickup}
|
||||
</when>
|
||||
<!-- <otherwise>-->
|
||||
<!-- AND o.BL_PICKUP = 1-->
|
||||
<!-- </otherwise>-->
|
||||
</choose>
|
||||
AND o.BL_PICKUP = 1
|
||||
<if test="query.orderStatuses != null and query.orderStatuses.size() > 0">
|
||||
AND o.ORDER_STATUS IN
|
||||
<foreach collection="query.orderStatuses" item="st" open="(" separator="," close=")">
|
||||
@@ -281,14 +274,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<foreach collection="batch" item="bid" open="(" separator="," close=")">
|
||||
#{bid}
|
||||
</foreach>
|
||||
<choose>
|
||||
<when test="blPickup != null">
|
||||
AND o.BL_PICKUP = #{blPickup}
|
||||
</when>
|
||||
<!-- <otherwise>-->
|
||||
<!-- AND o.BL_PICKUP = 1-->
|
||||
<!-- </otherwise>-->
|
||||
</choose>
|
||||
AND o.BL_PICKUP = 1
|
||||
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11)
|
||||
<if test="customerCodes != null and customerCodes.size() > 0">
|
||||
AND o.CUSTOMER_CODE IN
|
||||
|
||||
@@ -158,6 +158,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select count(1) from prc_price_set_detail where psv_id = #{psvId} and ifnull(is_del, '0') = '0'
|
||||
</select>
|
||||
|
||||
<select id="countByPsvIdAll" parameterType="Long" resultType="int">
|
||||
select count(1) from prc_price_set_detail where psv_id = #{psvId}
|
||||
</select>
|
||||
|
||||
<select id="selectDetailOptionRows" resultMap="PrcPriceSetDetailResult">
|
||||
select distinct product_code, product_name, country_code, country_name, zone_code, zone_name
|
||||
from prc_price_set_detail
|
||||
|
||||
@@ -37,7 +37,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="selectPrcPriceSetVersionList" parameterType="PrcPriceSetVersion" resultMap="PrcPriceSetVersionResult">
|
||||
<include refid="selectPrcPriceSetVersionVo"/>
|
||||
<where>
|
||||
and ifnull(is_del, '0') = '0'
|
||||
<choose>
|
||||
<when test="isDel != null and isDel != ''">
|
||||
and ifnull(is_del, '0') = #{isDel}
|
||||
</when>
|
||||
<otherwise>
|
||||
and ifnull(is_del, '0') = '0'
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="priceSetId != null">
|
||||
AND price_set_id = #{priceSetId}
|
||||
</if>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.SrvBillAirFeeMapper">
|
||||
|
||||
<select id="selectByBillCode" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
|
||||
select id, bill_code as billCode, fee_type as feeType, price, currency, remark,
|
||||
create_by as createBy, create_time as createTime, update_by as updateBy,
|
||||
update_time as updateTime, del_flag as delFlag
|
||||
from srv_bill_air_fee
|
||||
where bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
order by id
|
||||
</select>
|
||||
|
||||
<select id="selectLive" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
|
||||
select id, bill_code as billCode, fee_type as feeType, price, currency, remark,
|
||||
create_by as createBy, create_time as createTime, update_by as updateBy,
|
||||
update_time as updateTime, del_flag as delFlag
|
||||
from srv_bill_air_fee
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertFee" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into srv_bill_air_fee (
|
||||
bill_code, fee_type, price, currency, remark,
|
||||
create_by, create_time, update_by, update_time, del_flag
|
||||
) values (
|
||||
#{billCode}, #{feeType}, #{price}, #{currency}, #{remark},
|
||||
#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, '0'
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateFee">
|
||||
update srv_bill_air_fee
|
||||
set fee_type = #{feeType},
|
||||
price = #{price},
|
||||
currency = #{currency},
|
||||
remark = #{remark},
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<update id="deleteFee">
|
||||
update srv_bill_air_fee
|
||||
set del_flag = '1',
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<insert id="insertLog" useGeneratedKeys="true" keyProperty="logId">
|
||||
insert into srv_bill_air_fee_log (
|
||||
bill_code, fee_id, operation_type, summary, change_content,
|
||||
create_by, create_by_name, create_time
|
||||
) values (
|
||||
#{billCode}, #{feeId}, #{operationType}, #{summary}, #{changeContent},
|
||||
#{createBy}, #{createByName}, #{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="selectLogs" resultType="com.bms.price.airbill.domain.SrvBillAirFeeLog">
|
||||
select log_id as logId, bill_code as billCode, fee_id as feeId, operation_type as operationType,
|
||||
summary, change_content as changeContent, create_by as createBy,
|
||||
create_by_name as createByName, create_time as createTime
|
||||
from srv_bill_air_fee_log
|
||||
where bill_code = #{billCode}
|
||||
order by log_id desc
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user