Refactor billing options retrieval to include data permissions and add effective date handling for product imports

This commit is contained in:
wuweihua
2026-10-09 09:27:28 +08:00
parent fea34d1d10
commit c95a9861cc
42 changed files with 984 additions and 219 deletions
@@ -23,9 +23,9 @@ public class BmsProduct extends BaseEntity
private String updateNote;
/** TAX_INCLUDED / TAX_EXCLUDED */
private String taxMode;
@JsonFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date effectiveDate;
@JsonFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expireDate;
/** 0 pending, 1 active, 2 expired */
public static final String STATUS_PENDING = "0";
@@ -31,10 +31,10 @@ public class BmsProductCountry implements Serializable
private String sizeLimit;
private String lastMile;
private String leadTime;
@JsonFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date effectiveDate;
/** null=open; closed to next import effective_date */
@JsonFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expireDate;
/** 0 pending, 1 active, 2 expired */
private String status;
@@ -20,6 +20,10 @@ public class PrcIncomeBillFee implements Serializable
private String feeName;
private Long scId;
private BigDecimal amount;
/** 产品价(目录价,基础运费/挂号费明细对应分摊) */
private BigDecimal productAmount;
/** 红线价(预留) */
private BigDecimal redlineAmount;
private String currency;
private String roundMode;
private String remark;
@@ -37,6 +37,8 @@ public class PrcOrderBill extends BaseEntity
private String settlementManCode;
/** 客服员 */
private String serviceMan;
/** 客服员编码 */
private String serviceManCode;
private Long customerId;
private String customerCode;
private String customerName;
@@ -33,6 +33,8 @@ public class TmsPriceBillRequest extends TmsPriceQuoteRequest
private String settlementManCode;
/** 客服员 */
private String serviceMan;
/** 客服员编码 */
private String serviceManCode;
private String customerName;
private Integer pieces;
private BigDecimal actualWeight;
@@ -16,6 +16,10 @@ public class TmsPriceQuoteFeeLine implements Serializable
/** surcharge config id, 0 for freight/registration */
private Long scId;
private BigDecimal amount;
/** 产品价(目录价分摊) */
private BigDecimal productAmount;
/** 红线价(预留) */
private BigDecimal redlineAmount;
private String currency;
private String roundMode;
private String remark;
@@ -91,6 +91,8 @@ public class TmsPriceQuoteResult implements Serializable
private String settlementManCode;
/** 客服员 */
private String serviceMan;
/** 客服员编码 */
private String serviceManCode;
private String customerCode;
private String customerName;
/** ACTIVE / MISSING / DISABLED */
@@ -20,14 +20,14 @@ public interface PrcOrderBillMapper
public List<PrcOrderBill> selectPrcOrderBillList(PrcOrderBill bill);
/** 订单号下拉搜索:按关键词模糊匹配,返回去重后的订单号(按最近创建排序,限量返回) */
public List<String> selectOrderNoOptions(@Param("keyword") String keyword, @Param("limit") int limit);
/** 订单号下拉搜索(带数据权限) */
public List<String> selectOrderNoOptions(@Param("query") PrcOrderBill query, @Param("limit") int limit);
/** 运单号下拉搜索:按关键词模糊匹配,返回去重后的运单号(按最近创建排序,限量返回) */
public List<String> selectTrackingNoOptions(@Param("keyword") String keyword, @Param("limit") int limit);
/** 运单号下拉搜索(带数据权限) */
public List<String> selectTrackingNoOptions(@Param("query") PrcOrderBill query, @Param("limit") int limit);
/** 转单号下拉搜索:按关键词模糊匹配,返回去重后的转单号(按最近创建排序,限量返回) */
public List<String> selectTransferNoOptions(@Param("keyword") String keyword, @Param("limit") int limit);
/** 转单号下拉搜索(带数据权限) */
public List<String> selectTransferNoOptions(@Param("query") PrcOrderBill query, @Param("limit") int limit);
/** 应收账单生成读取指定客户截止日期前仍存在未出账费用的订单计费快照。 */
public List<PrcOrderBill> selectPrcOrderBillByCustomerPeriod(@Param("customerId") Long customerId,
@@ -26,11 +26,12 @@ public class OracleOrderBillMapper
req.setOrderNo(firstStr(row, "ORDER_ID", "ORG_ORDER_ID", "TRANSFER_ORDER_ID"));
req.setTrackingNo(firstStr(row, "ORG_ORDER_ID", "TRACK_ID", "BARCODE_NUMBER", "CUSTOMER_TRACK_ID"));
req.setTransferNo(firstStr(row, "TRANSFER_ORDER_ID"));
req.setBelongSaler(firstStr(row, "BELONG_SALER_OLD"));
// Oracle 人员字段为中文名;编码在落库前按 sys_user.nick_name 反查
req.setBelongSaler(firstStr(row, "BELONG_SALER_OLD", "BELONG_SALER_NAME", "BELONG_SALER"));
req.setBelongSalerCode(firstStr(row, "BELONG_SALER_CODE", "SALER_CODE"));
req.setSettlementMan(firstStr(row, "SETTLEMENT_MAN_OLD"));
req.setSettlementMan(firstStr(row, "SETTLEMENT_MAN_OLD", "SETTLEMENT_MAN_NAME", "SETTLEMENT_MAN"));
req.setSettlementManCode(firstStr(row, "SETTLEMENT_MAN_CODE", "SETTLEMENT_CODE"));
req.setServiceMan(firstStr(row, "SERVICE_MAN_OLD"));
req.setServiceMan(firstStr(row, "SERVICE_MAN_OLD", "SERVICE_MAN_NAME", "SERVICE_MAN"));
req.setUpstreamRow(row);
req.setCustomerCode(firstStr(row, "CUSTOMER_CODE"));
req.setCustomerName(firstStr(row, "CUSTOMER_NAME"));
@@ -32,24 +32,24 @@ public interface IBmsProductService
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate, Long productId);
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception;
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName, Date effectiveDate) throws Exception;
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName) throws Exception;
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName, Date effectiveDate) throws Exception;
/** Submit async all-country import; returns taskId for polling getImportTask. */
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName) throws Exception;
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName, Date effectiveDate) throws Exception;
/** Submit async single-country import; returns taskId for polling getImportTask. */
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName) throws Exception;
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName, Date effectiveDate) throws Exception;
/** Poll Redis progress for product import task. */
public BmsProductImportTask getImportTask(String taskId);
/** Sync import from bytes (used by async runner; taskId may be null). */
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName, String taskId) throws Exception;
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName, String taskId, Date effectiveDate) throws Exception;
/** Sync country import from bytes (used by async runner; taskId may be null). */
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName, String taskId) throws Exception;
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName, String taskId, Date effectiveDate) throws Exception;
public java.util.Map<String, List<BmsProductCountry>> parseQuoteExcelByProduct(MultipartFile file) throws Exception;
@@ -23,14 +23,14 @@ public interface IPrcOrderBillService
List<PrcOrderBill> selectBillList(PrcOrderBill query);
/** 订单号下拉搜索选项(模糊匹配,去重限量) */
List<String> listOrderNoOptions(String keyword);
/** 订单号下拉搜索选项(模糊匹配,去重限量,带数据权限) */
List<String> listOrderNoOptions(PrcOrderBill query);
/** 运单号下拉搜索选项(模糊匹配,去重限量) */
List<String> listTrackingNoOptions(String keyword);
/** 运单号下拉搜索选项(模糊匹配,去重限量,带数据权限) */
List<String> listTrackingNoOptions(PrcOrderBill query);
/** 转单号下拉搜索选项(模糊匹配,去重限量) */
List<String> listTransferNoOptions(String keyword);
/** 转单号下拉搜索选项(模糊匹配,去重限量,带数据权限) */
List<String> listTransferNoOptions(PrcOrderBill query);
TmsPriceQuoteResult getBill(String billId);
@@ -51,6 +51,12 @@ public interface IPrcPriceSetService
*/
public Long copyVersion(Long sourcePsvId, String createBy);
/**
* Create an empty draft version under the package (no detail copy).
* @return new psvId
*/
public Long createVersion(Long priceSetId, String createBy);
/** Delete a draft version and its details / surcharge snapshots */
public int deleteVersion(Long psvId, String updateBy);
@@ -244,21 +244,25 @@ public class BmsProductServiceImpl implements IBmsProductService
}
@Override
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName, Date effectiveDate)
throws Exception
{
validateImportFile(file);
return importProductExcel(file.getBytes(), file.getOriginalFilename(), updateSupport, operName, null);
return importProductExcel(file.getBytes(), file.getOriginalFilename(), updateSupport, operName, null,
effectiveDate);
}
@Override
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName) throws Exception
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName,
Date effectiveDate) throws Exception
{
validateImportFile(file);
Date eff = requireImportEffectiveDate(effectiveDate);
byte[] bytes = file.getBytes();
String filename = file.getOriginalFilename();
String taskId = IdUtils.fastSimpleUUID();
importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null);
importAsyncRunner.runImportAll(taskId, bytes, filename, updateSupport, operName);
importAsyncRunner.runImportAll(taskId, bytes, filename, updateSupport, operName, eff);
return taskId;
}
@@ -273,9 +277,11 @@ public class BmsProductServiceImpl implements IBmsProductService
}
@Override
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName, String taskId) throws Exception
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName,
String taskId, Date effectiveDate) throws Exception
{
validateImportBytes(bytes, filename);
Date forcedEff = requireImportEffectiveDate(effectiveDate);
reportImportProgress(taskId, 10, "parsing excel");
try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is))
@@ -293,6 +299,8 @@ public class BmsProductServiceImpl implements IBmsProductService
}
products = dedupeCatalogProducts(products);
reportImportProgress(taskId, 20, "parsing excel");
// Pre-check all products: reject whole import before any write if any is earlier than prev version
assertImportProductsEffectiveNotBeforePrev(products, forcedEff);
Map<String, BmsRegion> regionByNameCn = loadRegionByNameCn();
if (regionByNameCn.isEmpty())
@@ -319,8 +327,9 @@ public class BmsProductServiceImpl implements IBmsProductService
TransactionStatus tx = transactionManager.getTransaction(def);
try
{
product.setEffectiveDate(forcedEff);
int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions,
zoneByCountryAndCode, unmatchedCountries);
zoneByCountryAndCode, unmatchedCountries, forcedEff);
transactionManager.commit(tx);
successNum += nums[0];
skipNum += nums[1];
@@ -357,32 +366,38 @@ public class BmsProductServiceImpl implements IBmsProductService
}
@Override
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName) throws Exception
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName, Date effectiveDate)
throws Exception
{
validateImportFile(file);
return importCountryProductExcel(file.getBytes(), file.getOriginalFilename(), countryCode, operName, null);
return importCountryProductExcel(file.getBytes(), file.getOriginalFilename(), countryCode, operName, null,
effectiveDate);
}
@Override
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName) throws Exception
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName,
Date effectiveDate) throws Exception
{
validateImportFile(file);
if (StringUtils.isEmpty(countryCode))
{
throw new ServiceException(MessageUtils.message("price.product.import.countryRequired"));
}
Date eff = requireImportEffectiveDate(effectiveDate);
byte[] bytes = file.getBytes();
String filename = file.getOriginalFilename();
String taskId = IdUtils.fastSimpleUUID();
importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null);
importAsyncRunner.runImportCountry(taskId, bytes, filename, countryCode, operName);
importAsyncRunner.runImportCountry(taskId, bytes, filename, countryCode, operName, eff);
return taskId;
}
@Override
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName, String taskId) throws Exception
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName,
String taskId, Date effectiveDate) throws Exception
{
validateImportBytes(bytes, filename);
Date forcedEff = requireImportEffectiveDate(effectiveDate);
if (StringUtils.isEmpty(countryCode))
{
throw new ServiceException(MessageUtils.message("price.product.import.countryRequired"));
@@ -410,6 +425,8 @@ public class BmsProductServiceImpl implements IBmsProductService
products = dedupeCatalogProducts(products);
Map<String, PriceBlock> priceBlocks = parseCountryPriceBlocks(workbook);
reportImportProgress(taskId, 20, "parsing excel");
// Pre-check all products: reject whole import before any write if any is earlier than prev version
assertImportProductsEffectiveNotBeforePrev(products, forcedEff);
int successNum = 0;
int skipNum = 0;
@@ -437,11 +454,12 @@ public class BmsProductServiceImpl implements IBmsProductService
}
product.setDestRegion(countryLabel);
product.setSourceSheet(block.sheetName);
product.setEffectiveDate(forcedEff);
if (StringUtils.isEmpty(product.getTaxMode()))
{
product.setTaxMode(resolveTaxMode(product.getProductName()));
}
List<BmsProductCountry> countries = stampSelectedCountry(block.rows, region, product.getEffectiveDate());
List<BmsProductCountry> countries = stampSelectedCountry(block.rows, region, forcedEff);
int[] nums = persistImportedProduct(product, countries, operName);
transactionManager.commit(tx);
successNum += nums[0];
@@ -594,7 +612,7 @@ public class BmsProductServiceImpl implements IBmsProductService
/** @return int[]{added, skipped, countryRows} */
private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName,
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions,
Map<String, PrcZone> zoneByCountryAndCode, Set<String> unmatchedCountries)
Map<String, PrcZone> zoneByCountryAndCode, Set<String> unmatchedCountries, Date forcedEffectiveDate)
{
product.setSourceSheet(product.getProductName());
product.setCreateBy(operName);
@@ -614,13 +632,11 @@ public class BmsProductServiceImpl implements IBmsProductService
throw new ServiceException(MessageUtils.message("price.product.import.noCountry",
productSheet.getSheetName()));
}
Date sheetEff = resolveImportEffectiveDate(countries);
Date newEff = toDateOnly(sheetEff != null ? sheetEff : product.getEffectiveDate());
product.setEffectiveDate(newEff);
if (newEff == null)
{
throw new ServiceException(MessageUtils.message("price.product.import.effEmpty"));
}
// UI-selected effective date wins over Excel; previous open product/country rows expire at this date
Date newEff = requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate
: product.getEffectiveDate());
applyForcedEffectiveDate(product, countries, newEff);
assertImportEffectiveNotBeforePrev(product, newEff);
BmsProduct existing = productMapper.selectBmsProductByCodeAndDate(
product.getProductCode(), newEff);
@@ -657,7 +673,7 @@ public class BmsProductServiceImpl implements IBmsProductService
* - expire all earlier open weight bands under that country
* - delete same-day open weight bands (unique key blocks insert if only expired)
* - insert Excel weight bands only (no copy / merge of old bands)
* Reject when newEff &lt; last open effective_date.
* Reject when newEff is earlier than the product's previous version effective time.
*/
private int saveCountries(BmsProduct product, List<BmsProductCountry> countries)
{
@@ -667,22 +683,16 @@ public class BmsProductServiceImpl implements IBmsProductService
}
String productCode = product.getProductCode();
Long productId = product.getProductId();
Date newEff = toDateOnly(resolveImportEffectiveDate(countries));
Date newEff = normalizeToHour(product.getEffectiveDate());
if (newEff == null)
{
newEff = toDateOnly(product.getEffectiveDate());
newEff = normalizeToHour(resolveImportEffectiveDate(countries));
}
if (newEff == null)
{
throw new ServiceException(MessageUtils.message("price.product.import.effEmpty"));
}
Date lastOpenEff = toDateOnly(countryMapper.selectMaxOpenEffectiveDate(productCode));
if (lastOpenEff != null && newEff.before(lastOpenEff))
{
throw new ServiceException(MessageUtils.message("price.product.import.effTooEarly",
formatDate(newEff), formatDate(lastOpenEff)));
throw new ServiceException(MessageUtils.message("price.product.import.effRequired"));
}
assertImportEffectiveNotBeforePrev(product, newEff);
Set<String> replacedCountries = new HashSet<>();
String rowStatus = resolveStoredStatus(newEff, null);
@@ -776,6 +786,15 @@ public class BmsProductServiceImpl implements IBmsProductService
return new SimpleDateFormat("yyyy-MM-dd").format(d);
}
private String formatDateTime(Date d)
{
if (d == null)
{
return "-";
}
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(d);
}
private Date toDateOnly(Date d)
{
if (d == null)
@@ -792,17 +811,35 @@ public class BmsProductServiceImpl implements IBmsProductService
}
}
/** 0 pending / 1 active / 2 expired from dates vs today */
/** Truncate to hour precision (mm:ss = 00). */
private Date normalizeToHour(Date d)
{
if (d == null)
{
return null;
}
try
{
String s = new SimpleDateFormat("yyyy-MM-dd HH").format(d) + ":00:00";
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(s);
}
catch (Exception e)
{
return d;
}
}
/** 0 pending / 1 active / 2 expired from datetime vs now */
private String resolveStoredStatus(Date effectiveDate, Date expireDate)
{
Date today = toDateOnly(new Date());
Date expire = toDateOnly(expireDate);
Date eff = toDateOnly(effectiveDate);
if (expire != null && today != null && !expire.after(today))
Date now = new Date();
Date expire = expireDate;
Date eff = effectiveDate;
if (expire != null && !expire.after(now))
{
return BmsProduct.STATUS_EXPIRED;
}
if (eff == null || (today != null && eff.after(today)))
if (eff == null || eff.after(now))
{
return BmsProduct.STATUS_PENDING;
}
@@ -1893,6 +1930,85 @@ public class BmsProductServiceImpl implements IBmsProductService
return out;
}
/** Require import effective time (hour precision). */
private Date requireImportEffectiveDate(Date effectiveDate)
{
Date d = normalizeToHour(effectiveDate);
if (d == null)
{
throw new ServiceException(MessageUtils.message("price.product.import.effRequired"));
}
return d;
}
/**
* Reject import when selected effective time is earlier than the product's latest previous version.
* Same effective time (re-import that version) is allowed.
*/
private void assertImportEffectiveNotBeforePrev(BmsProduct product, Date newEff)
{
if (product == null || StringUtils.isEmpty(product.getProductCode()) || newEff == null)
{
return;
}
BmsProduct latest = productMapper.selectBmsProductByCode(product.getProductCode());
if (latest == null || latest.getEffectiveDate() == null)
{
return;
}
Date prevEff = normalizeToHour(latest.getEffectiveDate());
if (prevEff == null || !newEff.before(prevEff))
{
return;
}
String code = product.getProductCode();
String name = StringUtils.isNotEmpty(product.getProductName())
? product.getProductName()
: (StringUtils.isNotEmpty(latest.getProductName()) ? latest.getProductName() : "-");
throw new ServiceException(MessageUtils.message("price.product.import.effTooEarly",
code, name, formatDateTime(newEff), formatDateTime(prevEff)));
}
/** Batch pre-check: fail whole import before any DB write. */
private void assertImportProductsEffectiveNotBeforePrev(List<BmsProduct> products, Date forcedEff)
{
Date newEff = requireImportEffectiveDate(forcedEff);
if (products == null || products.isEmpty())
{
return;
}
for (BmsProduct product : products)
{
if (product == null)
{
continue;
}
product.setEffectiveDate(newEff);
assertImportEffectiveNotBeforePrev(product, newEff);
}
}
/** Stamp product + all country detail rows with the same effective date. */
private void applyForcedEffectiveDate(BmsProduct product, List<BmsProductCountry> countries, Date newEff)
{
Date eff = requireImportEffectiveDate(newEff);
if (product != null)
{
product.setEffectiveDate(eff);
}
if (countries == null)
{
return;
}
for (BmsProductCountry c : countries)
{
if (c != null)
{
c.setEffectiveDate(eff);
}
}
}
/** @return int[]{added, skipped, countryRows} */
private int[] persistImportedProduct(BmsProduct product, List<BmsProductCountry> countries, String operName)
{
@@ -1902,16 +2018,9 @@ public class BmsProductServiceImpl implements IBmsProductService
{
product.setSourceSheet(product.getProductName());
}
Date newEff = toDateOnly(product.getEffectiveDate());
if (newEff == null)
{
newEff = toDateOnly(resolveImportEffectiveDate(countries));
}
product.setEffectiveDate(newEff);
if (newEff == null)
{
throw new ServiceException(MessageUtils.message("price.product.import.effEmpty"));
}
Date newEff = requireImportEffectiveDate(product.getEffectiveDate());
applyForcedEffectiveDate(product, countries, newEff);
assertImportEffectiveNotBeforePrev(product, newEff);
BmsProduct existing = productMapper.selectBmsProductByCodeAndDate(product.getProductCode(), newEff);
if (existing != null)
{
@@ -13,6 +13,7 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -21,6 +22,8 @@ import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bms.ar.enums.ArIncomeBillFeeStatus;
import com.bms.ar.enums.ArOrderBillStatus;
import com.bms.common.annotation.DataScope;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DictUtils;
import com.bms.common.utils.MessageUtils;
@@ -29,6 +32,7 @@ import com.bms.common.utils.StringUtils;
import com.bms.common.utils.sign.Md5Utils;
import com.bms.customer.customerinfo.domain.BmsCustomer;
import com.bms.customer.customerinfo.mapper.BmsCustomerMapper;
import com.bms.system.mapper.SysUserMapper;
import com.bms.price.domain.PrcIncomeBill;
import com.bms.price.domain.PrcIncomeBillFee;
import com.bms.price.domain.PrcOrderBill;
@@ -70,6 +74,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
private static final String DICT_FEE_TYPE = "bms_fee_type";
private static final String FEE_TYPE_REFUND = "REFUND";
@Autowired
@Lazy
private IPrcOrderBillService self;
@Autowired
private ITmsPriceQuoteService tmsPriceQuoteService;
@@ -80,6 +88,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
@Autowired
private BmsCustomerMapper bmsCustomerMapper;
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private PrcOrderBillExtMapper orderBillExtMapper;
@@ -236,7 +247,12 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
}
}
/**
* 计费列表:按销售员/结算员字段做数据权限(仅本人/本部门等)。
* 编码口径同客户:belong_saler_code / settlement_man_code = sys_user.user_name(工号)或 user_id。
*/
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
public List<PrcOrderBill> selectBillList(PrcOrderBill query)
{
query.setOrderNos(normalizeCodeList(query.getOrderNos()));
@@ -248,33 +264,39 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
}
@Override
public List<String> listOrderNoOptions(String keyword)
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
public List<String> listOrderNoOptions(PrcOrderBill query)
{
if (StringUtils.isBlank(keyword))
if (query == null || query.getParams() == null
|| StringUtils.isBlank((String) query.getParams().get("optionKeyword")))
{
return new ArrayList<>();
}
return orderBillMapper.selectOrderNoOptions(keyword.trim(), 20);
return orderBillMapper.selectOrderNoOptions(query, 20);
}
@Override
public List<String> listTrackingNoOptions(String keyword)
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
public List<String> listTrackingNoOptions(PrcOrderBill query)
{
if (StringUtils.isBlank(keyword))
if (query == null || query.getParams() == null
|| StringUtils.isBlank((String) query.getParams().get("optionKeyword")))
{
return new ArrayList<>();
}
return orderBillMapper.selectTrackingNoOptions(keyword.trim(), 20);
return orderBillMapper.selectTrackingNoOptions(query, 20);
}
@Override
public List<String> listTransferNoOptions(String keyword)
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = "price:billing:bill")
public List<String> listTransferNoOptions(PrcOrderBill query)
{
if (StringUtils.isBlank(keyword))
if (query == null || query.getParams() == null
|| StringUtils.isBlank((String) query.getParams().get("optionKeyword")))
{
return new ArrayList<>();
}
return orderBillMapper.selectTransferNoOptions(keyword.trim(), 20);
return orderBillMapper.selectTransferNoOptions(query, 20);
}
/**
@@ -303,13 +325,17 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
{
throw new ServiceException(MessageUtils.message("price.bill.billIdRequired"));
}
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(billId);
if (bill == null)
PrcOrderBill query = new PrcOrderBill();
query.setBillId(billId.trim());
// 走 selectBillList,复用销售员/结算员数据权限,避免越权查详情
List<PrcOrderBill> list = self.selectBillList(query);
if (list == null || list.isEmpty())
{
throw new ServiceException(MessageUtils.message("price.bill.notFound"));
}
return toResult(bill, incomeBillFeeMapper.selectByBillId(billId),
payBillFeeMapper.selectByBillId(billId));
PrcOrderBill bill = list.get(0);
return toResult(bill, incomeBillFeeMapper.selectByBillId(bill.getBillId()),
payBillFeeMapper.selectByBillId(bill.getBillId()));
}
@Override
@@ -774,6 +800,45 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
return StringUtils.isEmpty(normalized) ? null : normalized.toUpperCase();
}
/**
* 同步订单:Oracle 销售员/结算员/客服为中文名,按 sys_user.nick_name 反查。
* 名称写 nick_name,编码写 user_name(工号,供数据权限);落到 prc_order_bill / prc_income_bill。
*/
private void resolveOrderStaffFromSysUser(TmsPriceBillRequest request)
{
if (request == null)
{
return;
}
SysUser saler = findUserByChineseName(request.getBelongSaler());
if (saler != null)
{
request.setBelongSaler(saler.getNickName());
request.setBelongSalerCode(saler.getUserName());
}
SysUser settlement = findUserByChineseName(request.getSettlementMan());
if (settlement != null)
{
request.setSettlementMan(settlement.getNickName());
request.setSettlementManCode(settlement.getUserName());
}
SysUser service = findUserByChineseName(request.getServiceMan());
if (service != null)
{
request.setServiceMan(service.getNickName());
request.setServiceManCode(service.getUserName());
}
}
private SysUser findUserByChineseName(String chineseName)
{
if (StringUtils.isBlank(chineseName))
{
return null;
}
return sysUserMapper.selectUserByNickName(chineseName.trim());
}
private void persistBill(TmsPriceBillRequest request, BigDecimal weight, Date bizDate,
TmsPriceQuoteResult result)
{
@@ -804,11 +869,14 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
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();
if (StringUtils.isEmpty(customerActiveStatus))
{
@@ -1109,6 +1177,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
fee.setFeeName(line.getFeeName());
fee.setScId(line.getScId() == null ? 0L : line.getScId());
fee.setAmount(line.getAmount() == null ? BigDecimal.ZERO : line.getAmount());
fee.setProductAmount(line.getProductAmount());
fee.setRedlineAmount(line.getRedlineAmount());
fee.setCurrency(line.getCurrency());
fee.setRoundMode(line.getRoundMode());
fee.setRemark(line.getRemark());
@@ -1301,6 +1371,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
line.setFeeName(fee.getFeeName());
line.setScId(fee.getScId());
line.setAmount(fee.getAmount());
line.setProductAmount(fee.getProductAmount());
line.setRedlineAmount(fee.getRedlineAmount());
line.setCurrency(fee.getCurrency());
line.setRoundMode(fee.getRoundMode());
line.setRemark(fee.getRemark());
@@ -1342,6 +1414,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
r.setSettlementMan(bill.getSettlementMan());
r.setSettlementManCode(bill.getSettlementManCode());
r.setServiceMan(bill.getServiceMan());
r.setServiceManCode(bill.getServiceManCode());
r.setCustomerName(bill.getCustomerName());
r.setCustomerActiveStatus(bill.getCustomerActiveStatus());
r.setCreditArrearsStatus(bill.getCreditArrearsStatus());
@@ -374,6 +374,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
throw new ServiceException(MessageUtils.message("price.set.edit.stopped"));
}
Date prevEffective = findPrevVersionEffectiveTime(version);
if (prevEffective != null && effectiveTime.before(prevEffective))
{
throw new ServiceException(MessageUtils.message("price.set.version.effectiveTime.beforePrev"));
}
// Set schedule; refreshVersionStatus decides PENDING(3) vs EFFECT(1) and chains expire_time
version.setEffectiveTime(effectiveTime);
version.setExpireTime(null);
@@ -383,6 +388,45 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return 1;
}
/** Previous version (lower version_seq) effective_time; null if none. */
private Date findPrevVersionEffectiveTime(PrcPriceSetVersion version)
{
if (version == null || version.getPriceSetId() == null)
{
return null;
}
int curSeq = version.getVersionSeq() == null ? 0 : version.getVersionSeq().intValue();
PrcPriceSetVersion q = new PrcPriceSetVersion();
q.setPriceSetId(version.getPriceSetId());
List<PrcPriceSetVersion> list = versionMapper.selectPrcPriceSetVersionList(q);
if (list == null || list.isEmpty())
{
return null;
}
PrcPriceSetVersion prev = null;
for (PrcPriceSetVersion v : list)
{
if (v == null || v.getEffectiveTime() == null)
{
continue;
}
if (version.getPsvId() != null && version.getPsvId().equals(v.getPsvId()))
{
continue;
}
int vs = v.getVersionSeq() == null ? 0 : v.getVersionSeq().intValue();
if (vs >= curSeq)
{
continue;
}
if (prev == null || vs > (prev.getVersionSeq() == null ? 0 : prev.getVersionSeq().intValue()))
{
prev = v;
}
}
return prev == null ? null : prev.getEffectiveTime();
}
@Override
@Transactional
public Long copyVersion(Long sourcePsvId, String createBy)
@@ -430,6 +474,35 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return nv.getPsvId();
}
@Override
@Transactional
public Long createVersion(Long priceSetId, String createBy)
{
PrcPriceSet set = requireSet(priceSetId);
if (STATUS_STOP.equals(set.getStatus()))
{
throw new ServiceException(MessageUtils.message("price.set.edit.stopped"));
}
if (hasDraftVersion(set.getPriceSetId()))
{
throw new ServiceException(MessageUtils.message("price.set.version.create.draftExists"));
}
Integer maxSeq = versionMapper.selectMaxVersionSeq(set.getPriceSetId());
int seq = maxSeq == null ? 1 : maxSeq.intValue() + 1;
PrcPriceSetVersion nv = new PrcPriceSetVersion();
nv.setPriceSetId(set.getPriceSetId());
nv.setVersionSeq(Integer.valueOf(seq));
nv.setVersionNo("V" + seq);
nv.setStatus(VER_DRAFT);
nv.setEffectiveTime(null);
nv.setExpireTime(null);
nv.setPriceBuildMode(null);
nv.setCreateBy(createBy);
versionMapper.insertPrcPriceSetVersion(nv);
refreshVersionStatus(set.getPriceSetId());
return nv.getPsvId();
}
@Override
@Transactional
public int deleteVersion(Long psvId, String updateBy)
@@ -957,8 +957,12 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
r.setPayTotal(productAmount);
String currency = normalizeCurrency(r.getCurrency());
List<TmsPriceQuoteFeeLine> payLines = new ArrayList<TmsPriceQuoteFeeLine>();
payLines.add(feeLine(FEE_BASIC, feeNameOf(FEE_BASIC), 0L, productFreight, currency, roundMode, null));
payLines.add(feeLine(FEE_REG, feeNameOf(FEE_REG), 0L, baseReg, currency, roundMode, null));
TmsPriceQuoteFeeLine payFreight = feeLine(FEE_BASIC, feeNameOf(FEE_BASIC), 0L, productFreight, currency, roundMode, null);
payFreight.setProductAmount(productFreight);
payLines.add(payFreight);
TmsPriceQuoteFeeLine payReg = feeLine(FEE_REG, feeNameOf(FEE_REG), 0L, baseReg.setScale(2, rm), currency, roundMode, null);
payReg.setProductAmount(baseReg.setScale(2, rm));
payLines.add(payReg);
r.setPayFeeDetails(payLines);
}
@@ -971,8 +975,14 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
List<PrcSurchargeRel> rels = loadMatchedSurcharges(result);
List<TmsPriceQuoteFeeLine> lines = new ArrayList<TmsPriceQuoteFeeLine>();
lines.add(feeLine(FEE_BASIC, feeNameOf(FEE_BASIC), 0L, freightAmount, currency, result.getRoundMode(), null));
lines.add(feeLine(FEE_REG, feeNameOf(FEE_REG), 0L, registrationFee, currency, result.getRoundMode(), null));
BigDecimal productFreight = productAmountOf(result.getPayFeeDetails(), FEE_BASIC);
BigDecimal productReg = productAmountOf(result.getPayFeeDetails(), FEE_REG);
TmsPriceQuoteFeeLine basicLine = feeLine(FEE_BASIC, feeNameOf(FEE_BASIC), 0L, freightAmount, currency, result.getRoundMode(), null);
basicLine.setProductAmount(productFreight);
lines.add(basicLine);
TmsPriceQuoteFeeLine regLine = feeLine(FEE_REG, feeNameOf(FEE_REG), 0L, registrationFee, currency, result.getRoundMode(), null);
regLine.setProductAmount(productReg);
lines.add(regLine);
BigDecimal surchargeTotal = BigDecimal.ZERO;
Map<Long, TmsPriceQuoteFeeLine> surchargeLines = new LinkedHashMap<Long, TmsPriceQuoteFeeLine>();
@@ -1382,6 +1392,23 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return Md5Utils.hash(raw);
}
/** Read catalog product amount for a fee type from payFeeDetails. */
private static BigDecimal productAmountOf(List<TmsPriceQuoteFeeLine> payLines, String feeType)
{
if (payLines == null || feeType == null)
{
return null;
}
for (TmsPriceQuoteFeeLine line : payLines)
{
if (line != null && feeType.equals(line.getFeeType()))
{
return line.getProductAmount() != null ? line.getProductAmount() : line.getAmount();
}
}
return null;
}
private static TmsPriceQuoteFeeLine feeLine(String feeType, String feeName, Long scId, BigDecimal amount,
String currency, String roundMode, String remark)
{
@@ -1,5 +1,6 @@
package com.bms.price.task;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,12 +31,14 @@ public class BmsProductImportAsyncRunner
/** Background all-country (Weishi catalog) import. */
@Async("threadPoolTaskExecutor")
public void runImportAll(String taskId, byte[] bytes, String filename, boolean updateSupport, String operName)
public void runImportAll(String taskId, byte[] bytes, String filename, boolean updateSupport, String operName,
Date effectiveDate)
{
try
{
put(taskId, BmsProductImportTask.STATUS_RUNNING, 5, "parsing excel", null);
String message = productService.importProductExcel(bytes, filename, updateSupport, operName, taskId);
String message = productService.importProductExcel(bytes, filename, updateSupport, operName, taskId,
effectiveDate);
put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, message, null);
log.info("async product import done, taskId={}", taskId);
}
@@ -48,12 +51,14 @@ public class BmsProductImportAsyncRunner
/** Background single-country cost-sheet import. */
@Async("threadPoolTaskExecutor")
public void runImportCountry(String taskId, byte[] bytes, String filename, String countryCode, String operName)
public void runImportCountry(String taskId, byte[] bytes, String filename, String countryCode, String operName,
Date effectiveDate)
{
try
{
put(taskId, BmsProductImportTask.STATUS_RUNNING, 5, "parsing excel", null);
String message = productService.importCountryProductExcel(bytes, filename, countryCode, operName, taskId);
String message = productService.importCountryProductExcel(bytes, filename, countryCode, operName, taskId,
effectiveDate);
put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, message, null);
log.info("async product country import done, taskId={}, country={}", taskId, countryCode);
}
@@ -51,6 +51,14 @@ public interface SysUserMapper
*/
public SysUser selectUserByUserName(String userName);
/**
* 通过中文昵称(nick_name)精确查用户(未删除,优先启用)
*
* @param nickName 中文名称
* @return 用户对象信息
*/
public SysUser selectUserByNickName(String nickName);
/**
* 通过用户ID查询用户
*