All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
- 新增 6 张表与成对迁移 000212,扩展企业微信审批场景业务类型白名单 - 后台线下套餐订单与两条代理线下充值入账路径在来源成功事务内建账,来源唯一键幂等 - 核销申请、审批尝试记录、账单分摊预占与驳回重提,审批业务类型 employee_collection_approval - 企业微信终态消费幂等:通过转已核销、驳回释放预占、通过后撤销不回滚并转异常终态 - 退款成功事务内按 bill_id+refund_id 幂等冲销账单或仅写退款关联提示 - 线下收款方式字典、账单查询/统计/关闭、申请查询与代办权限,均写入事务内审计 OpenSpec Change: add-employee-collection-bills
59 lines
2.2 KiB
Go
59 lines
2.2 KiB
Go
package employeecollection
|
|
|
|
import (
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// AllocationCandidate 是一笔待校验的核销申请账单分摊候选。
|
|
type AllocationCandidate struct {
|
|
// BillID 表示目标账单ID。
|
|
BillID uint
|
|
// BillStatus 表示目标账单当前持久化状态。
|
|
BillStatus int
|
|
// Amount 表示本次分摊金额(分)。
|
|
Amount int64
|
|
// Available 表示目标账单当前可核销余额(分),已扣除已通过分摊与其他审批中预占。
|
|
Available int64
|
|
}
|
|
|
|
// ValidateAllocations 校验核销申请的账单分摊集合。
|
|
// 规则:付款金额为正、分摊数量在允许区间、账单不重复、账单已关闭时拒绝、
|
|
// 单笔分摊为正且不超过该账单可核销余额、分摊总额不超过本次付款金额。
|
|
func ValidateAllocations(paidAmount int64, candidates []AllocationCandidate) error {
|
|
if paidAmount <= 0 {
|
|
return errors.New(errors.CodeInvalidParam, "付款金额必须大于零")
|
|
}
|
|
if len(candidates) == 0 {
|
|
return errors.New(errors.CodeInvalidParam, "核销申请至少需要一个账单分摊")
|
|
}
|
|
if len(candidates) > constants.EmployeeCollectionAllocationMaxCount {
|
|
return errors.New(errors.CodeInvalidParam, "核销申请账单分摊数量超出限制")
|
|
}
|
|
seen := make(map[uint]struct{}, len(candidates))
|
|
var total int64
|
|
for _, candidate := range candidates {
|
|
if candidate.BillID == 0 {
|
|
return errors.New(errors.CodeInvalidParam, "账单分摊缺少目标账单")
|
|
}
|
|
if _, exists := seen[candidate.BillID]; exists {
|
|
return errors.New(errors.CodeInvalidParam, "同一账单不能重复分摊")
|
|
}
|
|
seen[candidate.BillID] = struct{}{}
|
|
if candidate.BillStatus == constants.EmployeeCollectionBillStatusClosed {
|
|
return errors.New(errors.CodeEmployeeCollectionBillClosed)
|
|
}
|
|
if candidate.Amount <= 0 {
|
|
return errors.New(errors.CodeEmployeeCollectionAllocationAmountInvalid)
|
|
}
|
|
if candidate.Amount > candidate.Available {
|
|
return errors.New(errors.CodeEmployeeCollectionAllocationExceeded)
|
|
}
|
|
total += candidate.Amount
|
|
}
|
|
if total > paidAmount {
|
|
return errors.New(errors.CodeEmployeeCollectionPaidAmountExceeded)
|
|
}
|
|
return nil
|
|
}
|