应收账单模块,调整枚举代码优化

This commit is contained in:
caojia
2026-09-17 11:00:59 +08:00
parent 5207c00574
commit a17f09683b
26 changed files with 397 additions and 140 deletions
@@ -22,11 +22,6 @@ public class ArBalanceWriteoff extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 有效 */
public static final String STATUS_EFFECTIVE = "EFFECTIVE";
/** 已取消(反核销) */
public static final String STATUS_CANCELLED = "CANCELLED";
/** 余额核销ID */
private Long writeoffId;
@@ -61,7 +56,7 @@ public class ArBalanceWriteoff extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd")
private Date writeoffDate;
/** 状态:EFFECTIVE/CANCELLED */
/** 状态:EFFECTIVE(有效)/CANCELLED(已反核销),取值由 ArBalanceWriteoffStatus 维护。 */
private String status;
/** 核销备注 */
@@ -36,6 +36,7 @@ public class ArBill extends BaseEntity
private BigDecimal totalAmount;
private BigDecimal writtenOffAmount;
private BigDecimal unwrittenAmount;
/** 状态:DRAFT草稿、CONFIRMED已确认、PARTIAL_WRITTEN_OFF部分核销、WRITTEN_OFF已核销、VOID已作废。 */
private String status;
private Integer billVersion;
private String bankAccountName;
@@ -21,6 +21,7 @@ public class ArBillExport implements Serializable
private Long fileSize;
private String fileHash;
private Integer rowCount;
/** 导出状态:PROCESSING生成中、SUCCESS成功可下载、FAILED失败。 */
private String exportStatus;
private String exportBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -13,10 +13,12 @@ public class ArBillLog implements Serializable
private Long logId;
private Long arBillId;
/** 对象类型:BILL应收账单、EXPORT账单导出、BALANCE_WRITEOFF余额核销。 */
private String objectType;
private String objectId;
/** 对象业务标识,例如账单号、收款单号或核销单号。 */
private String objectDisplay;
/** 操作类型,取值由 ArLogOperationType 维护。 */
private String operationType;
private String operationDesc;
private String fromStatus;
@@ -25,9 +27,11 @@ public class ArBillLog implements Serializable
private String afterData;
private String operatorId;
private String operatorName;
/** 操作来源:WEB网页端、API接口、JOB定时任务、IMPORT数据导入。 */
private String operationSource;
private String requestId;
private String traceId;
/** 操作结果:SUCCESS成功、FAILED失败。 */
private String result;
private String errorMessage;
private String clientIp;
@@ -0,0 +1,50 @@
package com.ruoyi.ar.enums;
/**
* 应收账单余额核销单状态。
*
* <p>数据库保存英文编码,页面通过前端枚举展示中文名称。</p>
*/
public enum ArBalanceWriteoffStatus
{
/** 核销已生效,余额已扣减。 */
EFFECTIVE("EFFECTIVE", "有效"),
/** 核销已取消,余额已回补。 */
CANCELLED("CANCELLED", "已反核销");
private final String code;
private final String label;
ArBalanceWriteoffStatus(String code, String label)
{
this.code = code;
this.label = label;
}
public String getCode()
{
return code;
}
public String getLabel()
{
return label;
}
public boolean isEffective()
{
return this == EFFECTIVE;
}
public static ArBalanceWriteoffStatus of(String code)
{
for (ArBalanceWriteoffStatus status : values())
{
if (status.code.equalsIgnoreCase(code))
{
return status;
}
}
return null;
}
}
@@ -1,21 +1,52 @@
package com.ruoyi.ar.enums;
/** 应收账单导出任务状态。 */
/**
* 应收账单导出任务状态。
*
* <p>PROCESSING 表示处理中,SUCCESS 表示生成成功,FAILED 表示生成失败。</p>
*/
public enum ArExportStatus
{
PROCESSING("PROCESSING"),
SUCCESS("SUCCESS"),
FAILED("FAILED");
/** 文件正在生成。 */
PROCESSING("PROCESSING", "生成中"),
/** 文件已生成,可以下载。 */
SUCCESS("SUCCESS", "成功"),
/** 文件生成失败。 */
FAILED("FAILED", "失败");
private final String code;
private final String label;
ArExportStatus(String code)
ArExportStatus(String code, String label)
{
this.code = code;
this.label = label;
}
public String getCode()
{
return code;
}
public String getLabel()
{
return label;
}
public boolean isSuccess()
{
return this == SUCCESS;
}
public static ArExportStatus of(String code)
{
for (ArExportStatus status : values())
{
if (status.code.equalsIgnoreCase(code))
{
return status;
}
}
return null;
}
}
@@ -0,0 +1,31 @@
package com.ruoyi.ar.enums;
/** 应收日志关联对象类型。 */
public enum ArLogObjectType
{
/** 应收账单主对象。 */
BILL("BILL", "应收账单"),
/** 账单导出文件对象。 */
EXPORT("EXPORT", "账单导出"),
/** 客户余额核销单对象。 */
BALANCE_WRITEOFF("BALANCE_WRITEOFF", "余额核销");
private final String code;
private final String label;
ArLogObjectType(String code, String label)
{
this.code = code;
this.label = label;
}
public String getCode()
{
return code;
}
public String getLabel()
{
return label;
}
}
@@ -0,0 +1,33 @@
package com.ruoyi.ar.enums;
/** 应收日志操作来源。 */
public enum ArLogOperationSource
{
/** 浏览器页面操作。 */
WEB("WEB", "网页端"),
/** 开放接口或系统间接口调用。 */
API("API", "接口"),
/** 定时任务或后台作业。 */
JOB("JOB", "定时任务"),
/** 数据导入操作。 */
IMPORT("IMPORT", "数据导入");
private final String code;
private final String label;
ArLogOperationSource(String code, String label)
{
this.code = code;
this.label = label;
}
public String getCode()
{
return code;
}
public String getLabel()
{
return label;
}
}
@@ -0,0 +1,39 @@
package com.ruoyi.ar.enums;
/** 应收日志操作类型。 */
public enum ArLogOperationType
{
/** 生成应收账单成功。 */
CREATE("CREATE", "生成账单"),
/** 生成账单前的校验或生成失败记录。 */
GENERATE("GENERATE", "生成账单校验"),
/** 确认草稿账单。 */
CONFIRM("CONFIRM", "确认账单"),
/** 作废账单并释放来源订单。 */
VOID("VOID", "作废账单"),
/** 生成账单导出文件。 */
EXPORT("EXPORT", "导出账单"),
/** 使用客户余额核销账单。 */
BALANCE_WRITEOFF("BALANCE_WRITEOFF", "余额核销"),
/** 取消余额核销并回补客户余额。 */
CANCEL_BALANCE_WRITEOFF("CANCEL_BALANCE_WRITEOFF", "取消余额核销");
private final String code;
private final String label;
ArLogOperationType(String code, String label)
{
this.code = code;
this.label = label;
}
public String getCode()
{
return code;
}
public String getLabel()
{
return label;
}
}
@@ -3,18 +3,39 @@ package com.ruoyi.ar.enums;
/** 应收操作日志结果。 */
public enum ArLogResult
{
SUCCESS("SUCCESS"),
FAILED("FAILED");
/** 操作执行成功。 */
SUCCESS("SUCCESS", "成功"),
/** 操作执行失败。 */
FAILED("FAILED", "失败");
private final String code;
private final String label;
ArLogResult(String code)
ArLogResult(String code, String label)
{
this.code = code;
this.label = label;
}
public String getCode()
{
return code;
}
public String getLabel()
{
return label;
}
public static ArLogResult of(String code)
{
for (ArLogResult result : values())
{
if (result.code.equalsIgnoreCase(code))
{
return result;
}
}
return null;
}
}
@@ -27,7 +27,9 @@ public interface ArBalanceWriteoffMapper
int updateCancelled(@Param("writeoffId") Long writeoffId,
@Param("reverseFlowId") Long reverseFlowId,
@Param("cancelBy") String cancelBy,
@Param("cancelRemark") String cancelRemark);
@Param("cancelRemark") String cancelRemark,
@Param("status") String status,
@Param("effectiveStatus") String effectiveStatus);
/** 查询核销记录列表(关联账单名称/客户名称) */
List<ArBalanceWriteoff> selectWriteoffList(ArBalanceWriteoff query);
@@ -9,7 +9,8 @@ public interface ArBillLogMapper
List<ArBillLog> selectList(ArBillLog query);
ArBillLog selectSuccessByRequestId(@Param("requestId") String requestId,
@Param("objectType") String objectType);
@Param("objectType") String objectType,
@Param("result") String result);
int insert(ArBillLog log);
}
@@ -2,6 +2,8 @@ package com.ruoyi.ar.service;
import java.util.List;
import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.enums.ArLogObjectType;
import com.ruoyi.ar.enums.ArLogOperationType;
public interface IArBillLogService
{
@@ -9,6 +11,6 @@ public interface IArBillLogService
void record(ArBillLog log);
void recordFailure(Long arBillId, String objectType, String objectId, String operationType,
void recordFailure(Long arBillId, ArLogObjectType objectType, String objectId, ArLogOperationType operationType,
String fromStatus, String message, String operator, String requestId);
}
@@ -14,7 +14,11 @@ import org.springframework.util.StringUtils;
import com.ruoyi.ar.domain.ArBalanceWriteoff;
import com.ruoyi.ar.domain.ArBill;
import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.enums.ArBalanceWriteoffStatus;
import com.ruoyi.ar.enums.ArBillStatus;
import com.ruoyi.ar.enums.ArLogObjectType;
import com.ruoyi.ar.enums.ArLogOperationSource;
import com.ruoyi.ar.enums.ArLogOperationType;
import com.ruoyi.ar.enums.ArLogResult;
import com.ruoyi.ar.mapper.ArBalanceWriteoffMapper;
import com.ruoyi.ar.mapper.ArBillMapper;
@@ -119,7 +123,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService
writeoff.setCurrency(currency);
writeoff.setWriteoffAmount(amount);
writeoff.setWriteoffDate(writeoffDate);
writeoff.setStatus(ArBalanceWriteoff.STATUS_EFFECTIVE);
writeoff.setStatus(ArBalanceWriteoffStatus.EFFECTIVE.getCode());
writeoff.setRemark(StringUtils.hasText(cmd.getRemark()) ? cmd.getRemark().trim() : null);
writeoff.setCreateBy(operator);
writeoffMapper.insertArBalanceWriteoff(writeoff);
@@ -148,7 +152,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService
throw new ServiceException("账单状态已发生变化,请刷新后重试");
}
recordLog(bill.getArBillId(), writeoff.getWriteoffId(), "BALANCE_WRITEOFF",
recordLog(bill.getArBillId(), writeoff.getWriteoffId(), ArLogOperationType.BALANCE_WRITEOFF,
"账户余额核销账单 " + amount.toPlainString() + " " + currency,
fromStatus, nextStatus, operator, requestId);
writeoff.setAccountId(flow.getAccountId());
@@ -170,7 +174,7 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService
{
throw new ServiceException("核销记录不存在");
}
if (!ArBalanceWriteoff.STATUS_EFFECTIVE.equals(writeoff.getStatus()))
if (!ArBalanceWriteoffStatus.EFFECTIVE.getCode().equals(writeoff.getStatus()))
{
throw new ServiceException("只有有效状态的核销记录可以反核销");
}
@@ -213,11 +217,12 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService
}
// 4. 核销单置为CANCELLED并登记回补流水
if (writeoffMapper.updateCancelled(writeoffId, reverseFlow.getId(), operator, cancelRemark) != 1)
if (writeoffMapper.updateCancelled(writeoffId, reverseFlow.getId(), operator, cancelRemark,
ArBalanceWriteoffStatus.CANCELLED.getCode(), ArBalanceWriteoffStatus.EFFECTIVE.getCode()) != 1)
{
throw new ServiceException("核销记录状态已变化,请刷新后重试");
}
recordLog(bill.getArBillId(), writeoffId, "CANCEL_BALANCE_WRITEOFF",
recordLog(bill.getArBillId(), writeoffId, ArLogOperationType.CANCEL_BALANCE_WRITEOFF,
"反核销回补账单:" + cancelRemark,
fromStatus, nextStatus, operator, requestId);
}
@@ -234,20 +239,20 @@ public class ArBalanceWriteoffServiceImpl implements IArBalanceWriteoffService
? ArBillStatus.WRITTEN_OFF.getCode() : ArBillStatus.PARTIAL_WRITTEN_OFF.getCode();
}
private void recordLog(Long billId, Long writeoffId, String operationType, String desc,
private void recordLog(Long billId, Long writeoffId, ArLogOperationType operationType, String desc,
String fromStatus, String toStatus, String operator, String requestId)
{
ArBillLog log = new ArBillLog();
log.setArBillId(billId);
log.setObjectType("BALANCE_WRITEOFF");
log.setObjectType(ArLogObjectType.BALANCE_WRITEOFF.getCode());
log.setObjectId(String.valueOf(writeoffId));
log.setOperationType(operationType);
log.setOperationType(operationType.getCode());
log.setOperationDesc(desc);
log.setFromStatus(fromStatus);
log.setToStatus(toStatus);
log.setOperatorId(operator);
log.setOperatorName(operator);
log.setOperationSource("WEB");
log.setOperationSource(ArLogOperationSource.WEB.getCode());
log.setRequestId(requestId);
log.setResult(ArLogResult.SUCCESS.getCode());
logService.record(log);
@@ -6,6 +6,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.enums.ArLogObjectType;
import com.ruoyi.ar.enums.ArLogOperationSource;
import com.ruoyi.ar.enums.ArLogOperationType;
import com.ruoyi.ar.enums.ArLogResult;
import com.ruoyi.ar.mapper.ArBillLogMapper;
import com.ruoyi.ar.service.IArBillLogService;
@@ -32,19 +35,19 @@ public class ArBillLogServiceImpl implements IArBillLogService
/** 失败日志必须独立提交,不能随业务异常一起回滚。 */
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordFailure(Long arBillId, String objectType, String objectId, String operationType,
public void recordFailure(Long arBillId, ArLogObjectType objectType, String objectId, ArLogOperationType operationType,
String fromStatus, String message, String operator, String requestId)
{
ArBillLog log = new ArBillLog();
log.setArBillId(arBillId);
log.setObjectType(objectType);
log.setObjectType(objectType.getCode());
log.setObjectId(objectId);
log.setOperationType(operationType);
log.setOperationType(operationType.getCode());
log.setOperationDesc(message);
log.setFromStatus(fromStatus);
log.setOperatorId(operator);
log.setOperatorName(operator);
log.setOperationSource("WEB");
log.setOperationSource(ArLogOperationSource.WEB.getCode());
log.setRequestId(requestId == null ? IdUtils.fastSimpleUUID() : requestId);
log.setResult(ArLogResult.FAILED.getCode());
log.setErrorMessage(message);
@@ -37,6 +37,9 @@ import com.ruoyi.ar.domain.ArBillExport;
import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.enums.ArBillStatus;
import com.ruoyi.ar.enums.ArExportStatus;
import com.ruoyi.ar.enums.ArLogObjectType;
import com.ruoyi.ar.enums.ArLogOperationSource;
import com.ruoyi.ar.enums.ArLogOperationType;
import com.ruoyi.ar.enums.ArLogResult;
import com.ruoyi.ar.enums.ArOrderBillStatus;
import com.ruoyi.ar.mapper.ArBillDetailFeeMapper;
@@ -135,7 +138,8 @@ public class ArBillServiceImpl implements IArBillService
public ArBill generateBill(ArBillGenerateRequest request, String operator, String requestId)
{
validatePeriod(request);
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "BILL");
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, ArLogObjectType.BILL.getCode(),
ArLogResult.SUCCESS.getCode());
if (previous != null && isNumber(previous.getObjectId()))
{
return selectArBillById(Long.valueOf(previous.getObjectId()));
@@ -144,7 +148,8 @@ public class ArBillServiceImpl implements IArBillService
List<String> requestedSourceBillIds = normalizeSourceBillIds(request.getSourceBillIds());
if (requestedSourceBillIds.isEmpty())
{
throw failure(null, "BILL", "", "GENERATE", null, "请至少选择一条有效订单", operator, requestId);
throw failure(null, ArLogObjectType.BILL, "", ArLogOperationType.GENERATE,
null, "请至少选择一条有效订单", operator, requestId);
}
String requestedCurrency = StringUtils.isEmpty(request.getCurrency()) ? "CNY" : request.getCurrency();
List<PrcOrderBill> sources = sourceBillMapper.selectPrcOrderBillByCustomerCutoffAndIds(
@@ -152,7 +157,7 @@ public class ArBillServiceImpl implements IArBillService
requestedSourceBillIds, ArOrderBillStatus.UNBILLED.getCode());
if (sources == null || sources.isEmpty() || sources.size() != requestedSourceBillIds.size())
{
throw failure(null, "BILL", "", "GENERATE", null,
throw failure(null, ArLogObjectType.BILL, "", ArLogOperationType.GENERATE, null,
"部分所选订单已出账、已变更或不符合当前筛选条件,请重新查询后生成", operator, requestId);
}
@@ -162,7 +167,8 @@ public class ArBillServiceImpl implements IArBillService
{
if (source.getCurrency() != null && !currency.equalsIgnoreCase(source.getCurrency()))
{
throw failure(null, "BILL", "", "GENERATE", null, "截止日期内存在多个币种,不能合并为一张账单", operator, requestId);
throw failure(null, ArLogObjectType.BILL, "", ArLogOperationType.GENERATE,
null, "截止日期内存在多个币种,不能合并为一张账单", operator, requestId);
}
total = total.add(nvl(source.getIncomeTotal()));
}
@@ -170,7 +176,7 @@ public class ArBillServiceImpl implements IArBillService
ArBankAccount bankAccount = bankAccountMapper.selectDefaultByCurrency(currency);
if (bankAccount == null)
{
throw failure(null, "BILL", "", "GENERATE", null,
throw failure(null, ArLogObjectType.BILL, "", ArLogOperationType.GENERATE, null,
"未配置 " + currency + " 币种的公司默认收款账户,请先完成收款账户配置", operator, requestId);
}
@@ -206,7 +212,7 @@ public class ArBillServiceImpl implements IArBillService
ArOrderBillStatus.UNBILLED.getCode(), ArOrderBillStatus.BILLED.getCode());
if (claimedCount != sourceBillIds.size())
{
throw failure(null, "BILL", "", "GENERATE", null,
throw failure(null, ArLogObjectType.BILL, "", ArLogOperationType.GENERATE, null,
"部分计费订单已被其他账单占用,请重新查询后生成", operator, requestId);
}
@@ -249,7 +255,8 @@ public class ArBillServiceImpl implements IArBillService
{
detailFeeMapper.insertBatch(detailFees);
}
recordSuccess(bill, "CREATE", "生成正式账单", null, ArBillStatus.DRAFT.getCode(), requestId, operator);
recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.CREATE,
"生成正式账单", null, ArBillStatus.DRAFT.getCode(), requestId, operator);
bill.setBankAccountNo(maskAccount(bill.getBankAccountNo()));
return bill;
}
@@ -261,26 +268,35 @@ public class ArBillServiceImpl implements IArBillService
ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId);
if (bill == null)
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", null, "应收账单不存在", operator, requestId);
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM,
null, "应收账单不存在", operator, requestId);
}
ArBillStatus currentStatus = ArBillStatus.of(bill.getStatus());
if (currentStatus == null || !currentStatus.canConfirm())
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM,
bill.getStatus(),
"只有草稿账单可以确认,当前状态为 " + bill.getStatus(), operator, requestId);
}
if (!validateDetails(arBillId, bill.getTotalAmount()))
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM,
bill.getStatus(),
"账单费用明细汇总与订单或账单总额不一致", operator, requestId);
}
if (billMapper.updateArBillStatus(arBillId, ArBillStatus.CONFIRMED.getCode(),
StringUtils.isEmpty(request == null ? null : request.getRemark()) ? bill.getRemark() : request.getRemark(),
operator, ArBillStatus.DRAFT.getCode()) != 1)
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "CONFIRM", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM,
bill.getStatus(),
"账单状态已发生变化,请刷新后重试", operator, requestId);
}
// 账单确认:应收形成,按账单金额占用客户授信额度(账户不存在时仅CNY会自动初始化)
customerMoneyService.occupyCredit(bill.getCustomerId(), bill.getCurrency(),
nvl(bill.getTotalAmount()));
recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.CONFIRM,
"确认应收账单", ArBillStatus.DRAFT.getCode(), ArBillStatus.CONFIRMED.getCode(), requestId, operator);
// 账单确认:应收形成,按账单金额占用客户授信额度(账户不存在时仅CNY会自动初始化)。
// 不校验可用额度,兼容历史 used_credit 可高于总额度、外币账户静默跳过的口径
customerAccountApi.occupyCredit(buildCreditCommand(bill));
@@ -295,17 +311,20 @@ public class ArBillServiceImpl implements IArBillService
String remark = request == null ? null : request.getRemark();
if (StringUtils.isEmpty(remark))
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", null, "作废必须填写原因", operator, requestId);
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID,
null, "作废必须填写原因", operator, requestId);
}
ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId);
if (bill == null)
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", null, "应收账单不存在", operator, requestId);
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID,
null, "应收账单不存在", operator, requestId);
}
ArBillStatus currentStatus = ArBillStatus.of(bill.getStatus());
if (currentStatus == null || !currentStatus.canVoid())
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID,
bill.getStatus(),
"当前状态不允许作废", operator, requestId);
}
List<String> sourceBillIds = new ArrayList<>();
@@ -318,13 +337,15 @@ public class ArBillServiceImpl implements IArBillService
}
if (billMapper.updateArBillStatus(arBillId, ArBillStatus.VOID.getCode(), remark, operator, currentStatus.getCode()) != 1)
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID,
bill.getStatus(),
"账单状态已发生变化,请刷新后重试", operator, requestId);
}
// 账单作废:已确认账单占用的授信额度全额释放(可作废的CONFIRMED账单必然零核销)
if (currentStatus == ArBillStatus.CONFIRMED)
{
customerAccountApi.releaseCredit(buildCreditCommand(bill));
customerMoneyService.releaseCredit(bill.getCustomerId(), bill.getCurrency(),
nvl(bill.getTotalAmount()));
}
if (!sourceBillIds.isEmpty())
{
@@ -332,18 +353,21 @@ public class ArBillServiceImpl implements IArBillService
ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode());
if (releasedCount != sourceBillIds.size())
{
throw failure(arBillId, "BILL", String.valueOf(arBillId), "VOID", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID,
bill.getStatus(),
"部分来源订单状态异常,账单未作废,请刷新后重试", operator, requestId);
}
}
recordSuccess(bill, "VOID", "作废应收账单:" + remark, bill.getStatus(), ArBillStatus.VOID.getCode(), requestId, operator);
recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.VOID,
"作废应收账单:" + remark, bill.getStatus(), ArBillStatus.VOID.getCode(), requestId, operator);
return selectArBillById(arBillId);
}
@Override
public ArBillExport exportBill(Long arBillId, String operator, String requestId)
{
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "EXPORT");
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, ArLogObjectType.EXPORT.getCode(),
ArLogResult.SUCCESS.getCode());
if (previous != null && isNumber(previous.getObjectId()))
{
ArBillExport previousExport = exportMapper.selectById(Long.valueOf(previous.getObjectId()));
@@ -355,11 +379,13 @@ public class ArBillServiceImpl implements IArBillService
ArBill bill = billMapper.selectArBillById(arBillId);
if (bill == null)
{
throw failure(arBillId, "EXPORT", String.valueOf(arBillId), "EXPORT", null, "应收账单不存在", operator, requestId);
throw failure(arBillId, ArLogObjectType.EXPORT, String.valueOf(arBillId), ArLogOperationType.EXPORT,
null, "应收账单不存在", operator, requestId);
}
if (ArBillStatus.is(bill.getStatus(), ArBillStatus.DRAFT) || ArBillStatus.is(bill.getStatus(), ArBillStatus.VOID))
{
throw failure(arBillId, "EXPORT", String.valueOf(arBillId), "EXPORT", bill.getStatus(),
throw failure(arBillId, ArLogObjectType.EXPORT, String.valueOf(arBillId), ArLogOperationType.EXPORT,
bill.getStatus(),
"草稿或作废账单不可导出", operator, requestId);
}
fillDetails(bill);
@@ -385,7 +411,8 @@ public class ArBillServiceImpl implements IArBillService
export.setFileHash(sha256(data));
export.setExportStatus(ArExportStatus.SUCCESS.getCode());
exportMapper.insert(export);
recordSuccess(bill, "EXPORT", "导出账单 XLSX", bill.getStatus(), bill.getStatus(), requestId, operator, export.getExportId());
recordSuccess(bill, ArLogObjectType.EXPORT, ArLogOperationType.EXPORT,
"导出账单 XLSX", bill.getStatus(), bill.getStatus(), requestId, operator, export.getExportId());
return export;
}
catch (Exception e)
@@ -394,7 +421,8 @@ public class ArBillServiceImpl implements IArBillService
export.setExportStatus(ArExportStatus.FAILED.getCode());
export.setRemark(e.getMessage());
exportMapper.insert(export);
logService.recordFailure(arBillId, "EXPORT", String.valueOf(arBillId), "EXPORT", bill.getStatus(),
logService.recordFailure(arBillId, ArLogObjectType.EXPORT, String.valueOf(arBillId), ArLogOperationType.EXPORT,
bill.getStatus(),
e.getMessage(), operator, requestId);
throw new ServiceException("账单导出失败:" + e.getMessage());
}
@@ -513,20 +541,20 @@ public class ArBillServiceImpl implements IArBillService
return fee;
}
private void recordSuccess(ArBill bill, String operationType, String desc, String fromStatus,
private void recordSuccess(ArBill bill, ArLogObjectType objectType, ArLogOperationType operationType, String desc, String fromStatus,
String toStatus, String requestId, String operator)
{
recordSuccess(bill, operationType, desc, fromStatus, toStatus, requestId, operator, null);
recordSuccess(bill, objectType, operationType, desc, fromStatus, toStatus, requestId, operator, null);
}
private void recordSuccess(ArBill bill, String operationType, String desc, String fromStatus,
private void recordSuccess(ArBill bill, ArLogObjectType objectType, ArLogOperationType operationType, String desc, String fromStatus,
String toStatus, String requestId, String operator, Long objectId)
{
ArBillLog log = new ArBillLog();
log.setArBillId(bill.getArBillId());
log.setObjectType("BILL".equals(operationType) ? "BILL" : operationType.equals("EXPORT") ? "EXPORT" : "BILL");
log.setObjectType(objectType.getCode());
log.setObjectId(objectId == null ? String.valueOf(bill.getArBillId()) : String.valueOf(objectId));
log.setOperationType(operationType);
log.setOperationType(operationType.getCode());
log.setOperationDesc(desc);
log.setFromStatus(fromStatus);
log.setToStatus(toStatus);
@@ -534,13 +562,13 @@ public class ArBillServiceImpl implements IArBillService
log.setAfterData(safeStatusJson(toStatus));
log.setOperatorId(operator);
log.setOperatorName(operator);
log.setOperationSource("WEB");
log.setOperationSource(ArLogOperationSource.WEB.getCode());
log.setRequestId(requestId);
log.setResult(ArLogResult.SUCCESS.getCode());
logService.record(log);
}
private ServiceException failure(Long arBillId, String objectType, String objectId, String operationType,
private ServiceException failure(Long arBillId, ArLogObjectType objectType, String objectId, ArLogOperationType operationType,
String fromStatus, String message, String operator, String requestId)
{
logService.recordFailure(arBillId, objectType, objectId, operationType, fromStatus, message, operator, requestId);
@@ -861,21 +889,6 @@ public class ArBillServiceImpl implements IArBillService
private BigDecimal decimal(BigDecimal value, int scale) { return value == null ? null : value.setScale(scale, java.math.RoundingMode.HALF_UP); }
private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
/** 构造账单授信占用/释放命令(确认占用、作废释放共用,关联账单单据便于流水追溯) */
private CreditUsageCommand buildCreditCommand(ArBill bill)
{
CreditUsageCommand command = new CreditUsageCommand();
command.setCustomerId(bill.getCustomerId());
command.setCurrency(bill.getCurrency());
command.setAmount(nvl(bill.getTotalAmount()));
command.setBizType(CustomerFundBizType.AR_BILL);
command.setBizId(bill.getArBillId());
command.setBizNo(bill.getBillNo());
// 兼容历史口径:不校验可用额度(允许 used_credit 高于总额度、外币账户静默跳过)
command.setCheckAvailable(Boolean.FALSE);
return command;
}
private String safeFilePart(String value) { return StringUtils.isEmpty(value) ? "客户" : value.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5_-]", "_"); }
private String maskAccount(String value) { if (StringUtils.isEmpty(value)) return ""; return value.length() <= 4 ? "****" : "****" + value.substring(value.length() - 4); }
private String sha256(byte[] data) throws NoSuchAlgorithmException { byte[] digest = MessageDigest.getInstance("SHA-256").digest(data); StringBuilder sb = new StringBuilder(); for (byte b : digest) sb.append(String.format("%02x", b)); return sb.toString(); }
@@ -65,12 +65,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateCancelled">
update ar_balance_writeoff
set status = 'CANCELLED',
set status = #{status},
reverse_flow_id = #{reverseFlowId},
cancel_by = #{cancelBy},
cancel_time = sysdate(),
cancel_remark = #{cancelRemark}
where writeoff_id = #{writeoffId} and status = 'EFFECTIVE'
where writeoff_id = #{writeoffId} and status = #{effectiveStatus}
</update>
<select id="selectWriteoffList" parameterType="ArBalanceWriteoff" resultMap="ArBalanceWriteoffResult">
@@ -45,6 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="objectType != null and objectType != ''">and l.object_type = #{objectType}</if>
<if test="objectId != null and objectId != ''">and l.object_id = #{objectId}</if>
<if test="operationType != null and operationType != ''">and l.operation_type = #{operationType}</if>
<if test="operationSource != null and operationSource != ''">and l.operation_source = #{operationSource}</if>
<if test="requestId != null and requestId != ''">and l.request_id = #{requestId}</if>
<if test="result != null and result != ''">and l.result = #{result}</if>
</where>
@@ -56,7 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from_status, to_status, before_data, after_data, operator_id, operator_name,
operation_source, request_id, trace_id, result, error_message, client_ip, create_time
from ar_bill_log
where request_id = #{requestId} and object_type = #{objectType} and result = 'SUCCESS'
where request_id = #{requestId} and object_type = #{objectType} and result = #{result}
order by log_id limit 1
</select>
+1 -1
View File
@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS ar_balance_writeoff (
balance_flow_id BIGINT(20) DEFAULT NULL COMMENT '核销扣余额流水ID',
reverse_flow_id BIGINT(20) DEFAULT NULL COMMENT '反核销回补余额流水ID',
writeoff_date DATE NOT NULL COMMENT '核销日期',
status VARCHAR(30) NOT NULL COMMENT 'EFFECTIVE/CANCELLED',
status VARCHAR(30) NOT NULL COMMENT '状态:EFFECTIVE=有效(已扣减客户余额),CANCELLED=已反核销(已回补客户余额)',
remark VARCHAR(500) DEFAULT NULL COMMENT '核销备注',
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
create_time DATETIME NOT NULL COMMENT '创建时间',
+6 -6
View File
@@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS ar_bill (
total_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '账单应收总额',
written_off_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '已核销金额缓存',
unwritten_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '未核销金额缓存',
status VARCHAR(30) NOT NULL COMMENT 'DRAFT/CONFIRMED/PARTIAL_WRITTEN_OFF/WRITTEN_OFF/VOID',
status VARCHAR(30) NOT NULL COMMENT '状态:DRAFT=草稿,CONFIRMED=已确认,PARTIAL_WRITTEN_OFF=部分核销,WRITTEN_OFF=已核销,VOID=已作废',
bill_version INT(11) NOT NULL DEFAULT 1 COMMENT '账单版本',
bank_account_name VARCHAR(100) DEFAULT NULL COMMENT '收款账户名称快照',
bank_account_no VARCHAR(128) DEFAULT NULL COMMENT '收款账号快照,应用层脱敏展示',
@@ -109,7 +109,7 @@ CREATE TABLE IF NOT EXISTS ar_bill_export (
file_size BIGINT(20) DEFAULT NULL COMMENT '文件字节数',
file_hash VARCHAR(128) DEFAULT NULL COMMENT 'SHA-256校验值',
row_count INT(11) DEFAULT NULL COMMENT '导出订单行数',
export_status VARCHAR(30) NOT NULL COMMENT 'PROCESSING/SUCCESS/FAILED',
export_status VARCHAR(30) NOT NULL COMMENT '导出状态:PROCESSING=生成中,SUCCESS=成功(可下载),FAILED=失败',
export_by VARCHAR(64) NOT NULL COMMENT '导出人',
export_time DATETIME NOT NULL COMMENT '导出时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '失败原因或备注',
@@ -122,9 +122,9 @@ CREATE TABLE IF NOT EXISTS ar_bill_export (
CREATE TABLE IF NOT EXISTS ar_bill_log (
log_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
ar_bill_id BIGINT(20) DEFAULT NULL COMMENT '账单ID',
object_type VARCHAR(40) NOT NULL COMMENT 'BILL/EXPORT/BALANCE_WRITEOFF',
object_type VARCHAR(40) NOT NULL COMMENT '对象类型:BILL=应收账单,EXPORT=账单导出,BALANCE_WRITEOFF=余额核销;历史值可能包含已下线模块类型',
object_id VARCHAR(64) NOT NULL COMMENT '通用对象ID',
operation_type VARCHAR(40) NOT NULL COMMENT '操作类型:CONFIRM/VOID/EXPORT/BALANCE_WRITEOFF/CANCEL_BALANCE_WRITEOFF',
operation_type VARCHAR(40) NOT NULL COMMENT '操作类型:CREATE=生成账单,GENERATE=生成校验,CONFIRM=确认账单,VOID=作废账单,EXPORT=导出账单,BALANCE_WRITEOFF=余额核销,CANCEL_BALANCE_WRITEOFF=取消余额核销',
operation_desc VARCHAR(500) DEFAULT NULL COMMENT '操作说明',
from_status VARCHAR(30) DEFAULT NULL COMMENT '变更前状态',
to_status VARCHAR(30) DEFAULT NULL COMMENT '变更后状态',
@@ -132,10 +132,10 @@ CREATE TABLE IF NOT EXISTS ar_bill_log (
after_data TEXT COMMENT '变更后安全快照',
operator_id VARCHAR(64) NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(100) DEFAULT NULL COMMENT '操作人名称',
operation_source VARCHAR(30) NOT NULL COMMENT 'WEB/API/JOB/IMPORT',
operation_source VARCHAR(30) NOT NULL COMMENT '操作来源:WEB=网页端,API=接口,JOB=定时任务,IMPORT=数据导入',
request_id VARCHAR(64) DEFAULT NULL COMMENT '请求幂等标识',
trace_id VARCHAR(64) DEFAULT NULL COMMENT '链路标识',
result VARCHAR(20) NOT NULL COMMENT 'SUCCESS/FAILED',
result VARCHAR(20) NOT NULL COMMENT '执行结果:SUCCESS=成功,FAILED=失败',
error_message VARCHAR(1000) DEFAULT NULL COMMENT '失败原因',
client_ip VARCHAR(64) DEFAULT NULL COMMENT '来源IP',
create_time DATETIME NOT NULL COMMENT '日志时间',
@@ -1,6 +1,10 @@
-- 应收日志兼容余额核销:仅更新字段注释,不改变字段类型和历史数据
-- 应收日志枚举中文说明:仅更新字段注释,不改变字段类型、历史数据和业务逻辑
ALTER TABLE ar_bill_log
MODIFY COLUMN object_type VARCHAR(40) NOT NULL
COMMENT 'BILL/EXPORT/BALANCE_WRITEOFF',
COMMENT '对象类型:BILL=应收账单,EXPORT=账单导出,BALANCE_WRITEOFF=余额核销;历史值可能包含已下线模块类型',
MODIFY COLUMN operation_type VARCHAR(40) NOT NULL
COMMENT '操作类型:CONFIRM/VOID/EXPORT/BALANCE_WRITEOFF/CANCEL_BALANCE_WRITEOFF';
COMMENT '操作类型:CREATE=生成账单,GENERATE=生成校验,CONFIRM=确认账单,VOID=作废账单,EXPORT=导出账单,BALANCE_WRITEOFF=余额核销,CANCEL_BALANCE_WRITEOFF=取消余额核销',
MODIFY COLUMN operation_source VARCHAR(30) NOT NULL
COMMENT '操作来源:WEB=网页端,API=接口,JOB=定时任务,IMPORT=数据导入',
MODIFY COLUMN result VARCHAR(20) NOT NULL
COMMENT '执行结果:SUCCESS=成功,FAILED=失败';
@@ -0,0 +1,30 @@
-- =====================================================================
-- 应收模块枚举中文说明迁移
--
-- 目的:为数据库中的状态/日志枚举补充中文业务含义,方便运维和数据排查。
-- 说明:本脚本只修改字段 COMMENT,不修改字段类型、历史数据、索引或业务状态值。
-- ALTER TABLE ... MODIFY COLUMN 可重复执行;已执行过旧注释脚本的环境也可直接执行。
-- =====================================================================
SET NAMES utf8mb4;
ALTER TABLE ar_balance_writeoff
MODIFY COLUMN status VARCHAR(30) NOT NULL
COMMENT '状态:EFFECTIVE=有效(已扣减客户余额),CANCELLED=已反核销(已回补客户余额)';
ALTER TABLE ar_bill
MODIFY COLUMN status VARCHAR(30) NOT NULL
COMMENT '状态:DRAFT=草稿,CONFIRMED=已确认,PARTIAL_WRITTEN_OFF=部分核销,WRITTEN_OFF=已核销,VOID=已作废';
ALTER TABLE ar_bill_export
MODIFY COLUMN export_status VARCHAR(30) NOT NULL
COMMENT '导出状态:PROCESSING=生成中,SUCCESS=成功(可下载),FAILED=失败';
ALTER TABLE ar_bill_log
MODIFY COLUMN object_type VARCHAR(40) NOT NULL
COMMENT '对象类型:BILL=应收账单,EXPORT=账单导出,BALANCE_WRITEOFF=余额核销;历史值可能包含已下线模块类型',
MODIFY COLUMN operation_type VARCHAR(40) NOT NULL
COMMENT '操作类型:CREATE=生成账单,GENERATE=生成校验,CONFIRM=确认账单,VOID=作废账单,EXPORT=导出账单,BALANCE_WRITEOFF=余额核销,CANCEL_BALANCE_WRITEOFF=取消余额核销',
MODIFY COLUMN operation_source VARCHAR(30) NOT NULL
COMMENT '操作来源:WEB=网页端,API=接口,JOB=定时任务,IMPORT=数据导入',
MODIFY COLUMN result VARCHAR(20) NOT NULL
COMMENT '执行结果:SUCCESS=成功,FAILED=失败';
+6 -23
View File
@@ -1,23 +1,6 @@
export const AR_BILL_STATUS = Object.freeze({
DRAFT: 'DRAFT',
CONFIRMED: 'CONFIRMED',
PARTIAL_WRITTEN_OFF: 'PARTIAL_WRITTEN_OFF',
WRITTEN_OFF: 'WRITTEN_OFF',
VOID: 'VOID'
})
export const AR_BILL_STATUS_OPTIONS = Object.freeze([
{ label: '草稿', value: AR_BILL_STATUS.DRAFT },
{ label: '已确认', value: AR_BILL_STATUS.CONFIRMED },
{ label: '部分核销', value: AR_BILL_STATUS.PARTIAL_WRITTEN_OFF },
{ label: '已核销', value: AR_BILL_STATUS.WRITTEN_OFF },
{ label: '已作废', value: AR_BILL_STATUS.VOID }
])
export const AR_BILL_STATUS_TYPES = Object.freeze({
[AR_BILL_STATUS.DRAFT]: 'info',
[AR_BILL_STATUS.CONFIRMED]: 'primary',
[AR_BILL_STATUS.PARTIAL_WRITTEN_OFF]: 'warning',
[AR_BILL_STATUS.WRITTEN_OFF]: 'success',
[AR_BILL_STATUS.VOID]: 'danger'
})
// 保留旧的导入路径,统一从应收枚举文件导出,避免页面各自维护状态编码。
export {
AR_BILL_STATUS,
AR_BILL_STATUS_OPTIONS,
AR_BILL_STATUS_TYPES
} from './enums'
@@ -43,10 +43,10 @@
<el-table :data="detail.exports || []" border size="small">
<el-table-column prop="fileName" label="文件名" min-width="250" show-overflow-tooltip />
<el-table-column prop="templateVersion" label="模板版本" width="100" />
<el-table-column prop="exportStatus" label="状态" width="100"><template #default="{ row }"><el-tag :type="row.exportStatus === 'SUCCESS' ? 'success' : 'danger'">{{ row.exportStatus }}</el-tag></template></el-table-column>
<el-table-column prop="exportStatus" label="状态" width="100"><template #default="{ row }"><el-tag :type="exportStatusType(row.exportStatus)">{{ exportStatusText(row.exportStatus) }}</el-tag></template></el-table-column>
<el-table-column prop="exportBy" label="导出人" width="100" />
<el-table-column prop="exportTime" label="导出时间" width="170" />
<el-table-column label="操作" width="90"><template #default="{ row }"><el-button v-if="row.exportStatus === 'SUCCESS'" link type="primary" @click="$emit('download', row)">下载</el-button></template></el-table-column>
<el-table-column label="操作" width="90"><template #default="{ row }"><el-button v-if="row.exportStatus === AR_EXPORT_STATUS.SUCCESS" link type="primary" @click="$emit('download', row)">下载</el-button></template></el-table-column>
</el-table>
</template>
</div>
@@ -54,13 +54,16 @@
</template>
<script setup>
import { AR_BILL_STATUS_OPTIONS, AR_BILL_STATUS_TYPES, AR_EXPORT_STATUS, AR_EXPORT_STATUS_OPTIONS, AR_EXPORT_STATUS_TYPES, arEnumLabel } from '@/constants/ar/enums'
defineProps({ modelValue: Boolean, detail: { type: Object, default: null }, loading: Boolean })
defineEmits(['update:modelValue', 'download', 'export'])
const statusOptions = [{ label: '草稿', value: 'DRAFT' }, { label: '已确认', value: 'CONFIRMED' }, { label: '部分核销', value: 'PARTIAL_WRITTEN_OFF' }, { label: '已核销', value: 'WRITTEN_OFF' }, { label: '已作废', value: 'VOID' }]
function date(value) { return value ? String(value).slice(0, 10) : '-' }
function money(value) { const num = Number(value); return Number.isNaN(num) ? '-' : num.toFixed(2) }
function statusText(value) { return statusOptions.find(item => item.value === value)?.label || value || '-' }
function statusType(value) { return { DRAFT: 'info', CONFIRMED: 'primary', PARTIAL_WRITTEN_OFF: 'warning', WRITTEN_OFF: 'success', VOID: 'danger' }[value] || 'info' }
function statusText(value) { return arEnumLabel(AR_BILL_STATUS_OPTIONS, value) }
function statusType(value) { return AR_BILL_STATUS_TYPES[value] || 'info' }
function exportStatusText(value) { return arEnumLabel(AR_EXPORT_STATUS_OPTIONS, value) }
function exportStatusType(value) { return AR_EXPORT_STATUS_TYPES[value] || 'info' }
function dimensions(row) { return [row.pkgLength, row.pkgWidth, row.pkgHeight].map(v => v == null ? '-' : v).join(' × ') }
function mask(value) { if (!value) return '-'; return value.length <= 4 ? '****' : '****' + value.slice(-4) }
</script>
+21 -10
View File
@@ -1,30 +1,41 @@
<template>
<div class="app-container">
<basic-table ref="tableRef" :columns="columns" :search-columns="searchColumns" :query="listArBillLogs" row-key="logId" :selection="false">
<template #col-result="{ row }"><el-tag :type="row.result === 'SUCCESS' ? 'success' : 'danger'">{{ resultText(row.result) }}</el-tag></template>
<template #col-result="{ row }"><el-tag :type="AR_LOG_RESULT_TYPES[row.result] || 'info'">{{ resultText(row.result) }}</el-tag></template>
</basic-table>
</div>
</template>
<script setup name="ArBillLog">
import { listArBillLogs } from '@/api/ar/bill'
import {
AR_LOG_OBJECT_TYPE_OPTIONS,
AR_LOG_OPERATION_SOURCE_OPTIONS,
AR_LOG_OPERATION_TYPE_OPTIONS,
AR_LOG_RESULT_OPTIONS,
AR_LOG_RESULT_TYPES,
AR_LOG_STATUS_OPTIONS,
arEnumLabel
} from '@/constants/ar/enums'
const tableRef = ref()
const searchColumns = [
{ label: '对象类型', prop: 'objectType', component: 'select', options: [{ label: '应收账单', value: 'BILL' }, { label: '账单导出', value: 'EXPORT' }, { label: '余额核销', value: 'BALANCE_WRITEOFF' }], width: '130px' },
{ label: '操作类型', prop: 'operationType', component: 'input', width: '140px' }, { label: '结果', prop: 'result', component: 'select', options: [{ label: '成功', value: 'SUCCESS' }, { label: '失败', value: 'FAILED' }], width: '120px' }
{ label: '对象类型', prop: 'objectType', component: 'select', options: AR_LOG_OBJECT_TYPE_OPTIONS, width: '130px' },
{ label: '操作类型', prop: 'operationType', component: 'select', options: AR_LOG_OPERATION_TYPE_OPTIONS, width: '150px' },
{ label: '操作来源', prop: 'operationSource', component: 'select', options: AR_LOG_OPERATION_SOURCE_OPTIONS, width: '130px' },
{ label: '结果', prop: 'result', component: 'select', options: AR_LOG_RESULT_OPTIONS, width: '120px' }
]
const columns = [
{ label: '对象类型', prop: 'objectType', width: 110, formatter: row => objectTypeText(row.objectType) }, { label: '对象', prop: 'objectDisplay', minWidth: 220, showOverflowTooltip: true, formatter: row => row.objectDisplay || '-' },
{ label: '操作', prop: 'operationType', width: 150, formatter: row => operationText(row.operationType) }, { label: '说明', prop: 'operationDesc', minWidth: 220, showOverflowTooltip: true },
{ label: '状态变化', prop: 'status', width: 160, formatter: row => `${statusText(row.fromStatus)} → ${statusText(row.toStatus)}` }, { label: '操作人', prop: 'operatorName', width: 110 },
{ label: '来源', prop: 'operationSource', width: 100, formatter: row => sourceText(row.operationSource) },
{ label: '结果', prop: 'result', width: 90, slot: 'result' }, { label: '时间', prop: 'createTime', width: 170, parseTime: true }
]
const objectTypeLabels = { BILL: '应收账单', EXPORT: '账单导出', BALANCE_WRITEOFF: '余额核销' }
const operationLabels = { CONFIRM: '确认账单', VOID: '作废账单', EXPORT: '导出账单', BALANCE_WRITEOFF: '余额核销', CANCEL_BALANCE_WRITEOFF: '取消余额核销' }
const statusLabels = { DRAFT: '草稿', CONFIRMED: '已确认', PARTIAL_WRITTEN_OFF: '部分核销', WRITTEN_OFF: '已核销', VOID: '已作废', REGISTERED: '已登记', CANCELLED: '已取消', EFFECTIVE: '有效' }
function objectTypeText(value) { return objectTypeLabels[value] || value || '-' }
function operationText(value) { return operationLabels[value] || value || '-' }
function statusText(value) { return statusLabels[value] || value || '-' }
function resultText(value) { return value === 'SUCCESS' ? '成功' : value === 'FAILED' ? '失败' : value || '-' }
function objectTypeText(value) { return arEnumLabel(AR_LOG_OBJECT_TYPE_OPTIONS, value) }
function operationText(value) { return arEnumLabel(AR_LOG_OPERATION_TYPE_OPTIONS, value) }
function sourceText(value) { return arEnumLabel(AR_LOG_OPERATION_SOURCE_OPTIONS, value) }
function statusText(value) { return arEnumLabel(AR_LOG_STATUS_OPTIONS, value) }
function resultText(value) { return arEnumLabel(AR_LOG_RESULT_OPTIONS, value) }
</script>
@@ -485,8 +485,7 @@
<el-form :inline="true" class="search-form">
<el-form-item label="状态">
<el-select v-model="woQuery.status" placeholder="全部" clearable style="width: 130px;">
<el-option label="有效" value="EFFECTIVE" />
<el-option label="已反核销" value="CANCELLED" />
<el-option v-for="item in AR_BALANCE_WRITEOFF_STATUS_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
@@ -506,8 +505,8 @@
</el-table-column>
<el-table-column label="状态" align="center" width="90">
<template #default="scope">
<el-tag :type="scope.row.status === 'EFFECTIVE' ? 'success' : 'info'" size="small">
{{ scope.row.status === 'EFFECTIVE' ? '有效' : '已反核销' }}
<el-tag :type="AR_BALANCE_WRITEOFF_STATUS_TYPES[scope.row.status] || 'info'" size="small">
{{ formatOption(AR_BALANCE_WRITEOFF_STATUS_OPTIONS, scope.row.status) }}
</el-tag>
</template>
</el-table-column>
@@ -519,7 +518,7 @@
<el-table-column label="反核销原因" align="center" prop="cancelRemark" min-width="130" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="90" fixed="right">
<template #default="scope">
<el-button v-if="scope.row.status === 'EFFECTIVE'" link type="warning" size="small"
<el-button v-if="scope.row.status === AR_BALANCE_WRITEOFF_STATUS.EFFECTIVE" link type="warning" size="small"
@click="openCancelWriteoffDialog(scope.row)"
v-hasPermi="['customer:bill:writeoff:cancel']">反核销</el-button>
<span v-else>-</span>
@@ -899,6 +898,7 @@ import { listAccount, listReceipt, getReceipt, addReceipt, confirmReceipt, cance
import { listUnsettledBill, listBalanceWriteoff, balanceWriteoff, cancelBalanceWriteoff } from "@/api/customersuppliermanagement/customermoney/customerbill.js"
import { convertToBase } from "@/api/data/exchangeRate"
import { getToken } from "@/utils/auth"
import { AR_BALANCE_WRITEOFF_STATUS, AR_BALANCE_WRITEOFF_STATUS_OPTIONS, AR_BALANCE_WRITEOFF_STATUS_TYPES, AR_BILL_STATUS_OPTIONS, AR_BILL_STATUS_TYPES, arEnumLabel } from '@/constants/ar/enums'
import { useRoute } from 'vue-router'
import { nextTick } from 'vue'
@@ -1522,18 +1522,11 @@ function handleCancelApply(row) {
}
/* ================= 账单核销 ================= */
const billStatusMap = {
DRAFT: { label: '草稿', type: 'info' },
CONFIRMED: { label: '已确认', type: 'warning' },
PARTIAL_WRITTEN_OFF: { label: '部分核销', type: 'primary' },
WRITTEN_OFF: { label: '已核销', type: 'success' },
VOID: { label: '已作废', type: 'danger' }
}
function billStatusLabel(v) {
return billStatusMap[v]?.label || v || '-'
return arEnumLabel(AR_BILL_STATUS_OPTIONS, v)
}
function billStatusTag(v) {
return billStatusMap[v]?.type || 'info'
return AR_BILL_STATUS_TYPES[v] || 'info'
}
function formatDate(val) {
if (!val) return '-'