Merge branch 'master' of http://git.lionquexpress.com/wuweihua/BMS
This commit is contained in:
@@ -141,6 +141,8 @@ price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细
|
||||
price.bill.orderNoRequired=订单号为空
|
||||
price.bill.billIdRequired=计费ID为空
|
||||
price.bill.notFound=计费记录不存在
|
||||
price.bill.customerRequired=订单同步缺少有效客户
|
||||
price.bill.alreadyInArBill=订单{0}已生成应收账单,作废后方可重新计费
|
||||
price.bill.bizDateRequired=业务日期为空
|
||||
|
||||
price.set.version.effectiveTime.empty=版本生效时间不能为空
|
||||
|
||||
+12
-8
@@ -247,8 +247,8 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
validateUsageCommand(command);
|
||||
Long customerId = resolveCustomerId(command);
|
||||
String currency = resolveCurrency(command.getCurrency());
|
||||
String username = SecurityUtils.getUsername();
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String username = currentUsername();
|
||||
Long userId = currentUserId();
|
||||
CustomerAccount account = lockAccountForUsage(customerId, currency, username);
|
||||
if (account == null)
|
||||
{
|
||||
@@ -282,8 +282,8 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
validateUsageCommand(command);
|
||||
Long customerId = resolveCustomerId(command);
|
||||
String currency = resolveCurrency(command.getCurrency());
|
||||
String username = SecurityUtils.getUsername();
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String username = currentUsername();
|
||||
Long userId = currentUserId();
|
||||
CustomerAccount account = lockAccountForUsage(customerId, currency, username);
|
||||
if (account == null)
|
||||
{
|
||||
@@ -720,19 +720,23 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
|
||||
private String bizTypeLabel(String bizType)
|
||||
{
|
||||
if (CustomerFundBizType.RECEIPT.equals(bizType))
|
||||
{
|
||||
return "\u6536\u6b3e";
|
||||
}
|
||||
if (CustomerFundBizType.AR_BILL.equals(bizType))
|
||||
{
|
||||
return "应收账单";
|
||||
return "\u5e94\u6536\u8d26\u5355";
|
||||
}
|
||||
if (CustomerFundBizType.ORDER.equals(bizType))
|
||||
{
|
||||
return "计费订单";
|
||||
return "\u8ba1\u8d39\u8ba2\u5355";
|
||||
}
|
||||
if (CustomerFundBizType.BILL_PAY.equals(bizType))
|
||||
{
|
||||
return "账单核销";
|
||||
return "\u8d26\u5355\u6838\u9500";
|
||||
}
|
||||
return "业务";
|
||||
return "\u4e1a\u52a1";
|
||||
}
|
||||
|
||||
/** 临时额度是否已过期:有失效日期且今天已晚于失效日(失效日当天仍有效) */
|
||||
|
||||
@@ -8,6 +8,8 @@ public final class EsLogType
|
||||
{
|
||||
public static final String BILL = "BILL";
|
||||
public static final String QUOTE = "QUOTE";
|
||||
/** Order credit occupy/release */
|
||||
public static final String CREDIT = "CREDIT";
|
||||
|
||||
private EsLogType()
|
||||
{
|
||||
|
||||
+129
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.ar.enums.ArOrderBillStatus;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
@@ -28,6 +29,9 @@ import com.ruoyi.price.mapper.PrcPayBillFeeMapper;
|
||||
import com.ruoyi.price.search.EsBizLogSupport;
|
||||
import com.ruoyi.price.search.EsLogType;
|
||||
import com.ruoyi.price.search.IBillSearchStore;
|
||||
import com.ruoyi.customer.customermoney.api.CreditUsageCommand;
|
||||
import com.ruoyi.customer.customermoney.api.CustomerFundBizType;
|
||||
import com.ruoyi.customer.customermoney.api.ICustomerAccountApi;
|
||||
import com.ruoyi.price.service.IPrcOrderBillService;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
@@ -49,6 +53,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
@Autowired
|
||||
private IBillSearchStore billSearchStore;
|
||||
|
||||
@Autowired
|
||||
private ICustomerAccountApi customerAccountApi;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult bill(TmsPriceBillRequest request)
|
||||
@@ -65,6 +72,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
throw new ServiceException(MessageUtils.message("price.bill.bizDateRequired"));
|
||||
}
|
||||
Date bizDate = resolveBizDate(request.getBizDate());
|
||||
assertOrderBillEditable(request.getOrderNo());
|
||||
BigDecimal weight = request.getWeight();
|
||||
TmsPriceQuoteResult result = tmsPriceQuoteService.calculate(request);
|
||||
persistBill(request, weight, bizDate, result);
|
||||
@@ -121,6 +129,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
String orderNo = request.getOrderNo();
|
||||
String billKey = Md5Utils.hash(orderNo);
|
||||
PrcOrderBill existed = orderBillMapper.selectPrcOrderBillByOrderNo(orderNo);
|
||||
assertOrderBillEditable(existed);
|
||||
BigDecimal previousIncome = existed == null ? BigDecimal.ZERO : nz(existed.getIncomeTotal());
|
||||
PrcOrderBill bill = existed == null ? new PrcOrderBill() : existed;
|
||||
if (existed == null)
|
||||
{
|
||||
@@ -197,6 +207,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
result.setFromCache(Boolean.valueOf(existed != null));
|
||||
if (existed == null)
|
||||
{
|
||||
bill.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode());
|
||||
orderBillMapper.insertPrcOrderBill(bill);
|
||||
}
|
||||
else
|
||||
@@ -209,9 +220,127 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
{
|
||||
incomeBillFeeMapper.insertBatch(fees);
|
||||
}
|
||||
syncOrderCredit(bill, previousIncome);
|
||||
billSearchStore.save(EsLogType.BILL, orderNo, toRequestJson(request), toResponseJson(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Occupy/release credit by income total delta (bizType=ORDER).
|
||||
* Always write CREDIT log to ES (SUCCESS / FAIL), then rethrow on failure.
|
||||
*/
|
||||
private void syncOrderCredit(PrcOrderBill bill, BigDecimal previousIncome)
|
||||
{
|
||||
if (bill == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Long customerId = bill.getCustomerId();
|
||||
if (customerId == null || customerId.longValue() <= 0L)
|
||||
{
|
||||
ServiceException ex = new ServiceException(MessageUtils.message("price.bill.customerRequired"));
|
||||
saveCreditEsLog(bill, null, "SKIP", false, EsBizLogSupport.errorText(ex));
|
||||
throw ex;
|
||||
}
|
||||
BigDecimal oldAmt = nz(previousIncome);
|
||||
BigDecimal newAmt = nz(bill.getIncomeTotal());
|
||||
BigDecimal delta = newAmt.subtract(oldAmt);
|
||||
if (delta.compareTo(BigDecimal.ZERO) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CreditUsageCommand cmd = new CreditUsageCommand();
|
||||
cmd.setCustomerId(customerId);
|
||||
cmd.setCustomerCode(bill.getCustomerCode());
|
||||
cmd.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency());
|
||||
cmd.setBizType(CustomerFundBizType.ORDER);
|
||||
cmd.setBizNo(bill.getOrderNo());
|
||||
cmd.setCheckAvailable(Boolean.TRUE);
|
||||
String action = delta.compareTo(BigDecimal.ZERO) > 0 ? "OCCUPY" : "RELEASE";
|
||||
try
|
||||
{
|
||||
if (delta.compareTo(BigDecimal.ZERO) > 0)
|
||||
{
|
||||
cmd.setAmount(delta);
|
||||
customerAccountApi.occupyCredit(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.setAmount(delta.abs());
|
||||
customerAccountApi.releaseCredit(cmd);
|
||||
}
|
||||
saveCreditEsLog(bill, cmd, action, true, null);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
saveCreditEsLog(bill, cmd, action, false, EsBizLogSupport.errorText(e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/** CREDIT ES log; does not set ATTR_FAIL_LOGGED so BILL fail can still be written. */
|
||||
private void saveCreditEsLog(PrcOrderBill bill, CreditUsageCommand cmd, String action, boolean success,
|
||||
String errorMessage)
|
||||
{
|
||||
if (billSearchStore == null || bill == null || StringUtils.isEmpty(bill.getOrderNo()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("orderNo", bill.getOrderNo());
|
||||
req.put("billId", bill.getBillId());
|
||||
req.put("customerId", bill.getCustomerId());
|
||||
req.put("customerCode", bill.getCustomerCode());
|
||||
req.put("currency", bill.getCurrency());
|
||||
req.put("incomeTotal", bill.getIncomeTotal());
|
||||
req.put("action", action);
|
||||
req.put("bizType", CustomerFundBizType.ORDER);
|
||||
if (cmd != null)
|
||||
{
|
||||
req.put("amount", cmd.getAmount());
|
||||
req.put("checkAvailable", cmd.getCheckAvailable());
|
||||
}
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("success", Boolean.valueOf(success));
|
||||
if (success)
|
||||
{
|
||||
resp.put("msg", action);
|
||||
}
|
||||
else
|
||||
{
|
||||
resp.put("msg", errorMessage);
|
||||
}
|
||||
String status = success ? EsBizLogSupport.STATUS_SUCCESS : EsBizLogSupport.STATUS_FAIL;
|
||||
String err = success ? null : EsBizLogSupport.cut(errorMessage);
|
||||
billSearchStore.save(EsLogType.CREDIT, bill.getOrderNo(), req.toJSONString(), resp.toJSONString(), status, err);
|
||||
}
|
||||
|
||||
/** Block re-bill when order already claimed by an AR bill (BILLED). Void restores UNBILLED. */
|
||||
private void assertOrderBillEditable(String orderNo)
|
||||
{
|
||||
if (StringUtils.isEmpty(orderNo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
assertOrderBillEditable(orderBillMapper.selectPrcOrderBillByOrderNo(orderNo.trim()));
|
||||
}
|
||||
|
||||
private void assertOrderBillEditable(PrcOrderBill existed)
|
||||
{
|
||||
if (existed == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ArOrderBillStatus.BILLED.getCode().equals(existed.getArBillStatus()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.alreadyInArBill", existed.getOrderNo()));
|
||||
}
|
||||
}
|
||||
|
||||
private static BigDecimal nz(BigDecimal v)
|
||||
{
|
||||
return v == null ? BigDecimal.ZERO : v;
|
||||
}
|
||||
|
||||
private List<PrcIncomeBillFee> toFeeRows(String billId, TmsPriceQuoteResult result)
|
||||
{
|
||||
List<PrcIncomeBillFee> fees = new ArrayList<PrcIncomeBillFee>();
|
||||
|
||||
@@ -167,7 +167,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
set_type, bind_priority, customer_priceset_id, product_country_id,
|
||||
weight_min, weight_max, weight_label, effective_date, charge_weight,
|
||||
freight_rate, base_freight_rate, discount_percent, registration_fee,
|
||||
freight_amount, surcharge_amount, income_total, pay_total, currency, round_mode,
|
||||
freight_amount, surcharge_amount, income_total, pay_total, currency, ar_bill_status, round_mode,
|
||||
create_time
|
||||
) values (
|
||||
#{billId}, #{billKey}, #{orderNo}, #{trackingNo}, #{customerId}, #{customerCode}, #{customerName},
|
||||
@@ -178,7 +178,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{setType}, #{bindPriority}, #{customerPricesetId}, #{productCountryId},
|
||||
#{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight},
|
||||
#{freightRate}, #{baseFreightRate}, #{discountPercent}, #{registrationFee},
|
||||
#{freightAmount}, #{surchargeAmount}, #{incomeTotal}, #{payTotal}, #{currency}, #{roundMode},
|
||||
#{freightAmount}, #{surchargeAmount}, #{incomeTotal}, #{payTotal}, #{currency}, #{arBillStatus}, #{roundMode},
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- customer fund biz_type dict (CustomerFundBizType)
|
||||
-- dict_type: customer_fund_biz_type
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
SELECT '客户资金业务类型', 'customer_fund_biz_type', '0', 'admin', sysdate(), 'ICustomerAccountApi bizType'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'customer_fund_biz_type');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 1, '收款', 'RECEIPT', 'customer_fund_biz_type', '', 'success', 'N', '0', 'admin', sysdate(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'customer_fund_biz_type' AND dict_value = 'RECEIPT');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 2, '应收账单', 'AR_BILL', 'customer_fund_biz_type', '', 'primary', 'N', '0', 'admin', sysdate(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'customer_fund_biz_type' AND dict_value = 'AR_BILL');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 3, '账单核销', 'BILL_PAY', 'customer_fund_biz_type', '', 'danger', 'N', '0', 'admin', sysdate(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'customer_fund_biz_type' AND dict_value = 'BILL_PAY');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 4, '计费订单', 'ORDER', 'customer_fund_biz_type', '', 'warning', 'Y', '0', 'admin', sysdate(), 'TMS bill sync occupy/release'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'customer_fund_biz_type' AND dict_value = 'ORDER');
|
||||
@@ -943,7 +943,9 @@ const payMethodOptions = [
|
||||
]
|
||||
const bizTypeOptions = [
|
||||
{ value: 'RECEIPT', label: '收款' },
|
||||
{ value: 'BILL_PAY', label: '账单支付' },
|
||||
{ value: 'AR_BILL', label: '应收账单' },
|
||||
{ value: 'BILL_PAY', label: '账单核销' },
|
||||
{ value: 'ORDER', label: '计费订单' },
|
||||
{ value: 'REFUND', label: '退款' },
|
||||
{ value: 'ADJUST', label: '手工调整' },
|
||||
{ value: 'FREEZE', label: '冻结' },
|
||||
@@ -973,7 +975,7 @@ function receiptStatusTag(v) {
|
||||
return { PENDING: 'warning', CONFIRMED: 'success', CANCELLED: 'danger' }[v] || 'info'
|
||||
}
|
||||
const bizTypeTag = function(v) {
|
||||
return { RECEIPT: 'success', BILL_PAY: 'danger', REFUND: 'warning', ADJUST: 'info', FREEZE: 'info', UNFREEZE: '' }[v] || 'info'
|
||||
return { RECEIPT: 'success', AR_BILL: 'primary', BILL_PAY: 'danger', ORDER: 'warning', REFUND: 'warning', ADJUST: 'info', FREEZE: 'info', UNFREEZE: '' }[v] || 'info'
|
||||
}
|
||||
|
||||
/* ================= 额度相关枚举 ================= */
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="业务ID">{{ detail.bizId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="写入时间">{{ detail.updateTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.logType === 'CREDIT'" label="扣减动作">
|
||||
{{ creditActionLabel(detail) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.logType === 'CREDIT'" label="金额">
|
||||
{{ creditAmountLabel(detail) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.errorMessage" label="错误信息" :span="2">
|
||||
<span class="err-text">{{ detail.errorMessage }}</span>
|
||||
</el-descriptions-item>
|
||||
@@ -66,7 +72,8 @@ const drawerTitle = ref('ES日志详情')
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '计费', value: 'BILL' },
|
||||
{ label: '试算', value: 'QUOTE' }
|
||||
{ label: '试算', value: 'QUOTE' },
|
||||
{ label: '扣减额度', value: 'CREDIT' }
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
@@ -97,9 +104,31 @@ function typeLabel(type) {
|
||||
return found ? found.label : (type || '-')
|
||||
}
|
||||
|
||||
function parseReq(detail) {
|
||||
if (!detail || !detail.requestJson) return {}
|
||||
try {
|
||||
return typeof detail.requestJson === 'object' ? detail.requestJson : JSON.parse(detail.requestJson)
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function creditActionLabel(detail) {
|
||||
const req = parseReq(detail)
|
||||
const map = { OCCUPY: '占用', RELEASE: '释放', SKIP: '跳过' }
|
||||
return map[req.action] || req.action || '-'
|
||||
}
|
||||
|
||||
function creditAmountLabel(detail) {
|
||||
const req = parseReq(detail)
|
||||
if (req.amount == null || req.amount === '') return '-'
|
||||
return String(req.amount) + (req.currency ? (' ' + req.currency) : '')
|
||||
}
|
||||
|
||||
function typeTag(type) {
|
||||
if (type === 'BILL') return 'success'
|
||||
if (type === 'QUOTE') return 'warning'
|
||||
if (type === 'CREDIT') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user