应收账单模块,调整枚举代码优化
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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
-9
@@ -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);
|
||||
|
||||
+7
-4
@@ -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);
|
||||
|
||||
+58
-45
@@ -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>
|
||||
|
||||
|
||||
@@ -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 '创建时间',
|
||||
|
||||
@@ -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=失败';
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+7
-14
@@ -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 '-'
|
||||
|
||||
Reference in New Issue
Block a user