chore: 并入 iotlcm-worker-svr 历史到 worker-svr/(原提交 96f0f4e)
This commit is contained in:
@@ -0,0 +1,632 @@
|
||||
package renewchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
||||
rc "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/renewchange"
|
||||
)
|
||||
|
||||
const (
|
||||
renewFinanceAccountStatusNormal = 0
|
||||
renewFinanceIncomeExpenseExpense = 1
|
||||
)
|
||||
|
||||
type renewPaymentAccountRow struct {
|
||||
ID int64 `orm:"id"`
|
||||
Balance float64 `orm:"balance"`
|
||||
Status int `orm:"status"`
|
||||
}
|
||||
|
||||
func (r *RenewExecutor) ensureRenewExecutionContract(
|
||||
ctx context.Context,
|
||||
tx gdb.TX,
|
||||
main *rc.BatchMainRow,
|
||||
details []*rc.BatchDetailRow,
|
||||
cards map[string]*rc.SimCardInfo,
|
||||
binds map[string]*rc.SimCardBindPkg,
|
||||
futureBinds map[string][]*rc.SimCardBindPkg,
|
||||
now time.Time,
|
||||
) (*ExecutionContract, error) {
|
||||
if contract, err := ParseExecutionContract(main); err == nil {
|
||||
if err = r.validateRenewRuntimePermissions(ctx, tx, main, contract); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contract, nil
|
||||
}
|
||||
|
||||
info := main.BatchInfo
|
||||
if info == nil || info.IsNil() {
|
||||
return nil, gerror.New("续费批次缺少导入参数")
|
||||
}
|
||||
durationValue := firstInt(info, "durationValue", "durationYears")
|
||||
if durationValue <= 0 {
|
||||
durationValue = normalizeLegacyRenewDurationValue(firstInt(info, "durationMonths"))
|
||||
}
|
||||
if durationValue <= 0 {
|
||||
return nil, gerror.New("续费时长不能为空")
|
||||
}
|
||||
giftDurationMonths := firstInt(info, "giftDurationMonths")
|
||||
if giftDurationMonths < 0 {
|
||||
return nil, gerror.New("赠送时长不能小于0")
|
||||
}
|
||||
|
||||
iccids, err := strictIccidsFromDetails(details)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customerID, packageCode, beginDates, err := runtimeRenewFacts(main, details, cards, binds, futureBinds, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg, err := r.repo.Tables().LoadRenewPackage(ctx, tx, main.TenantId, packageCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pkg.Enabled != 1 {
|
||||
return nil, gerror.New("当前套餐未启用")
|
||||
}
|
||||
durationUnit := renewDurationUnitForPackage(pkg)
|
||||
actualDurationMonths := consts.DurationValueToMonths(durationUnit, durationValue)
|
||||
actualGiftDurationMonths := consts.DurationValueToMonths(durationUnit, giftDurationMonths)
|
||||
options, err := validateRuntimeRenewDuration(pkg, durationValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customerDept, err := loadRenewDeptTx(ctx, tx, main.TenantId, customerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priceSnap, err := loadRuntimeRenewPriceSnapshot(ctx, tx, main.TenantId, pkg, customerDept, options, beginDates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
amount := roundAmount(priceSnap.RenewUnitPrice * float64(durationValue) * float64(len(iccids)))
|
||||
|
||||
mode := strings.TrimSpace(firstString(info, "paymentMode", "paymentSnapshot.paymentMode"))
|
||||
payment, err := r.buildRuntimeRenewPayment(ctx, tx, main, mode, customerID, amount, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addition := map[string]any{
|
||||
"customerAgentId": customerID,
|
||||
"customerName": strings.TrimSpace(customerDept.DeptName),
|
||||
"packageCode": priceSnap.PackageCode,
|
||||
"packageName": priceSnap.PackageName,
|
||||
"priceSource": priceSnap.PriceSource,
|
||||
"priceOwnerDeptId": priceSnap.PriceOwnerDeptId,
|
||||
"agentDefaultPriceId": priceSnap.AgentDefaultPriceId,
|
||||
"targetPriceId": priceSnap.TargetPriceId,
|
||||
"renewUnitPrice": priceSnap.RenewUnitPrice,
|
||||
"renewableDurationOptions": priceSnap.RenewableDurationOptions,
|
||||
"renewablePackageCodes": priceSnap.RenewablePackageCodes,
|
||||
"renewablePackages": priceSnap.RenewablePackages,
|
||||
"billingMethod": priceSnap.BillingMethod,
|
||||
"renewDurationUnit": priceSnap.RenewDurationUnit,
|
||||
"renewDurationUnitText": priceSnap.RenewDurationUnitText,
|
||||
"renewBeginDate": priceSnap.RenewBeginDate,
|
||||
"renewBeginDateMin": priceSnap.RenewBeginDateMin,
|
||||
"renewBeginDateMax": priceSnap.RenewBeginDateMax,
|
||||
"sameRenewBeginDate": priceSnap.SameRenewBeginDate,
|
||||
"priceSnapshot": priceSnap,
|
||||
"quantity": len(iccids),
|
||||
"durationValue": durationValue,
|
||||
"durationMonths": actualDurationMonths,
|
||||
"actualDurationMonths": actualDurationMonths,
|
||||
"giftDurationMonths": giftDurationMonths,
|
||||
"actualGiftDurationMonths": actualGiftDurationMonths,
|
||||
"amount": amount,
|
||||
}
|
||||
for key, value := range payment {
|
||||
addition[key] = value
|
||||
}
|
||||
merged := mergeBatchInfo(main.BatchInfo, addition)
|
||||
main.BatchInfo = gjson.New(merged)
|
||||
|
||||
contract, err := ParseExecutionContract(main)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = r.validateRenewRuntimePermissions(ctx, tx, main, contract); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contract, nil
|
||||
}
|
||||
|
||||
func runtimeRenewFacts(
|
||||
main *rc.BatchMainRow,
|
||||
details []*rc.BatchDetailRow,
|
||||
cards map[string]*rc.SimCardInfo,
|
||||
binds map[string]*rc.SimCardBindPkg,
|
||||
futureBinds map[string][]*rc.SimCardBindPkg,
|
||||
now time.Time,
|
||||
) (int64, string, []string, error) {
|
||||
customerIDs := map[int64]struct{}{}
|
||||
packageCodes := map[string]struct{}{}
|
||||
beginDates := make([]string, 0, len(details))
|
||||
periodRows := make([]*RenewRow, 0, len(details))
|
||||
sourceBatchNo := executionSourceBatchNo(main, main.BatchInfo)
|
||||
|
||||
for _, detail := range details {
|
||||
if detail == nil {
|
||||
return 0, "", nil, gerror.New("续费明细为空")
|
||||
}
|
||||
iccid := strings.TrimSpace(detail.ItemId)
|
||||
card := cards[iccid]
|
||||
bind := binds[iccid]
|
||||
if card == nil {
|
||||
return 0, "", nil, gerror.Newf("ICCID不存在:%s", iccid)
|
||||
}
|
||||
if bind == nil {
|
||||
return 0, "", nil, gerror.Newf("ICCID没有订购记录:%s", iccid)
|
||||
}
|
||||
expectedMSISDN, expectedIMSI := snapshotIdentity(detail.Snapshot)
|
||||
if err := validateCurrentSubscription(card, bind, subscriptionValidation{
|
||||
ICCID: iccid,
|
||||
MSISDN: expectedMSISDN,
|
||||
IMSI: expectedIMSI,
|
||||
TenantID: main.TenantId,
|
||||
CustomerDeptID: card.DeptId,
|
||||
PackageCode: card.CurrentPackageCode,
|
||||
SourceBatchNo: sourceBatchNo,
|
||||
}); err != nil {
|
||||
return 0, "", nil, gerror.Wrapf(err, "校验续费卡失败,ICCID:%s", iccid)
|
||||
}
|
||||
base := renewBaseSubscriptionForCard(card, bind, futureBinds[iccid])
|
||||
if err := validateRenewBaseWindow(base, now, iccid); err != nil {
|
||||
return 0, "", nil, err
|
||||
}
|
||||
customerIDs[card.DeptId] = struct{}{}
|
||||
packageCodes[strings.TrimSpace(card.CurrentPackageCode)] = struct{}{}
|
||||
periodRows = append(periodRows, &RenewRow{
|
||||
RowNo: detail.RowNo,
|
||||
ICCID: iccid,
|
||||
OldEffTime: base.EffectiveTime,
|
||||
OldExpireTime: base.ExpectedExpireTime,
|
||||
})
|
||||
beginAt := renewBeginDateFromExpire(base.ExpectedExpireTime)
|
||||
if beginAt != nil {
|
||||
beginDates = append(beginDates, beginAt.Format("Y-m-d"))
|
||||
}
|
||||
}
|
||||
if err := validateRenewSameCurrentPeriod(periodRows, nil); err != nil {
|
||||
return 0, "", nil, err
|
||||
}
|
||||
if len(customerIDs) != 1 {
|
||||
return 0, "", nil, gerror.New("当前批次下存在多个客户,不能统一续费")
|
||||
}
|
||||
if len(packageCodes) != 1 {
|
||||
return 0, "", nil, gerror.New("当前批次下存在多个套餐,不能统一续费")
|
||||
}
|
||||
var customerID int64
|
||||
for id := range customerIDs {
|
||||
customerID = id
|
||||
}
|
||||
var packageCode string
|
||||
for code := range packageCodes {
|
||||
packageCode = code
|
||||
}
|
||||
return customerID, packageCode, beginDates, nil
|
||||
}
|
||||
|
||||
func normalizeLegacyRenewDurationValue(raw int) int {
|
||||
if raw <= 0 {
|
||||
return 0
|
||||
}
|
||||
if raw >= 12 && raw%12 == 0 {
|
||||
return raw / 12
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func validateRuntimeRenewDuration(pkg *rc.FlowPackageInfo, durationValue int) ([]int, error) {
|
||||
if pkg == nil {
|
||||
return nil, gerror.New("续费套餐不存在")
|
||||
}
|
||||
options, err := renewableYearOptions(pkg.RenewableDurationConfig)
|
||||
if err != nil || len(options) == 0 {
|
||||
return nil, gerror.Newf("当前套餐%s未配置可续费时长", runtimeRenewPackageLabel(pkg))
|
||||
}
|
||||
if !rc.ContainsInt(options, durationValue) {
|
||||
return nil, gerror.Newf(
|
||||
"续费时长%d%s不在当前套餐%s可续费范围内,可选:%s",
|
||||
durationValue,
|
||||
consts.RenewDurationUnitText(renewDurationUnitForPackage(pkg)),
|
||||
runtimeRenewPackageLabel(pkg),
|
||||
formatRuntimeRenewDurationOptions(options, renewDurationUnitForPackage(pkg)),
|
||||
)
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func runtimeRenewPackageLabel(pkg *rc.FlowPackageInfo) string {
|
||||
if pkg == nil {
|
||||
return ""
|
||||
}
|
||||
name := strings.TrimSpace(pkg.PackageName)
|
||||
code := strings.TrimSpace(pkg.PackageCode)
|
||||
switch {
|
||||
case name != "" && code != "":
|
||||
return name + "(" + code + ")"
|
||||
case name != "":
|
||||
return name
|
||||
default:
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
func formatRuntimeRenewDurationOptions(options []int, unit string) string {
|
||||
if len(options) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, 0, len(options))
|
||||
for _, option := range options {
|
||||
parts = append(parts, fmt.Sprintf("%d%s", option, consts.RenewDurationUnitText(unit)))
|
||||
}
|
||||
return strings.Join(parts, "、")
|
||||
}
|
||||
|
||||
func snapshotIdentity(snapshot *gjson.Json) (string, string) {
|
||||
if snapshot == nil {
|
||||
return "", ""
|
||||
}
|
||||
msisdn := strings.TrimSpace(snapshot.Get("msisdn").String())
|
||||
if msisdn == "" {
|
||||
msisdn = strings.TrimSpace(snapshot.Get("MSISDN").String())
|
||||
}
|
||||
imsi := strings.TrimSpace(snapshot.Get("imsi").String())
|
||||
if imsi == "" {
|
||||
imsi = strings.TrimSpace(snapshot.Get("IMSI").String())
|
||||
}
|
||||
return msisdn, imsi
|
||||
}
|
||||
|
||||
func loadRuntimeRenewPriceSnapshot(
|
||||
ctx context.Context,
|
||||
tx gdb.TX,
|
||||
tenantID string,
|
||||
pkg *rc.FlowPackageInfo,
|
||||
customerDept *rc.SysDeptRow,
|
||||
options []int,
|
||||
beginDates []string,
|
||||
) (*RenewPriceSnapshot, error) {
|
||||
if pkg == nil || customerDept == nil {
|
||||
return nil, gerror.New("续费套餐或客户信息缺失")
|
||||
}
|
||||
priceOwnerDeptID := renewPriceOwnerDeptID(customerDept)
|
||||
salePrice, err := resolveRenewEffectiveSalePrice(ctx, renewTxEffectivePriceStore{tx: tx}, tenantID, priceOwnerDeptID, customerDept.DeptId, pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sameDate, minDate, maxDate, same := summarizeRenewBeginDates(beginDates)
|
||||
durationUnit := renewDurationUnitForPackage(pkg)
|
||||
renewableCodes, renewablePackages, err := runtimeRenewablePackageOptions(ctx, tx, tenantID, customerDept.DeptId, priceOwnerDeptID, pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RenewPriceSnapshot{
|
||||
CustomerAgentId: customerDept.DeptId,
|
||||
PriceOwnerDeptId: priceOwnerDeptID,
|
||||
PackageCode: pkg.PackageCode,
|
||||
PackageName: pkg.PackageName,
|
||||
BillingMethod: pkg.BillingMethod,
|
||||
RenewDurationUnit: durationUnit,
|
||||
RenewDurationUnitText: consts.RenewDurationUnitText(durationUnit),
|
||||
PriceSource: salePrice.Rule.PriceSource,
|
||||
AgentDefaultPriceId: salePrice.Rule.AgentDefaultPriceId,
|
||||
TargetPriceId: salePrice.Rule.TargetPriceId,
|
||||
RenewUnitPrice: salePrice.AgentShipmentPrice,
|
||||
RenewableDurationOptions: options,
|
||||
RenewablePackageCodes: renewableCodes,
|
||||
RenewablePackages: renewablePackages,
|
||||
RenewBeginDate: sameDate,
|
||||
RenewBeginDateMin: minDate,
|
||||
RenewBeginDateMax: maxDate,
|
||||
SameRenewBeginDate: same,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func runtimeRenewablePackageOptions(ctx context.Context, tx gdb.TX, tenantID string, customerDeptID, priceOwnerDeptID int64, currentPkg *rc.FlowPackageInfo) ([]string, []RenewPackageOption, error) {
|
||||
if currentPkg == nil {
|
||||
return nil, nil, gerror.New("当前套餐不存在或未启用")
|
||||
}
|
||||
codes := renewPackageCodesFromJSON(currentPkg.RenewablePackageCodes)
|
||||
if len(codes) == 0 {
|
||||
return nil, nil, gerror.New("当前套餐未配置可续费套餐")
|
||||
}
|
||||
validCodes := make([]string, 0, len(codes))
|
||||
options := make([]RenewPackageOption, 0, len(codes))
|
||||
for _, code := range codes {
|
||||
pkg, err := loadRenewPackageOptionTx(ctx, tx, tenantID, code)
|
||||
if err != nil || pkg == nil || pkg.Enabled != 1 {
|
||||
continue
|
||||
}
|
||||
unitPrice, err := runtimeRenewPackageUnitPrice(ctx, tx, tenantID, customerDeptID, priceOwnerDeptID, pkg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
durations, err := renewableYearOptions(pkg.RenewableDurationConfig)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
validCodes = append(validCodes, pkg.PackageCode)
|
||||
options = append(options, RenewPackageOption{
|
||||
PackageCode: pkg.PackageCode,
|
||||
PackageName: pkg.PackageName,
|
||||
RenewUnitPrice: unitPrice,
|
||||
RenewableDurationOptions: durations,
|
||||
})
|
||||
}
|
||||
if len(options) == 0 {
|
||||
return nil, nil, gerror.New("当前套餐未配置可用的可续费套餐")
|
||||
}
|
||||
return validCodes, options, nil
|
||||
}
|
||||
|
||||
func loadRenewPackageOptionTx(ctx context.Context, tx gdb.TX, tenantID, packageCode string) (*rc.FlowPackageInfo, error) {
|
||||
var pkg rc.FlowPackageInfo
|
||||
err := tx.Ctx(ctx).Model(rc.TableSimFlowPackageInfo).
|
||||
Where("tenant_id", strings.TrimSpace(tenantID)).
|
||||
Where("package_code", strings.TrimSpace(packageCode)).
|
||||
LockShared().
|
||||
Scan(&pkg)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, gerror.Wrap(err, "查询可续费套餐失败")
|
||||
}
|
||||
if pkg.Id == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
func runtimeRenewPackageUnitPrice(ctx context.Context, tx gdb.TX, tenantID string, customerDeptID, priceOwnerDeptID int64, pkg *rc.FlowPackageInfo) (float64, error) {
|
||||
if pkg == nil {
|
||||
return 0, gerror.New("续费套餐不存在")
|
||||
}
|
||||
salePrice, err := resolveRenewEffectiveSalePrice(ctx, renewTxEffectivePriceStore{tx: tx}, tenantID, priceOwnerDeptID, customerDeptID, pkg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return salePrice.AgentShipmentPrice, nil
|
||||
}
|
||||
|
||||
func (r *RenewExecutor) buildRuntimeRenewPayment(ctx context.Context, tx gdb.TX, main *rc.BatchMainRow, requestedMode string, payerDeptID int64, amount float64, now time.Time) (map[string]any, error) {
|
||||
isTop, operatorDeptID, err := isRenewTopDeptTx(ctx, tx, main)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if payerDeptID <= 0 {
|
||||
payerDeptID = operatorDeptID
|
||||
}
|
||||
mode := strings.TrimSpace(requestedMode)
|
||||
if mode == "" {
|
||||
if isTop {
|
||||
mode = "offline"
|
||||
} else {
|
||||
mode = "balance"
|
||||
}
|
||||
}
|
||||
switch mode {
|
||||
case "offline":
|
||||
if !isTop {
|
||||
return nil, gerror.New("当前组织续费必须使用余额支付")
|
||||
}
|
||||
if main.BatchInfo != nil && !main.BatchInfo.Get("offlineContractCompleted").Bool() {
|
||||
return nil, gerror.New("请确认线下业务已完成")
|
||||
}
|
||||
return runtimePaymentSnapshot(mode, consts.PayTypeOffline, amount, 0, amount, 0, 0, "", payerDeptID, main.CreatedBy, isTop, now), nil
|
||||
case "balance":
|
||||
before, after, serialNo, err := deductRuntimeRenewBalance(ctx, tx, main, payerDeptID, amount, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runtimePaymentSnapshot(mode, consts.PayTypeBalance, amount, amount, 0, before, after, serialNo, payerDeptID, main.CreatedBy, isTop, now), nil
|
||||
default:
|
||||
return nil, gerror.New("续费付款方式不正确")
|
||||
}
|
||||
}
|
||||
|
||||
func runtimePaymentSnapshot(mode string, payType int, payAmount, balanceAmount, offlineAmount, before, after float64, serialNo string, payerDeptID, payerUserID int64, isTop bool, now time.Time) map[string]any {
|
||||
paymentTime := now.Format("2006-01-02 15:04:05")
|
||||
snapshot := map[string]any{
|
||||
"paymentMode": mode,
|
||||
"payType": payType,
|
||||
"payStatus": consts.PayStatusPaid,
|
||||
"payAmount": payAmount,
|
||||
"balanceAmount": balanceAmount,
|
||||
"offlinePaidAmount": offlineAmount,
|
||||
"balanceBefore": before,
|
||||
"balanceAfter": after,
|
||||
"balanceDetailSerialNo": strings.TrimSpace(serialNo),
|
||||
"paymentTime": paymentTime,
|
||||
"payerDeptId": payerDeptID,
|
||||
"payerUserId": payerUserID,
|
||||
"isTopDept": isTop,
|
||||
}
|
||||
return map[string]any{
|
||||
"paymentMode": mode,
|
||||
"payType": payType,
|
||||
"payStatus": consts.PayStatusPaid,
|
||||
"payAmount": payAmount,
|
||||
"balanceAmount": balanceAmount,
|
||||
"offlinePaidAmount": offlineAmount,
|
||||
"balanceBefore": before,
|
||||
"balanceAfter": after,
|
||||
"balanceDetailSerialNo": strings.TrimSpace(serialNo),
|
||||
"paymentTime": paymentTime,
|
||||
"payerDeptId": payerDeptID,
|
||||
"payerUserId": payerUserID,
|
||||
"isTopDept": isTop,
|
||||
"paymentSnapshot": snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
func deductRuntimeRenewBalance(ctx context.Context, tx gdb.TX, main *rc.BatchMainRow, payerDeptID int64, amount float64, now time.Time) (float64, float64, string, error) {
|
||||
amount = roundAmount(amount)
|
||||
if amount <= 0 {
|
||||
return 0, 0, "", nil
|
||||
}
|
||||
serialNo := "RENPAY" + strings.TrimSpace(main.BatchNo)
|
||||
if serialNo == "RENPAY" {
|
||||
return 0, 0, "", gerror.New("续费余额流水号生成失败")
|
||||
}
|
||||
existingModel := tx.Ctx(ctx).Model(rc.TableBalanceDetail).
|
||||
Where("tenant_id", main.TenantId).
|
||||
Where("serial_no", serialNo)
|
||||
existing, err := runtimeRenewLockForUpdate(existingModel).One()
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, 0, "", gerror.Wrap(err, "查询续费余额流水失败")
|
||||
}
|
||||
if existing != nil && !existing.IsEmpty() {
|
||||
return 0, 0, "", gerror.New("当前续费批次已扣款,请勿重复提交")
|
||||
}
|
||||
|
||||
var account renewPaymentAccountRow
|
||||
recordModel := tx.Ctx(ctx).Model(rc.TableAgentBalance).
|
||||
Where("dept_id", payerDeptID).
|
||||
Where("tenant_id", main.TenantId)
|
||||
record, err := runtimeRenewLockForUpdate(recordModel).One()
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, 0, "", gerror.Wrap(err, "查询账户余额失败")
|
||||
}
|
||||
if record != nil && !record.IsEmpty() {
|
||||
if err := record.Struct(&account); err != nil {
|
||||
return 0, 0, "", gerror.Wrap(err, "解析账户余额失败")
|
||||
}
|
||||
}
|
||||
if account.ID == 0 {
|
||||
return 0, 0, "", gerror.New("账户余额不存在,请先充值")
|
||||
}
|
||||
if account.Status != renewFinanceAccountStatusNormal {
|
||||
return 0, 0, "", gerror.New("账户余额状态异常,不能续费")
|
||||
}
|
||||
before := roundAmount(account.Balance)
|
||||
if before+0.0001 < amount {
|
||||
return 0, 0, "", gerror.Newf("账户余额不足,需支付%.2f元,当前余额%.2f元", amount, before)
|
||||
}
|
||||
after := roundAmount(before - amount)
|
||||
nowTime := gtime.NewFromTime(now)
|
||||
if _, err := tx.Ctx(ctx).Model(rc.TableAgentBalance).Where("id", account.ID).Data(g.Map{
|
||||
"balance": after,
|
||||
"updated_by": main.CreatedBy,
|
||||
"updated_at": nowTime,
|
||||
}).Update(); err != nil {
|
||||
return 0, 0, "", gerror.Wrap(err, "扣减账户余额失败")
|
||||
}
|
||||
if _, err := tx.Ctx(ctx).Model(rc.TableBalanceDetail).Data(g.Map{
|
||||
"dept_id": payerDeptID,
|
||||
"serial_no": serialNo,
|
||||
"transaction_time": nowTime,
|
||||
"income_expense_type": renewFinanceIncomeExpenseExpense,
|
||||
"transaction_type": renewFinanceTransactionTypeConsume,
|
||||
"transaction_channel": renewFinanceTransactionChannelBalance,
|
||||
"transaction_status": renewFinanceTransactionStatusPaid,
|
||||
"amount": amount,
|
||||
"before_balance": before,
|
||||
"balance_after": after,
|
||||
"order_sn": main.BatchNo,
|
||||
"remark": fmt.Sprintf("SIM卡续费余额支付,批次号:%s", main.BatchNo),
|
||||
"tenant_id": main.TenantId,
|
||||
"created_dept": payerDeptID,
|
||||
"created_by": main.CreatedBy,
|
||||
"created_at": nowTime,
|
||||
}).Insert(); err != nil {
|
||||
return 0, 0, "", gerror.Wrap(err, "写入续费余额流水失败")
|
||||
}
|
||||
return before, after, serialNo, nil
|
||||
}
|
||||
|
||||
func runtimeRenewLockForUpdate(model *gdb.Model) *gdb.Model {
|
||||
if g.DB().GetConfig().Type == "sqlite" {
|
||||
return model
|
||||
}
|
||||
return model.LockUpdate()
|
||||
}
|
||||
|
||||
func (r *RenewExecutor) validateRenewRuntimePermissions(ctx context.Context, tx gdb.TX, main *rc.BatchMainRow, contract *ExecutionContract) error {
|
||||
if contract == nil {
|
||||
return gerror.New("续费执行合同不能为空")
|
||||
}
|
||||
isTop, _, err := isRenewTopDeptTx(ctx, tx, main)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contract.PaymentMode == "offline" && !isTop {
|
||||
return gerror.New("当前组织续费必须使用余额支付")
|
||||
}
|
||||
if contract.PackageBeginTime != nil && !isTop {
|
||||
return gerror.New("当前组织不能修改续费日期")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isRenewTopDeptTx(ctx context.Context, tx gdb.TX, main *rc.BatchMainRow) (bool, int64, error) {
|
||||
deptID := main.CreatedDept
|
||||
if deptID <= 0 {
|
||||
deptID = main.DeptId
|
||||
}
|
||||
if deptID <= 0 {
|
||||
return false, 0, gerror.New("当前批次缺少操作组织")
|
||||
}
|
||||
dept, err := loadRenewDeptTx(ctx, tx, main.TenantId, deptID)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
return dept.ParentId == 0, deptID, nil
|
||||
}
|
||||
|
||||
func loadRenewDeptTx(ctx context.Context, tx gdb.TX, tenantID string, deptID int64) (*rc.SysDeptRow, error) {
|
||||
var row rc.SysDeptRow
|
||||
err := tx.Ctx(ctx).Model(rc.TableSysDept).
|
||||
Where("tenant_id", tenantID).
|
||||
Where("dept_id", deptID).
|
||||
WhereNull("deleted_at").
|
||||
LockShared().
|
||||
Scan(&row)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询客户部门失败")
|
||||
}
|
||||
if row.DeptId == 0 {
|
||||
return nil, gerror.New("客户部门不存在")
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func loadRenewPriceOverrideTx(ctx context.Context, tx gdb.TX, tenantID string, deptID, targetAgentID int64, scopeType int, packageCode string) (*rc.FlowPackagePrice, error) {
|
||||
var row rc.FlowPackagePrice
|
||||
err := tx.Ctx(ctx).Model(rc.TableSimFlowPackagePrice).
|
||||
Where("tenant_id", tenantID).
|
||||
Where("dept_id", deptID).
|
||||
Where("agent_scope_type", scopeType).
|
||||
Where("target_agent_id", targetAgentID).
|
||||
Where("package_code", strings.TrimSpace(packageCode)).
|
||||
Where("enabled", 1).
|
||||
LockShared().
|
||||
Scan(&row)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, gerror.Wrap(err, "查询代理续费定价失败")
|
||||
}
|
||||
if row.Id == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
Reference in New Issue
Block a user