1269 lines
48 KiB
Go
1269 lines
48 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"sort"
|
||
"strings"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchmetadata"
|
||
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchimport"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||
|
||
"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/os/gtime"
|
||
)
|
||
|
||
type appRenewBatchContext struct {
|
||
Items []*appRenewPackageContext
|
||
CurrentPackage *entity.TSimFlowPackageInfo
|
||
Package *entity.TSimFlowPackageInfo
|
||
Price *appRenewPriceSnapshot
|
||
Party *appRenewOrderParty
|
||
Durations []int
|
||
OwnerType string
|
||
RenewablePackageCodes []string
|
||
RenewableTargets []*appRenewTargetContext
|
||
}
|
||
|
||
type appRenewPrepareBatchSnapshot struct {
|
||
CustomerAgentId int64
|
||
CurrentPackageCode string
|
||
CurrentPackageName string
|
||
PackageCode string
|
||
PackageName string
|
||
PriceSource string
|
||
AgentDefaultPriceId int64
|
||
TargetPriceId int64
|
||
RenewUnitPrice float64
|
||
RenewableDurationItems []int
|
||
RenewablePackageCodes []string
|
||
Targets map[string]*appRenewPrepareTargetSnapshot
|
||
Quantity int
|
||
}
|
||
|
||
type appRenewPrepareTargetSnapshot struct {
|
||
PackageCode string
|
||
PackageName string
|
||
RenewUnitPrice float64
|
||
PriceSource string
|
||
AgentDefaultPriceId int64
|
||
TargetPriceId int64
|
||
Durations []int
|
||
}
|
||
|
||
// 批量续费约束做预校验:
|
||
// 同一批次必须同当前套餐、同客户归属、同定价,才能生成统一续费档位。
|
||
func (s *sAppCard) RenewBatchPrepare(ctx context.Context, param *model.AppCardRenewBatchPrepareParam) (res *model.AppCardRenewPrepareModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRenewBatchPrepareParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
batch, err := s.buildRenewBatchContext(ctx, user, param.CardValues, param.PackageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
ctx = appContextWithUser(ctx, user)
|
||
processor := newAppRenewBatchProcessor(batch)
|
||
rows := appRenewBatchRows(batch)
|
||
req := batchimport.SubmitRequest{
|
||
BizParams: param,
|
||
SourceType: consts.BatchSourceTypeBatchSelect,
|
||
SourceChannel: batchmetadata.MiniProgram,
|
||
ExecutionMode: batchmetadata.Bulk,
|
||
BatchType: consts.BatchTypePrepare,
|
||
FailureBatchType: consts.BatchTypeRenew,
|
||
SkipExecute: true,
|
||
TargetDeptId: batch.Party.CustomerAgentId,
|
||
Operator: batchimport.OperatorContext{
|
||
TenantId: user.TenantId,
|
||
DeptId: batch.Party.CreatedDept,
|
||
UserId: user.UserId,
|
||
},
|
||
}
|
||
result, err := batchimport.RunRows(ctx, req, processor, rows, "miniapp-renew-prepare")
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "写入续费预处理批次失败")
|
||
}
|
||
if result == nil || result.ImportStatus != consts.ImportStatusSuccess {
|
||
failReason := "续费预处理批次校验失败"
|
||
if result != nil && strings.TrimSpace(result.FailReason) != "" {
|
||
failReason = strings.TrimSpace(result.FailReason)
|
||
}
|
||
return nil, gerror.New(failReason)
|
||
}
|
||
|
||
res = s.buildRenewBatchPrepareModel(ctx, user, batch)
|
||
res.BatchNo = result.BatchNo
|
||
res.BatchType = consts.BatchTypePrepare
|
||
return res, nil
|
||
}
|
||
|
||
// RenewBatchSubmit 确认续费预处理批次,并在同一事务内完成余额扣款和批量续费落库。
|
||
// 批量续费必须先走预处理校验,避免卡片、套餐或价格在提交时发生变化。
|
||
func (s *sAppCard) RenewBatchSubmit(ctx context.Context, param *model.AppCardRenewBatchSubmitParam) (res *model.AppCardRenewSubmitModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRenewBatchSubmitParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(param.Items) > 0 {
|
||
return nil, gerror.New("批量续费请先校验生成预处理批次,再提交续费批次")
|
||
}
|
||
if strings.TrimSpace(param.BatchNo) != "" {
|
||
return s.renewBatchSubmitPrepared(ctx, user, param)
|
||
}
|
||
|
||
batch, err := s.buildRenewBatchContext(ctx, user, param.CardValues, param.PackageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
duration := appRenewDurationSelectionFromParam(param.DurationYears, param.DurationMonths, batch.Durations)
|
||
if duration.Years <= 0 || duration.Months <= 0 {
|
||
return nil, gerror.New("请选择续费年限")
|
||
}
|
||
if !appContainsInt(batch.Durations, duration.Years) {
|
||
return nil, gerror.New("续费时长不在目标套餐可续费范围内")
|
||
}
|
||
|
||
quantity := len(batch.Items)
|
||
singleCardAmount := appRenewTotalAmount(batch.Price.UnitPrice, duration.Years, 1)
|
||
totalAmount := appRoundAmount(singleCardAmount * float64(quantity))
|
||
now := gtime.Now()
|
||
|
||
var orderId int64
|
||
var orderSn string
|
||
var batchNo string
|
||
var minBeginAt *gtime.Time
|
||
var maxEndAt *gtime.Time
|
||
var cardPeriods []map[string]any
|
||
err = dao.TOrder.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
exec, err := s.prepareAppRenewExecution(ctx, tx, batch, duration.Months)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
minBeginAt, maxEndAt = exec.MinBeginAt, exec.MaxEndAt
|
||
cardPeriods = exec.CardPeriods
|
||
|
||
orderSn = appOrderNo("SO", user.UserId, now)
|
||
batchNo, err = appNextBatchNo(ctx, consts.BatchNoPrefixRenew)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err = s.deductAppBalanceForDept(ctx, tx, user, orderSn, totalAmount, "批量套餐续费", batch.Party.CustomerAgentId, now); err != nil {
|
||
return err
|
||
}
|
||
orderId, err = s.insertAppRenewOrder(ctx, tx, user, batch.Party, strings.TrimSpace(param.BuyerNote), orderSn, totalAmount, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = s.insertAppRenewSubscriptionBinds(ctx, tx, user, batch, exec, batchNo, orderSn, duration.Months, now); err != nil {
|
||
return err
|
||
}
|
||
buyerNote := strings.TrimSpace(param.BuyerNote)
|
||
if err = s.insertAppRenewBatchOrderItem(ctx, tx, user, batch, orderId, orderSn, batchNo, "", duration.Months, singleCardAmount, totalAmount, minBeginAt, maxEndAt, cardPeriods, now); err != nil {
|
||
return err
|
||
}
|
||
return s.insertAppRenewSubmitBatch(ctx, tx, user, batch, batchNo, "", orderSn, duration.Months, singleCardAmount, totalAmount, minBeginAt, maxEndAt, cardPeriods, buyerNote, batchmetadata.Bulk, now)
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
cardModels := s.renewBatchCardModels(ctx, user, batch)
|
||
packageModel := s.renewPackageModel(batch.Items[0].Card, batch.Package, batch.OwnerType)
|
||
option := appRenewOptionModel(duration.Years, batch.Price.UnitPrice, totalAmount, maxEndAt, appRenewRecommendIndex(batch.Durations) == appIndexOfInt(batch.Durations, duration.Years))
|
||
res = &model.AppCardRenewSubmitModel{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
OrderId: orderId,
|
||
OrderSn: orderSn,
|
||
TotalAmount: totalAmount,
|
||
PriceText: option.PriceText,
|
||
Quantity: quantity,
|
||
OrderStatus: consts.OrderStatusCompleted,
|
||
PayStatus: consts.PayStatusPaid,
|
||
Status: "success",
|
||
EffectiveRule: appRenewBatchEffectiveRule(),
|
||
Card: cardModels[0],
|
||
Cards: cardModels,
|
||
CurrentPackage: s.renewPackageModel(batch.Items[0].Card, batch.CurrentPackage, batch.OwnerType),
|
||
Package: packageModel,
|
||
Option: option,
|
||
}
|
||
_ = s.publishAppBusinessAsyncEvent(ctx, appBusinessAsyncEvent{
|
||
EventType: appAsyncEventRenewCompleted,
|
||
OrderSn: orderSn,
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
TenantId: user.TenantId,
|
||
UserId: user.UserId,
|
||
DeptId: user.DeptId,
|
||
Trigger: "renew_batch_submit",
|
||
})
|
||
return res, nil
|
||
}
|
||
|
||
func (s *sAppCard) renewBatchSubmitPrepared(ctx context.Context, user *entity.SysUser, param *model.AppCardRenewBatchSubmitParam) (*model.AppCardRenewSubmitModel, error) {
|
||
now := gtime.Now()
|
||
var orderId int64
|
||
var orderSn string
|
||
var batchNo string
|
||
var batch *appRenewBatchContext
|
||
var snapshot *appRenewPrepareBatchSnapshot
|
||
var duration appRenewDurationSelection
|
||
var minBeginAt *gtime.Time
|
||
var maxEndAt *gtime.Time
|
||
var cardPeriods []map[string]any
|
||
|
||
err := dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
prepareBatch, err := s.lockAppRenewPrepareBatch(ctx, tx, user, param.BatchNo)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
snapshot, err = appRenewPrepareSnapshotFromBatch(prepareBatch)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
selectedPackageCode := appRenewSubmitTargetPackageCode(snapshot, param.PackageCode)
|
||
snapshotTarget := appRenewPrepareSnapshotTarget(snapshot, selectedPackageCode)
|
||
if snapshotTarget == nil {
|
||
return gerror.New("续费套餐不在当前套餐可续费范围内")
|
||
}
|
||
duration = appRenewDurationSelectionFromParam(param.DurationYears, param.DurationMonths, snapshotTarget.Durations)
|
||
if duration.Years <= 0 || duration.Months <= 0 {
|
||
return gerror.New("请选择续费年限")
|
||
}
|
||
if !appContainsInt(snapshotTarget.Durations, duration.Years) {
|
||
return gerror.New("续费时长不在目标套餐可续费范围内")
|
||
}
|
||
|
||
cardValues, err := s.loadAppRenewPrepareBatchCards(ctx, tx, prepareBatch.BatchNo)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if len(cardValues) == 0 {
|
||
return gerror.New("续费预处理批次没有可确认的卡")
|
||
}
|
||
if len(param.CardValues) > 0 && !appSameStringSet(appNormalizeRenewCardValues(param.CardValues), cardValues) {
|
||
return gerror.New("提交卡片与预处理批次不一致,请重新校验")
|
||
}
|
||
|
||
batch, err = s.buildRenewBatchContext(ctx, user, cardValues, selectedPackageCode)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !appSameRenewPrepareSnapshot(snapshot, batch) {
|
||
return gerror.New("当前续费价格、客户或套餐已变化,请重新校验")
|
||
}
|
||
if !appContainsInt(batch.Durations, duration.Years) {
|
||
return gerror.New("续费时长不在目标套餐可续费范围内")
|
||
}
|
||
|
||
quantity := len(batch.Items)
|
||
singleCardAmount := appRenewTotalAmount(batch.Price.UnitPrice, duration.Years, 1)
|
||
totalAmount := appRoundAmount(singleCardAmount * float64(quantity))
|
||
exec, err := s.prepareAppRenewExecution(ctx, tx, batch, duration.Months)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
minBeginAt, maxEndAt = exec.MinBeginAt, exec.MaxEndAt
|
||
cardPeriods = exec.CardPeriods
|
||
|
||
orderSn = appOrderNo("SO", user.UserId, now)
|
||
batchNo = prepareBatch.BatchNo
|
||
sourceBatchNo := appRenewSourceBatchNoFromBatch(prepareBatch)
|
||
if _, err = s.deductAppBalanceForDept(ctx, tx, user, orderSn, totalAmount, "批量套餐续费", batch.Party.CustomerAgentId, now); err != nil {
|
||
return err
|
||
}
|
||
orderId, err = s.insertAppRenewOrder(ctx, tx, user, batch.Party, strings.TrimSpace(param.BuyerNote), orderSn, totalAmount, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = s.insertAppRenewSubscriptionBinds(ctx, tx, user, batch, exec, batchNo, orderSn, duration.Months, now); err != nil {
|
||
return err
|
||
}
|
||
if err = s.insertAppRenewBatchOrderItem(ctx, tx, user, batch, orderId, orderSn, batchNo, sourceBatchNo, duration.Months, singleCardAmount, totalAmount, minBeginAt, maxEndAt, cardPeriods, now); err != nil {
|
||
return err
|
||
}
|
||
return s.markAppRenewPrepareBatchConfirmed(ctx, tx, user, prepareBatch, batch, sourceBatchNo, orderSn, duration.Months, singleCardAmount, totalAmount, minBeginAt, maxEndAt, cardPeriods, strings.TrimSpace(param.BuyerNote), now)
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if batch == nil || len(batch.Items) == 0 {
|
||
return nil, gerror.New("续费提交结果不完整")
|
||
}
|
||
|
||
cardModels := s.renewBatchCardModels(ctx, user, batch)
|
||
packageModel := s.renewPackageModel(batch.Items[0].Card, batch.Package, batch.OwnerType)
|
||
totalAmount := appRenewTotalAmount(batch.Price.UnitPrice, duration.Years, len(batch.Items))
|
||
option := appRenewOptionModel(duration.Years, batch.Price.UnitPrice, totalAmount, maxEndAt, appRenewRecommendIndex(batch.Durations) == appIndexOfInt(batch.Durations, duration.Years))
|
||
res := &model.AppCardRenewSubmitModel{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
OrderId: orderId,
|
||
OrderSn: orderSn,
|
||
TotalAmount: totalAmount,
|
||
PriceText: option.PriceText,
|
||
Quantity: len(batch.Items),
|
||
OrderStatus: consts.OrderStatusCompleted,
|
||
PayStatus: consts.PayStatusPaid,
|
||
Status: "success",
|
||
EffectiveRule: appRenewBatchEffectiveRule(),
|
||
Card: cardModels[0],
|
||
Cards: cardModels,
|
||
CurrentPackage: s.renewPackageModel(batch.Items[0].Card, batch.CurrentPackage, batch.OwnerType),
|
||
Package: packageModel,
|
||
Option: option,
|
||
}
|
||
_ = s.publishAppBusinessAsyncEvent(ctx, appBusinessAsyncEvent{
|
||
EventType: appAsyncEventRenewCompleted,
|
||
OrderSn: orderSn,
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
TenantId: user.TenantId,
|
||
UserId: user.UserId,
|
||
DeptId: user.DeptId,
|
||
Trigger: "renew_prepared_submit",
|
||
})
|
||
return res, nil
|
||
}
|
||
|
||
func (s *sAppCard) buildRenewBatchContext(ctx context.Context, user *entity.SysUser, cardValues []string, packageCode string) (*appRenewBatchContext, error) {
|
||
values := appNormalizeRenewCardValues(cardValues)
|
||
if len(values) == 0 {
|
||
return nil, gerror.New("请输入需要续费的卡")
|
||
}
|
||
if len(values) > 100 {
|
||
return nil, gerror.New("单次批量续费最多支持100张卡")
|
||
}
|
||
|
||
batch := &appRenewBatchContext{
|
||
Items: make([]*appRenewPackageContext, 0, len(values)),
|
||
OwnerType: s.ownerType(ctx, user),
|
||
}
|
||
seenIccid := make(map[string]struct{}, len(values))
|
||
for index, value := range values {
|
||
card, err := s.findVisibleCard(ctx, user, value)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if card == nil {
|
||
return nil, gerror.Newf("第%d张卡信息不存在或无权续费:%s", index+1, value)
|
||
}
|
||
if err = s.ensureOperateCard(ctx, user, card, "续费"); err != nil {
|
||
return nil, err
|
||
}
|
||
iccid := strings.TrimSpace(card.Iccid)
|
||
if _, exists := seenIccid[iccid]; exists {
|
||
return nil, gerror.Newf("批量续费卡重复:%s", iccid)
|
||
}
|
||
seenIccid[iccid] = struct{}{}
|
||
|
||
renewCtx, err := s.resolveRenewPackageContext(ctx, card, "")
|
||
if err != nil {
|
||
return nil, gerror.Newf("%s:%v", appFirstNotEmpty(card.Iccid, value), err)
|
||
}
|
||
if batch.CurrentPackage == nil {
|
||
batch.CurrentPackage = renewCtx.Package
|
||
} else if strings.TrimSpace(batch.CurrentPackage.PackageCode) != strings.TrimSpace(renewCtx.Package.PackageCode) {
|
||
return nil, gerror.New("当前批次下存在多个当前套餐,不能统一续费")
|
||
}
|
||
|
||
party, err := s.renewOrderParty(ctx, user, card)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if batch.Party == nil {
|
||
batch.Party = party
|
||
} else if !appSameRenewOrderParty(batch.Party, party) {
|
||
return nil, gerror.New("当前批次下存在多个客户,不能统一续费")
|
||
}
|
||
|
||
batch.Items = append(batch.Items, renewCtx)
|
||
}
|
||
if len(batch.Items) == 0 || batch.CurrentPackage == nil || batch.Party == nil {
|
||
return nil, gerror.New("续费批量校验结果不完整")
|
||
}
|
||
renewableCodes, renewableTargets, selectedTarget, err := s.resolveRenewTargetPackages(ctx, user, batch.Items[0], packageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
batch.RenewablePackageCodes = renewableCodes
|
||
batch.RenewableTargets = renewableTargets
|
||
batch.Package = selectedTarget.Package
|
||
batch.Price = selectedTarget.Price
|
||
batch.Durations = selectedTarget.Durations
|
||
for _, item := range batch.Items[1:] {
|
||
price, err := s.resolveRenewUnitPrice(ctx, user, item.Card, batch.Package)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !appSameRenewPrice(batch.Price, price) {
|
||
return nil, gerror.New("当前批次下存在不同定价,不能统一续费")
|
||
}
|
||
}
|
||
if batch.Package == nil || batch.Price == nil || len(batch.Durations) == 0 {
|
||
return nil, gerror.New("续费批量校验结果不完整")
|
||
}
|
||
return batch, nil
|
||
}
|
||
|
||
func (s *sAppCard) buildRenewBatchPrepareModel(ctx context.Context, user *entity.SysUser, batch *appRenewBatchContext) *model.AppCardRenewPrepareModel {
|
||
cardModels := s.renewBatchCardModels(ctx, user, batch)
|
||
quantity := len(cardModels)
|
||
recommendIndex := appRenewRecommendIndex(batch.Durations)
|
||
options := appRenewOptionModels(batch.Durations, batch.Price.UnitPrice, quantity, func(years int) *gtime.Time {
|
||
return appRenewBatchMaxExpireAfter(batch.Items, appRenewActualDurationMonths(years))
|
||
})
|
||
|
||
return &model.AppCardRenewPrepareModel{
|
||
Card: cardModels[0],
|
||
Cards: cardModels,
|
||
Quantity: quantity,
|
||
CurrentPackage: s.renewPackageModel(batch.Items[0].Card, batch.CurrentPackage, batch.OwnerType),
|
||
Package: s.renewPackageModel(batch.Items[0].Card, batch.Package, batch.OwnerType),
|
||
RenewablePackageCodes: batch.RenewablePackageCodes,
|
||
RenewablePackages: s.renewTargetPackageOptionModels(batch.Items[0].Card, batch.RenewableTargets, batch.OwnerType, quantity, func(years int) *gtime.Time {
|
||
return appRenewBatchMaxExpireAfter(batch.Items, appRenewActualDurationMonths(years))
|
||
}),
|
||
Options: options,
|
||
PriceSource: batch.Price.PriceSource,
|
||
UnitPrice: batch.Price.UnitPrice,
|
||
EffectiveRule: appRenewBatchEffectiveRule(),
|
||
RecommendIndex: recommendIndex,
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) renewBatchCardModels(ctx context.Context, user *entity.SysUser, batch *appRenewBatchContext) []*model.AppCardModel {
|
||
cardModels := make([]*model.AppCardModel, 0, len(batch.Items))
|
||
for _, item := range batch.Items {
|
||
cardModels = append(cardModels, s.cardRowToModel(ctx, item.Card, user, batch.OwnerType, true))
|
||
}
|
||
return cardModels
|
||
}
|
||
|
||
func (s *sAppCard) insertAppRenewPrepareBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appRenewBatchContext, batchNo string, now *gtime.Time) error {
|
||
quantity := len(batch.Items)
|
||
_, err := dao.BatchMain.Ctx(ctx).TX(tx).Data(do.BatchMain{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypePrepare,
|
||
BatchStatus: consts.BatchStatusSuccess,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusSuccess,
|
||
SourceType: consts.BatchSourceTypeBatchSelect,
|
||
SourceChannel: batchmetadata.MiniProgram,
|
||
ExecutionMode: batchmetadata.Bulk,
|
||
SourceBatchNo: batchNo,
|
||
IsCrossDept: 0,
|
||
TargetDeptId: batch.Party.CustomerAgentId,
|
||
DeptId: batch.Party.CreatedDept,
|
||
TotalCount: quantity,
|
||
ValidCount: quantity,
|
||
SuccessCount: quantity,
|
||
FailCount: 0,
|
||
RunResult: consts.BatchRunResultSuccess,
|
||
BatchInfo: appRenewPrepareBatchInfo(batch, "", "", 0, 0),
|
||
StartTime: now,
|
||
EndTime: now,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).Insert()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "写入续费预处理主表失败")
|
||
}
|
||
|
||
details := make([]do.BatchDetail, 0, quantity)
|
||
for index, item := range batch.Items {
|
||
rowNo := appRenewBatchSourceRowNo(index)
|
||
details = append(details, do.BatchDetail{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypePrepare,
|
||
DetailStatus: consts.BatchDetailStatusSuccess,
|
||
BlankOutStatus: consts.BlankOutStatusNo,
|
||
RowNo: rowNo,
|
||
ItemId: item.Card.Iccid,
|
||
Snapshot: appRenewPrepareDetailSnapshot(batch, item, rowNo),
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
})
|
||
}
|
||
if len(details) == 0 {
|
||
return nil
|
||
}
|
||
if _, err = dao.BatchDetail.Ctx(ctx).TX(tx).Data(details).Insert(); err != nil {
|
||
return gerror.Wrap(err, "写入续费预处理明细失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) insertAppRenewSubmitBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appRenewBatchContext, batchNo string, sourceBatchNo string, orderSn string, durationMonths int, singleCardAmount float64, totalAmount float64, beginAt, endAt *gtime.Time, cardPeriods []map[string]any, buyerNote string, executionMode int, now *gtime.Time) error {
|
||
sourceBatchNo = appRenewEffectiveSourceBatchNo(batchNo, sourceBatchNo)
|
||
quantity := len(batch.Items)
|
||
logFiles, err := s.ensureAppRenewBatchLogFiles(ctx, tx, user, batch, nil, batchNo, sourceBatchNo, orderSn, durationMonths, singleCardAmount, totalAmount, beginAt, endAt, cardPeriods, buyerNote, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
batchInfo := appRenewBatchInfoWithLogFiles(
|
||
appRenewSubmitBatchInfo(batch, batchNo, sourceBatchNo, orderSn, durationMonths, singleCardAmount, totalAmount, beginAt, endAt, cardPeriods, buyerNote, now),
|
||
logFiles,
|
||
)
|
||
_, err = dao.BatchMain.Ctx(ctx).TX(tx).Data(do.BatchMain{
|
||
BatchNo: batchNo,
|
||
OrderSn: orderSn,
|
||
BatchType: consts.BatchTypeRenew,
|
||
BatchStatus: consts.BatchStatusSuccess,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusSuccess,
|
||
SourceType: consts.BatchSourceTypeBatchSelect,
|
||
SourceChannel: batchmetadata.MiniProgram,
|
||
SourceBatchNo: strings.TrimSpace(sourceBatchNo),
|
||
ExecutionMode: batchmetadata.NormalizeMode(executionMode),
|
||
IsCrossDept: 0,
|
||
TargetDeptId: batch.Party.CustomerAgentId,
|
||
DeptId: batch.Party.CreatedDept,
|
||
TotalCount: quantity,
|
||
ValidCount: quantity,
|
||
SuccessCount: quantity,
|
||
FailCount: 0,
|
||
RunResult: consts.BatchRunResultSuccess,
|
||
BatchInfo: batchInfo,
|
||
StartTime: now,
|
||
EndTime: now,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).Insert()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "写入续费批次主表失败")
|
||
}
|
||
|
||
details := make([]do.BatchDetail, 0, quantity)
|
||
for index, item := range batch.Items {
|
||
rowNo := appRenewBatchSourceRowNo(index)
|
||
details = append(details, do.BatchDetail{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypeRenew,
|
||
DetailStatus: consts.BatchDetailStatusSuccess,
|
||
BlankOutStatus: consts.BlankOutStatusNo,
|
||
RowNo: rowNo,
|
||
ItemId: item.Card.Iccid,
|
||
Snapshot: appRenewSubmitDetailSnapshot(batch, item, batchNo, sourceBatchNo, orderSn, durationMonths, singleCardAmount, cardPeriods, rowNo),
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
})
|
||
}
|
||
if len(details) == 0 {
|
||
return nil
|
||
}
|
||
if _, err = dao.BatchDetail.Ctx(ctx).TX(tx).Data(details).Insert(); err != nil {
|
||
return gerror.Wrap(err, "写入续费批次明细失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) lockAppRenewPrepareBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, batchNo string) (*entity.BatchMain, error) {
|
||
batchNo = strings.TrimSpace(batchNo)
|
||
if batchNo == "" {
|
||
return nil, gerror.New("预处理批次号不能为空")
|
||
}
|
||
var batch *entity.BatchMain
|
||
columns := dao.BatchMain.Columns()
|
||
err := dao.BatchMain.Ctx(ctx).TX(tx).
|
||
Where(columns.BatchNo, batchNo).
|
||
Where(columns.BatchType, consts.BatchTypePrepare).
|
||
Where(columns.RunResult, consts.BatchRunResultSuccess).
|
||
Where(columns.TenantId, user.TenantId).
|
||
Where(columns.CreatedBy, user.UserId).
|
||
LockUpdate().
|
||
Limit(1).
|
||
Scan(&batch)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询续费预处理批次失败")
|
||
}
|
||
if batch == nil || batch.Id == 0 {
|
||
return nil, gerror.New("续费预处理批次不存在或无权确认")
|
||
}
|
||
if strings.TrimSpace(batch.OrderSn) != "" {
|
||
return nil, gerror.New("续费预处理批次已生成订单,请勿重复提交")
|
||
}
|
||
if batch.BatchInfo == nil || batch.BatchInfo.Get("targetBatchType").Int() != consts.BatchTypeRenew {
|
||
return nil, gerror.New("批次不是套餐续费预处理批次")
|
||
}
|
||
return batch, nil
|
||
}
|
||
|
||
func (s *sAppCard) loadAppRenewPrepareBatchCards(ctx context.Context, tx gdb.TX, batchNo string) ([]string, error) {
|
||
var details []*entity.BatchDetail
|
||
columns := dao.BatchDetail.Columns()
|
||
err := dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Fields(columns.RowNo, columns.ItemId).
|
||
Where(columns.BatchNo, strings.TrimSpace(batchNo)).
|
||
Where(columns.BatchType, consts.BatchTypePrepare).
|
||
Where(columns.DetailStatus, consts.BatchDetailStatusSuccess).
|
||
Where(columns.BlankOutStatus, consts.BlankOutStatusNo).
|
||
OrderAsc(columns.RowNo).
|
||
Scan(&details)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询续费预处理明细失败")
|
||
}
|
||
cardValues := make([]string, 0, len(details))
|
||
for _, detail := range details {
|
||
if detail == nil || strings.TrimSpace(detail.ItemId) == "" {
|
||
continue
|
||
}
|
||
cardValues = append(cardValues, strings.TrimSpace(detail.ItemId))
|
||
}
|
||
return cardValues, nil
|
||
}
|
||
|
||
func appRenewSourceBatchNoFromBatch(batch *entity.BatchMain) string {
|
||
if batch == nil {
|
||
return ""
|
||
}
|
||
if batch.BatchInfo != nil {
|
||
if sourceBatchNo := strings.TrimSpace(batch.BatchInfo.Get("sourceBatchNo").String()); sourceBatchNo != "" {
|
||
return sourceBatchNo
|
||
}
|
||
}
|
||
return strings.TrimSpace(batch.SourceBatchNo)
|
||
}
|
||
|
||
func (s *sAppCard) markAppRenewPrepareBatchConfirmed(ctx context.Context, tx gdb.TX, user *entity.SysUser, prepareBatch *entity.BatchMain, batch *appRenewBatchContext, sourceBatchNo string, orderSn string, durationMonths int, singleCardAmount float64, totalAmount float64, beginAt, endAt *gtime.Time, cardPeriods []map[string]any, buyerNote string, now *gtime.Time) error {
|
||
if prepareBatch == nil {
|
||
return gerror.New("续费预处理批次不存在")
|
||
}
|
||
batchNo := strings.TrimSpace(prepareBatch.BatchNo)
|
||
sourceBatchNo = appRenewEffectiveSourceBatchNo(batchNo, sourceBatchNo)
|
||
for index, item := range batch.Items {
|
||
rowNo := appRenewBatchSourceRowNo(index)
|
||
_, err := dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||
Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypePrepare).
|
||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusSuccess).
|
||
Where(dao.BatchDetail.Columns().ItemId, item.Card.Iccid).
|
||
Data(do.BatchDetail{
|
||
RowNo: rowNo,
|
||
Snapshot: appRenewSubmitDetailSnapshot(batch, item, batchNo, sourceBatchNo, orderSn, durationMonths, singleCardAmount, cardPeriods, rowNo),
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "更新续费批次明细快照失败")
|
||
}
|
||
}
|
||
if _, err := dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||
Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypePrepare).
|
||
Data(do.BatchDetail{
|
||
BatchType: consts.BatchTypeRenew,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update(); err != nil {
|
||
return gerror.Wrap(err, "更新续费批次明细类型失败")
|
||
}
|
||
|
||
logFiles, err := s.ensureAppRenewBatchLogFiles(ctx, tx, user, batch, prepareBatch.BatchInfo, batchNo, sourceBatchNo, orderSn, durationMonths, singleCardAmount, totalAmount, beginAt, endAt, cardPeriods, buyerNote, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
batchInfo := appRenewBatchInfoWithLogFiles(
|
||
appRenewSubmitBatchInfo(batch, batchNo, sourceBatchNo, orderSn, durationMonths, singleCardAmount, totalAmount, beginAt, endAt, cardPeriods, buyerNote, now),
|
||
logFiles,
|
||
)
|
||
_, err = dao.BatchMain.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchMain.Columns().Id, prepareBatch.Id).
|
||
Where(dao.BatchMain.Columns().BatchType, consts.BatchTypePrepare).
|
||
Data(do.BatchMain{
|
||
BatchType: consts.BatchTypeRenew,
|
||
BatchStatus: consts.BatchStatusSuccess,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusSuccess,
|
||
OrderSn: orderSn,
|
||
TargetDeptId: batch.Party.CustomerAgentId,
|
||
TotalCount: len(batch.Items),
|
||
ValidCount: len(batch.Items),
|
||
SuccessCount: len(batch.Items),
|
||
FailCount: 0,
|
||
RunResult: consts.BatchRunResultSuccess,
|
||
BatchInfo: batchInfo,
|
||
EndTime: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "更新续费批次失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func appRenewEffectiveSourceBatchNo(batchNo string, sourceBatchNo string) string {
|
||
sourceBatchNo = strings.TrimSpace(sourceBatchNo)
|
||
if sourceBatchNo != "" {
|
||
return sourceBatchNo
|
||
}
|
||
return strings.TrimSpace(batchNo)
|
||
}
|
||
|
||
func (s *sAppCard) insertAppRenewBatchOrderItem(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appRenewBatchContext, orderId int64, orderSn string, batchNo string, sourceBatchNo string, durationMonths int, singleCardAmount float64, totalAmount float64, beginAt, endAt *gtime.Time, cardPeriods []map[string]any, now *gtime.Time) error {
|
||
sourceBatchNo = appRenewEffectiveSourceBatchNo(batchNo, sourceBatchNo)
|
||
pkg := batch.Package
|
||
durationYears := appRenewDurationYearsFromLegacy(durationMonths, batch.Durations)
|
||
skuData := appRenewMarshalJSON([]map[string]any{
|
||
{"skuName": "套餐", "skuValue": pkg.PackageName, "packageCode": pkg.PackageCode, "packageType": pkg.PackageType},
|
||
{"skuName": "续费卡数", "skuValue": fmt.Sprintf("%d张", len(batch.Items)), "quantity": len(batch.Items)},
|
||
{"skuName": "续费时长", "skuValue": appRenewDurationYearLabel(durationYears), "durationYears": durationYears, "durationMonths": durationMonths},
|
||
{"skuName": "单卡金额", "skuValue": fmt.Sprintf("¥%.2f", singleCardAmount)},
|
||
})
|
||
extraSkuData := appRenewMarshalJSON(map[string]any{
|
||
"business": "miniapp_card_renew_batch",
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"sourceBatchNo": strings.TrimSpace(sourceBatchNo),
|
||
"packageCode": pkg.PackageCode,
|
||
"durationYears": durationYears,
|
||
"durationValue": durationYears,
|
||
"durationMonths": durationMonths,
|
||
"actualDurationMonths": durationMonths,
|
||
"renewDurationUnit": appRenewDurationUnitYear,
|
||
"renewDurationUnitText": appRenewDurationUnitYearText,
|
||
"unitPrice": batch.Price.UnitPrice,
|
||
"priceSource": batch.Price.PriceSource,
|
||
"agentDefaultPriceId": batch.Price.AgentDefaultPriceId,
|
||
"targetPriceId": batch.Price.TargetPriceId,
|
||
"singleCardAmount": singleCardAmount,
|
||
"totalAmount": totalAmount,
|
||
"quantity": len(batch.Items),
|
||
"cards": cardPeriods,
|
||
})
|
||
_, err := dao.TOrderItem.Ctx(ctx).TX(tx).Data(do.TOrderItem{
|
||
OrderId: orderId,
|
||
OrderSn: orderSn,
|
||
CustomerType: batch.Party.CustomerType,
|
||
CustomerAgentId: batch.Party.CustomerAgentId,
|
||
EndUserId: batch.Party.EndUserId,
|
||
Price: singleCardAmount,
|
||
Quantity: len(batch.Items),
|
||
ProductId: pkg.Id,
|
||
ProductName: pkg.PackageName,
|
||
ProductSn: pkg.PackageCode,
|
||
PicThumb: "",
|
||
SkuId: 0,
|
||
SkuData: skuData,
|
||
DeliveryQuantity: 0,
|
||
ProductType: consts.ProductTypeCard,
|
||
IsGift: 0,
|
||
AgentId: batch.Party.AgentId,
|
||
PrepayPrice: 0,
|
||
Commission: "",
|
||
OriginPrice: batch.Price.UnitPrice,
|
||
PromotionData: "",
|
||
ExtraSkuData: extraSkuData,
|
||
PayMode: consts.PayModeYearMonth,
|
||
ServiceBeginTime: beginAt,
|
||
ServiceEndTime: endAt,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).Insert()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "创建批量续费订单项失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func appRenewPrepareBatchInfo(batch *appRenewBatchContext, renewBatchNo string, orderSn string, durationMonths int, totalAmount float64) *gjson.Json {
|
||
durationYears := appRenewDurationYearsFromLegacy(durationMonths, batch.Durations)
|
||
info := map[string]any{
|
||
"source": "app",
|
||
"targetBatchType": consts.BatchTypeRenew,
|
||
"targetBatchName": "套餐续费",
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"currentPackageCode": batch.CurrentPackage.PackageCode,
|
||
"currentPackageName": batch.CurrentPackage.PackageName,
|
||
"currentPackageType": batch.CurrentPackage.PackageType,
|
||
"packageCode": batch.Package.PackageCode,
|
||
"packageName": batch.Package.PackageName,
|
||
"packageType": batch.Package.PackageType,
|
||
"quantity": len(batch.Items),
|
||
"priceSource": batch.Price.PriceSource,
|
||
"agentDefaultPriceId": batch.Price.AgentDefaultPriceId,
|
||
"targetPriceId": batch.Price.TargetPriceId,
|
||
"renewUnitPrice": batch.Price.UnitPrice,
|
||
"renewDurationUnit": appRenewDurationUnitYear,
|
||
"renewDurationUnitText": appRenewDurationUnitYearText,
|
||
"renewableDurationOptions": batch.Durations,
|
||
"renewablePackageCodes": batch.RenewablePackageCodes,
|
||
"renewablePackages": appRenewTargetSnapshotList(batch),
|
||
"cards": appRenewBatchCardSnapshotList(batch),
|
||
}
|
||
if strings.TrimSpace(renewBatchNo) != "" {
|
||
info["renewBatchNo"] = strings.TrimSpace(renewBatchNo)
|
||
}
|
||
if strings.TrimSpace(orderSn) != "" {
|
||
info["orderSn"] = strings.TrimSpace(orderSn)
|
||
}
|
||
if durationMonths > 0 {
|
||
info["durationYears"] = durationYears
|
||
info["durationValue"] = durationYears
|
||
info["durationMonths"] = durationMonths
|
||
info["actualDurationMonths"] = durationMonths
|
||
info["amount"] = fmt.Sprintf("%.2f", totalAmount)
|
||
}
|
||
return gjson.New(info)
|
||
}
|
||
|
||
func appRenewSubmitBatchInfo(batch *appRenewBatchContext, batchNo string, sourceBatchNo string, orderSn string, durationMonths int, singleCardAmount float64, totalAmount float64, beginAt, endAt *gtime.Time, cardPeriods []map[string]any, buyerNote string, now *gtime.Time) *gjson.Json {
|
||
durationYears := appRenewDurationYearsFromLegacy(durationMonths, batch.Durations)
|
||
return gjson.New(map[string]any{
|
||
"source": "app",
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"sourceBatchNo": strings.TrimSpace(sourceBatchNo),
|
||
"orderSn": strings.TrimSpace(orderSn),
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"currentPackageCode": batch.CurrentPackage.PackageCode,
|
||
"currentPackageName": batch.CurrentPackage.PackageName,
|
||
"currentPackageType": batch.CurrentPackage.PackageType,
|
||
"packageCode": batch.Package.PackageCode,
|
||
"packageName": batch.Package.PackageName,
|
||
"packageType": batch.Package.PackageType,
|
||
"durationYears": durationYears,
|
||
"durationValue": durationYears,
|
||
"durationMonths": durationMonths,
|
||
"actualDurationMonths": durationMonths,
|
||
"giftDurationMonths": 0,
|
||
"quantity": len(batch.Items),
|
||
"singleCardAmount": singleCardAmount,
|
||
"amount": fmt.Sprintf("%.2f", totalAmount),
|
||
"offlineContractCompleted": true,
|
||
"buyerNote": strings.TrimSpace(buyerNote),
|
||
"adminNote": "",
|
||
"remark": "",
|
||
"renewDate": appRenewBatchRenewDate(now),
|
||
"renewTime": now,
|
||
"priceSource": batch.Price.PriceSource,
|
||
"agentDefaultPriceId": batch.Price.AgentDefaultPriceId,
|
||
"targetPriceId": batch.Price.TargetPriceId,
|
||
"renewUnitPrice": batch.Price.UnitPrice,
|
||
"renewDurationUnit": appRenewDurationUnitYear,
|
||
"renewDurationUnitText": appRenewDurationUnitYearText,
|
||
"renewableDurationOptions": batch.Durations,
|
||
"renewablePackageCodes": batch.RenewablePackageCodes,
|
||
"renewablePackages": appRenewTargetSnapshotList(batch),
|
||
"serviceBeginAt": appRenewFormatTime(beginAt),
|
||
"serviceEndAt": appRenewFormatTime(endAt),
|
||
"cards": cardPeriods,
|
||
})
|
||
}
|
||
|
||
func appRenewPrepareDetailSnapshot(batch *appRenewBatchContext, item *appRenewPackageContext, rowNo int) *gjson.Json {
|
||
card := item.Card
|
||
return gjson.New(map[string]any{
|
||
"ICCID": card.Iccid,
|
||
"MSISDN": card.Msisdn,
|
||
"IMSI": card.Imsi,
|
||
"rowNo": rowNo,
|
||
"cardId": card.Id,
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"packageCode": batch.Package.PackageCode,
|
||
"packageName": batch.Package.PackageName,
|
||
"currentPackageCode": item.ExpectedCurrentPackageCode,
|
||
"currentPackageName": batch.CurrentPackage.PackageName,
|
||
"priceSource": batch.Price.PriceSource,
|
||
"renewUnitPrice": batch.Price.UnitPrice,
|
||
"agentDefaultPriceId": batch.Price.AgentDefaultPriceId,
|
||
"targetPriceId": batch.Price.TargetPriceId,
|
||
"expireTime": appRenewFormatTime(item.expireAt()),
|
||
})
|
||
}
|
||
|
||
func appRenewSubmitDetailSnapshot(batch *appRenewBatchContext, item *appRenewPackageContext, batchNo string, sourceBatchNo string, orderSn string, durationMonths int, singleCardAmount float64, cardPeriods []map[string]any, rowNo int) *gjson.Json {
|
||
card := item.Card
|
||
period := appRenewCardPeriodAt(cardPeriods, rowNo-2)
|
||
durationYears := appRenewDurationYearsFromLegacy(durationMonths, batch.Durations)
|
||
return gjson.New(map[string]any{
|
||
"ICCID": card.Iccid,
|
||
"MSISDN": card.Msisdn,
|
||
"IMSI": card.Imsi,
|
||
"rowNo": rowNo,
|
||
"cardId": card.Id,
|
||
"renewBatchNo": strings.TrimSpace(batchNo),
|
||
"sourceBatchNo": strings.TrimSpace(sourceBatchNo),
|
||
"orderSn": strings.TrimSpace(orderSn),
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"packageCode": batch.Package.PackageCode,
|
||
"packageName": batch.Package.PackageName,
|
||
"oldSubscriptionNo": period["oldSubscriptionNo"],
|
||
"newSubscriptionNo": period["newSubscriptionNo"],
|
||
"currentPackageCode": item.ExpectedCurrentPackageCode,
|
||
"currentPackageName": batch.CurrentPackage.PackageName,
|
||
"durationYears": durationYears,
|
||
"durationValue": durationYears,
|
||
"durationMonths": durationMonths,
|
||
"actualDurationMonths": durationMonths,
|
||
"singleCardAmount": singleCardAmount,
|
||
"priceSource": batch.Price.PriceSource,
|
||
"renewUnitPrice": batch.Price.UnitPrice,
|
||
"renewDurationUnit": appRenewDurationUnitYear,
|
||
"renewDurationUnitText": appRenewDurationUnitYearText,
|
||
"agentDefaultPriceId": batch.Price.AgentDefaultPriceId,
|
||
"targetPriceId": batch.Price.TargetPriceId,
|
||
"serviceBeginAt": period["serviceBeginAt"],
|
||
"serviceEndAt": period["serviceEndAt"],
|
||
"renewEffectiveTime": period["renewEffectiveTime"],
|
||
"renewExpectedExpireTime": period["renewExpectedExpireTime"],
|
||
"cardFlowPkgEndAt": period["cardFlowPkgEndAt"],
|
||
})
|
||
}
|
||
|
||
func appRenewBatchSourceRowNo(index int) int {
|
||
return index + 2
|
||
}
|
||
|
||
func appRenewCardPeriodAt(cardPeriods []map[string]any, index int) map[string]any {
|
||
if index >= 0 && index < len(cardPeriods) && cardPeriods[index] != nil {
|
||
return cardPeriods[index]
|
||
}
|
||
return map[string]any{}
|
||
}
|
||
|
||
func appRenewBatchCardSnapshotList(batch *appRenewBatchContext) []map[string]any {
|
||
cards := make([]map[string]any, 0, len(batch.Items))
|
||
for _, item := range batch.Items {
|
||
cards = append(cards, map[string]any{
|
||
"cardId": item.Card.Id,
|
||
"iccid": item.Card.Iccid,
|
||
"msisdn": item.Card.Msisdn,
|
||
"currentPackage": item.ExpectedCurrentPackageCode,
|
||
"expireTime": appRenewFormatTime(item.expireAt()),
|
||
})
|
||
}
|
||
return cards
|
||
}
|
||
|
||
func appRenewTargetSnapshotList(batch *appRenewBatchContext) []map[string]any {
|
||
if batch == nil {
|
||
return nil
|
||
}
|
||
result := make([]map[string]any, 0, len(batch.RenewableTargets))
|
||
for _, target := range batch.RenewableTargets {
|
||
if target == nil || target.Package == nil || target.Price == nil {
|
||
continue
|
||
}
|
||
result = append(result, map[string]any{
|
||
"packageCode": target.Package.PackageCode,
|
||
"packageName": target.Package.PackageName,
|
||
"packageType": target.Package.PackageType,
|
||
"flowMb": target.Package.FlowMb,
|
||
"renewUnitPrice": target.Price.UnitPrice,
|
||
"unitPrice": target.Price.UnitPrice,
|
||
"priceSource": target.Price.PriceSource,
|
||
"agentDefaultPriceId": target.Price.AgentDefaultPriceId,
|
||
"targetPriceId": target.Price.TargetPriceId,
|
||
"renewableDurationOptions": target.Durations,
|
||
"durations": target.Durations,
|
||
})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appRenewPrepareSnapshotFromBatch(batch *entity.BatchMain) (*appRenewPrepareBatchSnapshot, error) {
|
||
if batch == nil || batch.BatchInfo == nil {
|
||
return nil, gerror.New("续费预处理批次缺少报价信息")
|
||
}
|
||
options := appNormalizeRenewYearOptions(batch.BatchInfo.Get("renewableDurationOptions").Ints())
|
||
sort.Ints(options)
|
||
snapshot := &appRenewPrepareBatchSnapshot{
|
||
CustomerAgentId: batch.BatchInfo.Get("customerAgentId").Int64(),
|
||
CurrentPackageCode: strings.TrimSpace(batch.BatchInfo.Get("currentPackageCode").String()),
|
||
CurrentPackageName: strings.TrimSpace(batch.BatchInfo.Get("currentPackageName").String()),
|
||
PackageCode: strings.TrimSpace(batch.BatchInfo.Get("packageCode").String()),
|
||
PackageName: strings.TrimSpace(batch.BatchInfo.Get("packageName").String()),
|
||
PriceSource: strings.TrimSpace(batch.BatchInfo.Get("priceSource").String()),
|
||
AgentDefaultPriceId: batch.BatchInfo.Get("agentDefaultPriceId").Int64(),
|
||
TargetPriceId: batch.BatchInfo.Get("targetPriceId").Int64(),
|
||
RenewUnitPrice: appRoundAmount(batch.BatchInfo.Get("renewUnitPrice").Float64()),
|
||
RenewableDurationItems: options,
|
||
RenewablePackageCodes: appNormalizePackageCodes(batch.BatchInfo.Get("renewablePackageCodes").Strings()),
|
||
Targets: appRenewPrepareTargetSnapshots(batch.BatchInfo),
|
||
Quantity: batch.BatchInfo.Get("quantity").Int(),
|
||
}
|
||
if snapshot.CurrentPackageCode == "" {
|
||
snapshot.CurrentPackageCode = snapshot.PackageCode
|
||
}
|
||
if len(snapshot.RenewablePackageCodes) == 0 {
|
||
snapshot.RenewablePackageCodes = []string{snapshot.PackageCode}
|
||
}
|
||
if len(snapshot.Targets) == 0 && snapshot.PackageCode != "" {
|
||
snapshot.Targets = map[string]*appRenewPrepareTargetSnapshot{
|
||
snapshot.PackageCode: {
|
||
PackageCode: snapshot.PackageCode,
|
||
PackageName: snapshot.PackageName,
|
||
RenewUnitPrice: snapshot.RenewUnitPrice,
|
||
PriceSource: snapshot.PriceSource,
|
||
AgentDefaultPriceId: snapshot.AgentDefaultPriceId,
|
||
TargetPriceId: snapshot.TargetPriceId,
|
||
Durations: append([]int(nil), snapshot.RenewableDurationItems...),
|
||
},
|
||
}
|
||
}
|
||
if snapshot.CustomerAgentId <= 0 || snapshot.PackageCode == "" || snapshot.RenewUnitPrice < 0 || len(snapshot.RenewableDurationItems) == 0 {
|
||
return nil, gerror.New("续费预处理批次报价信息不完整")
|
||
}
|
||
return snapshot, nil
|
||
}
|
||
|
||
func appSameRenewPrepareSnapshot(snapshot *appRenewPrepareBatchSnapshot, batch *appRenewBatchContext) bool {
|
||
if snapshot == nil || batch == nil || batch.CurrentPackage == nil || batch.Package == nil || batch.Price == nil || batch.Party == nil {
|
||
return false
|
||
}
|
||
if snapshot.CustomerAgentId != batch.Party.CustomerAgentId ||
|
||
snapshot.CurrentPackageCode != strings.TrimSpace(batch.CurrentPackage.PackageCode) ||
|
||
snapshot.Quantity != len(batch.Items) {
|
||
return false
|
||
}
|
||
target := appRenewPrepareSnapshotTarget(snapshot, batch.Package.PackageCode)
|
||
return appSameRenewPrepareTargetSnapshot(target, batch)
|
||
}
|
||
|
||
func appRenewPrepareTargetSnapshots(info *gjson.Json) map[string]*appRenewPrepareTargetSnapshot {
|
||
result := make(map[string]*appRenewPrepareTargetSnapshot)
|
||
if info == nil {
|
||
return result
|
||
}
|
||
for _, raw := range info.Get("renewablePackages").Interfaces() {
|
||
item := gjson.New(raw)
|
||
code := strings.TrimSpace(item.Get("packageCode").String())
|
||
if code == "" {
|
||
continue
|
||
}
|
||
durations := appNormalizeRenewYearOptions(item.Get("renewableDurationOptions").Ints())
|
||
if len(durations) == 0 {
|
||
durations = appNormalizeRenewYearOptions(item.Get("durations").Ints())
|
||
}
|
||
sort.Ints(durations)
|
||
result[code] = &appRenewPrepareTargetSnapshot{
|
||
PackageCode: code,
|
||
PackageName: strings.TrimSpace(item.Get("packageName").String()),
|
||
RenewUnitPrice: appRoundAmount(appFirstPositiveFloat64(item.Get("renewUnitPrice").Float64(), item.Get("unitPrice").Float64())),
|
||
PriceSource: strings.TrimSpace(item.Get("priceSource").String()),
|
||
AgentDefaultPriceId: item.Get("agentDefaultPriceId").Int64(),
|
||
TargetPriceId: item.Get("targetPriceId").Int64(),
|
||
Durations: durations,
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appRenewSubmitTargetPackageCode(snapshot *appRenewPrepareBatchSnapshot, requestedPackageCode string) string {
|
||
requestedPackageCode = strings.TrimSpace(requestedPackageCode)
|
||
if requestedPackageCode != "" {
|
||
return requestedPackageCode
|
||
}
|
||
if snapshot == nil {
|
||
return ""
|
||
}
|
||
return strings.TrimSpace(snapshot.PackageCode)
|
||
}
|
||
|
||
func appRenewPrepareSnapshotTarget(snapshot *appRenewPrepareBatchSnapshot, packageCode string) *appRenewPrepareTargetSnapshot {
|
||
if snapshot == nil {
|
||
return nil
|
||
}
|
||
packageCode = strings.TrimSpace(packageCode)
|
||
if packageCode == "" {
|
||
return nil
|
||
}
|
||
if target := snapshot.Targets[packageCode]; target != nil {
|
||
return target
|
||
}
|
||
if snapshot.PackageCode == packageCode {
|
||
return &appRenewPrepareTargetSnapshot{
|
||
PackageCode: snapshot.PackageCode,
|
||
PackageName: snapshot.PackageName,
|
||
RenewUnitPrice: snapshot.RenewUnitPrice,
|
||
PriceSource: snapshot.PriceSource,
|
||
AgentDefaultPriceId: snapshot.AgentDefaultPriceId,
|
||
TargetPriceId: snapshot.TargetPriceId,
|
||
Durations: append([]int(nil), snapshot.RenewableDurationItems...),
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func appSameRenewPrepareTargetSnapshot(snapshot *appRenewPrepareTargetSnapshot, batch *appRenewBatchContext) bool {
|
||
if snapshot == nil || batch == nil || batch.Package == nil || batch.Price == nil {
|
||
return false
|
||
}
|
||
durations := append([]int(nil), batch.Durations...)
|
||
sort.Ints(durations)
|
||
return snapshot.PackageCode == strings.TrimSpace(batch.Package.PackageCode) &&
|
||
appRoundAmount(snapshot.RenewUnitPrice) == appRoundAmount(batch.Price.UnitPrice) &&
|
||
snapshot.PriceSource == strings.TrimSpace(batch.Price.PriceSource) &&
|
||
snapshot.AgentDefaultPriceId == batch.Price.AgentDefaultPriceId &&
|
||
snapshot.TargetPriceId == batch.Price.TargetPriceId &&
|
||
appSameIntSlice(snapshot.Durations, durations)
|
||
}
|
||
|
||
func appFirstPositiveFloat64(values ...float64) float64 {
|
||
for _, value := range values {
|
||
if value > 0 {
|
||
return value
|
||
}
|
||
}
|
||
if len(values) > 0 {
|
||
return values[0]
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func appNormalizeRenewCardValues(values []string) []string {
|
||
seen := map[string]struct{}{}
|
||
result := make([]string, 0, len(values))
|
||
for _, raw := range values {
|
||
parts := strings.FieldsFunc(raw, func(r rune) bool {
|
||
switch r {
|
||
case ',', ',', ';', ';', '\n', '\r', '\t', ' ':
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
})
|
||
for _, part := range parts {
|
||
value := strings.TrimSpace(part)
|
||
if value == "" {
|
||
continue
|
||
}
|
||
if _, exists := seen[value]; exists {
|
||
continue
|
||
}
|
||
seen[value] = struct{}{}
|
||
result = append(result, value)
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appSameRenewOrderParty(left, right *appRenewOrderParty) bool {
|
||
if left == nil || right == nil {
|
||
return false
|
||
}
|
||
return left.CustomerType == right.CustomerType &&
|
||
left.CustomerAgentId == right.CustomerAgentId &&
|
||
left.EndUserId == right.EndUserId &&
|
||
left.AgentId == right.AgentId &&
|
||
left.CreatedDept == right.CreatedDept
|
||
}
|
||
|
||
func appSameRenewPrice(left, right *appRenewPriceSnapshot) bool {
|
||
if left == nil || right == nil {
|
||
return false
|
||
}
|
||
return appRoundAmount(left.UnitPrice) == appRoundAmount(right.UnitPrice) &&
|
||
strings.TrimSpace(left.PriceSource) == strings.TrimSpace(right.PriceSource) &&
|
||
left.AgentDefaultPriceId == right.AgentDefaultPriceId &&
|
||
left.TargetPriceId == right.TargetPriceId
|
||
}
|
||
|
||
func appSameIntSlice(left, right []int) bool {
|
||
if len(left) != len(right) {
|
||
return false
|
||
}
|
||
for index := range left {
|
||
if left[index] != right[index] {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func appSameStringSet(left, right []string) bool {
|
||
if len(left) != len(right) {
|
||
return false
|
||
}
|
||
counts := make(map[string]int, len(left))
|
||
for _, item := range left {
|
||
counts[strings.TrimSpace(item)]++
|
||
}
|
||
for _, item := range right {
|
||
key := strings.TrimSpace(item)
|
||
if counts[key] == 0 {
|
||
return false
|
||
}
|
||
counts[key]--
|
||
}
|
||
return true
|
||
}
|
||
|
||
func appRenewBatchMaxExpireAfter(items []*appRenewPackageContext, months int) *gtime.Time {
|
||
var maxExpireAt *gtime.Time
|
||
for _, item := range items {
|
||
maxExpireAt = appLaterTime(maxExpireAt, appRenewExpireAfter(item.expireAt(), months))
|
||
}
|
||
return maxExpireAt
|
||
}
|
||
|
||
func appEarlierTime(current, next *gtime.Time) *gtime.Time {
|
||
if current == nil || current.IsZero() {
|
||
return next
|
||
}
|
||
if next != nil && !next.IsZero() && next.Before(current) {
|
||
return next
|
||
}
|
||
return current
|
||
}
|
||
|
||
func appLaterTime(current, next *gtime.Time) *gtime.Time {
|
||
if current == nil || current.IsZero() {
|
||
return next
|
||
}
|
||
if next != nil && !next.IsZero() && next.After(current) {
|
||
return next
|
||
}
|
||
return current
|
||
}
|
||
|
||
func appIndexOfInt(values []int, target int) int {
|
||
for index, value := range values {
|
||
if value == target {
|
||
return index
|
||
}
|
||
}
|
||
return -1
|
||
}
|
||
|
||
func appRenewBatchEffectiveRule() string {
|
||
return "按各卡当前套餐到期时间分别顺延"
|
||
}
|