1296 lines
48 KiB
Go
1296 lines
48 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"reflect"
|
||
"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/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"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
)
|
||
|
||
type appCardChangeBatchContext struct {
|
||
Items []*appCardChangeItemContext
|
||
CurrentPackage *entity.TSimFlowPackageInfo
|
||
TargetOptions []*appCardChangeTargetContext
|
||
Party *appRenewOrderParty
|
||
OwnerType string
|
||
}
|
||
|
||
type appCardChangeItemContext struct {
|
||
Card *appCardQueryRow
|
||
LatestBind *entity.TSimCardBindPkg
|
||
ExpectedCurrentPackageCode string
|
||
RowNo int
|
||
}
|
||
|
||
type appCardChangeTargetContext struct {
|
||
Package *entity.TSimFlowPackageInfo
|
||
Price *appRenewPriceSnapshot
|
||
Durations []int
|
||
}
|
||
|
||
type appCardChangePrepareSnapshot struct {
|
||
CustomerAgentId int64
|
||
OldPackageCode string
|
||
Quantity int
|
||
Targets map[string]*appCardChangeTargetSnapshot
|
||
}
|
||
|
||
type appCardChangeTargetSnapshot struct {
|
||
PackageCode string
|
||
PackageName string
|
||
UnitPrice float64
|
||
PriceSource string
|
||
AgentDefaultPriceId int64
|
||
TargetPriceId int64
|
||
Durations []int
|
||
}
|
||
|
||
type appCardChangeExecutionContext struct {
|
||
OldBindsByIccid map[string]*entity.TSimCardBindPkg
|
||
NewBindsByIccid map[string]*entity.TSimCardBindPkg
|
||
ServiceBeginAt *gtime.Time
|
||
ServiceEndAt *gtime.Time
|
||
CardPeriods []map[string]any
|
||
}
|
||
|
||
// ChangePrepare 校验企业用户套餐变更条件,并写入预处理批次。
|
||
func (s *sAppCard) ChangePrepare(ctx context.Context, param *model.AppCardChangePrepareParam) (res *model.AppCardChangePrepareModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardChangePrepareParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
batch, err := s.buildChangeBatchContext(ctx, user, param.CardValues, appChangeRequestedPackageCode(param.NewPackageCode, param.TargetPackageCode))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
now := gtime.Now()
|
||
batchNo, err := appNextBatchNo(ctx, consts.BatchNoPrefixChange)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err = dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
return s.insertAppChangePrepareBatch(ctx, tx, user, batch, batchNo, now)
|
||
}); err != nil {
|
||
return nil, gerror.Wrap(err, "写入套餐变更预处理批次失败")
|
||
}
|
||
|
||
res = s.buildChangePrepareModel(ctx, user, batch)
|
||
res.BatchNo = batchNo
|
||
res.BatchType = consts.BatchTypePrepare
|
||
return res, nil
|
||
}
|
||
|
||
// ChangeSubmit 确认套餐变更预处理批次,并在同一事务内完成余额扣款和本地套餐变更。
|
||
func (s *sAppCard) ChangeSubmit(ctx context.Context, param *model.AppCardChangeSubmitParam) (res *model.AppCardChangeSubmitModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardChangeSubmitParam{}
|
||
}
|
||
if param.DurationMonths <= 0 {
|
||
return nil, gerror.New("请选择套餐时长")
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if s.isTerminalUser(ctx, user) {
|
||
return nil, gerror.New("终端用户暂不能使用套餐变更功能")
|
||
}
|
||
|
||
now := gtime.Now()
|
||
var batch *appCardChangeBatchContext
|
||
var snapshot *appCardChangePrepareSnapshot
|
||
var target *appCardChangeTargetContext
|
||
var orderId int64
|
||
var orderSn string
|
||
var serviceBeginAt *gtime.Time
|
||
var serviceEndAt *gtime.Time
|
||
var cardPeriods []map[string]any
|
||
|
||
err = dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
prepareBatch, err := s.lockAppChangePrepareBatch(ctx, tx, user, param.BatchNo)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
snapshot, err = appChangePrepareSnapshotFromBatch(prepareBatch)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
cardValues, err := s.loadAppChangePrepareBatchCards(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.buildChangeBatchContext(ctx, user, cardValues, strings.TrimSpace(param.NewPackageCode))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !appSameChangePrepareSnapshot(snapshot, batch) {
|
||
return gerror.New("当前套餐变更客户、套餐或价格已变化,请重新校验")
|
||
}
|
||
target = batch.findTarget(param.NewPackageCode)
|
||
if target == nil {
|
||
return gerror.New("目标套餐不在当前可变更套餐范围内")
|
||
}
|
||
if !appContainsInt(target.Durations, param.DurationMonths) {
|
||
return gerror.New("套餐时长不在目标套餐可办理范围内")
|
||
}
|
||
|
||
unitAmount := appRoundAmount(target.Price.UnitPrice * float64(param.DurationMonths))
|
||
totalAmount := appRoundAmount(unitAmount * float64(len(batch.Items)))
|
||
exec, err := s.prepareAppChangeExecution(ctx, tx, batch, target, param.DurationMonths, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
serviceBeginAt, serviceEndAt = exec.ServiceBeginAt, exec.ServiceEndAt
|
||
cardPeriods = exec.CardPeriods
|
||
orderSn = appOrderNo("SO", user.UserId, now)
|
||
if _, err = s.deductAppBalance(ctx, tx, user, orderSn, totalAmount, "套餐变更", now); err != nil {
|
||
return err
|
||
}
|
||
orderId, err = s.insertAppChangeOrder(ctx, tx, user, batch.Party, strings.TrimSpace(param.BuyerNote), orderSn, totalAmount, now)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = s.completeAppChangeSubscriptions(ctx, tx, user, batch, target, exec, prepareBatch.BatchNo, orderSn, param.DurationMonths, now); err != nil {
|
||
return err
|
||
}
|
||
if err = s.insertAppChangeOrderItem(ctx, tx, user, batch, target, orderId, orderSn, prepareBatch.BatchNo, param.DurationMonths, unitAmount, totalAmount, serviceBeginAt, serviceEndAt, cardPeriods, now); err != nil {
|
||
return err
|
||
}
|
||
return s.markAppChangePrepareBatchConfirmed(ctx, tx, user, prepareBatch, batch, target, exec, orderSn, param.DurationMonths, unitAmount, totalAmount, strings.TrimSpace(param.BuyerNote), strings.TrimSpace(param.Remark), now)
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if batch == nil || target == nil {
|
||
return nil, gerror.New("套餐变更提交结果不完整")
|
||
}
|
||
|
||
cardModels := s.changeBatchCardModels(ctx, user, batch)
|
||
currentPackage := s.renewPackageModel(batch.Items[0].Card, batch.CurrentPackage, batch.OwnerType)
|
||
newPackage := s.renewPackageModel(batch.Items[0].Card, target.Package, batch.OwnerType)
|
||
totalAmount := appRoundAmount(target.Price.UnitPrice * float64(param.DurationMonths) * float64(len(batch.Items)))
|
||
option := &model.AppRenewOptionModel{
|
||
DurationMonths: param.DurationMonths,
|
||
Label: appRenewDurationLabel(param.DurationMonths),
|
||
UnitPrice: target.Price.UnitPrice,
|
||
TotalAmount: totalAmount,
|
||
PriceText: fmt.Sprintf("¥%.2f", totalAmount),
|
||
ExpireAfter: serviceEndAt,
|
||
Recommended: appRenewRecommendIndex(target.Durations) == appIndexOfInt(target.Durations, param.DurationMonths),
|
||
}
|
||
res = &model.AppCardChangeSubmitModel{
|
||
BatchNo: strings.TrimSpace(param.BatchNo),
|
||
BatchType: consts.BatchTypeChange,
|
||
OrderId: orderId,
|
||
OrderSn: orderSn,
|
||
TotalAmount: totalAmount,
|
||
PriceText: option.PriceText,
|
||
Quantity: len(cardModels),
|
||
OrderStatus: consts.OrderStatusCompleted,
|
||
PayStatus: consts.PayStatusPaid,
|
||
Status: "success",
|
||
EffectiveRule: appChangeEffectiveRule(),
|
||
Cards: cardModels,
|
||
CurrentPackage: currentPackage,
|
||
NewPackage: newPackage,
|
||
Option: option,
|
||
}
|
||
_ = s.publishAppBusinessAsyncEvent(ctx, appBusinessAsyncEvent{
|
||
EventType: appAsyncEventChangeCompleted,
|
||
OrderSn: orderSn,
|
||
BatchNo: strings.TrimSpace(param.BatchNo),
|
||
BatchType: consts.BatchTypeChange,
|
||
TenantId: user.TenantId,
|
||
UserId: user.UserId,
|
||
DeptId: user.DeptId,
|
||
Trigger: "change_submit",
|
||
})
|
||
return res, nil
|
||
}
|
||
|
||
func (s *sAppCard) buildChangeBatchContext(ctx context.Context, user *entity.SysUser, cardValues []string, targetPackageCode string) (*appCardChangeBatchContext, error) {
|
||
if s.isTerminalUser(ctx, user) {
|
||
return nil, gerror.New("终端用户暂不能使用套餐变更功能")
|
||
}
|
||
if user.DeptId <= 0 {
|
||
return nil, gerror.New("当前账号未绑定企业,不能使用套餐变更功能")
|
||
}
|
||
values := appNormalizeRenewCardValues(cardValues)
|
||
if len(values) == 0 {
|
||
return nil, gerror.New("请选择需要变更套餐的卡")
|
||
}
|
||
if len(values) > 100 {
|
||
return nil, gerror.New("单次批量套餐变更最多支持100张卡")
|
||
}
|
||
|
||
batch := &appCardChangeBatchContext{
|
||
Items: make([]*appCardChangeItemContext, 0, len(values)),
|
||
OwnerType: appCardOwnerAgent,
|
||
}
|
||
seenIccid := make(map[string]struct{}, len(values))
|
||
customerAgentId := int64(0)
|
||
currentPackageCode := ""
|
||
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{}{}
|
||
|
||
latestBind, err := s.loadLatestRenewBind(ctx, card)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if latestBind == nil || latestBind.Id == 0 {
|
||
return nil, gerror.Newf("ICCID没有订购记录,不能变更套餐:%s", iccid)
|
||
}
|
||
cardPackageCode := strings.TrimSpace(appCardCurrentPackageCode(card))
|
||
if cardPackageCode == "" || card.PackageStatus == consts.PackageStatusNone {
|
||
return nil, gerror.Newf("ICCID没有当前套餐,不能变更套餐:%s", iccid)
|
||
}
|
||
if strings.TrimSpace(latestBind.PackageCode) != cardPackageCode {
|
||
return nil, gerror.Newf("ICCID当前套餐与最新订购记录不一致,不能变更套餐:%s", iccid)
|
||
}
|
||
if currentPackageCode == "" {
|
||
currentPackageCode = cardPackageCode
|
||
} else if currentPackageCode != cardPackageCode {
|
||
return nil, gerror.New("当前批次下存在多个当前套餐,不能统一变更")
|
||
}
|
||
if customerAgentId == 0 {
|
||
customerAgentId = card.DeptId
|
||
} else if customerAgentId != card.DeptId {
|
||
return nil, gerror.New("当前批次下存在多个客户,不能统一变更套餐")
|
||
}
|
||
|
||
batch.Items = append(batch.Items, &appCardChangeItemContext{
|
||
Card: card,
|
||
LatestBind: latestBind,
|
||
ExpectedCurrentPackageCode: cardPackageCode,
|
||
RowNo: index + 2,
|
||
})
|
||
}
|
||
|
||
currentPackage, err := s.findRenewPackageOrNil(ctx, user.TenantId, currentPackageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if currentPackage == nil {
|
||
return nil, gerror.Newf("当前套餐编码 %s 未配置套餐信息", currentPackageCode)
|
||
}
|
||
batch.CurrentPackage = currentPackage
|
||
party, err := s.renewOrderParty(ctx, user, batch.Items[0].Card)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
batch.Party = party
|
||
|
||
targets, err := s.resolveChangeTargetPackages(ctx, user, batch, strings.TrimSpace(targetPackageCode))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(targets) == 0 {
|
||
return nil, gerror.New("当前套餐没有可变更的目标套餐")
|
||
}
|
||
batch.TargetOptions = targets
|
||
return batch, nil
|
||
}
|
||
|
||
func (s *sAppCard) resolveChangeTargetPackages(ctx context.Context, user *entity.SysUser, batch *appCardChangeBatchContext, targetPackageCode string) ([]*appCardChangeTargetContext, error) {
|
||
codes := appChangePackageCodes(batch.CurrentPackage.ChangeablePackageCodes)
|
||
if len(codes) == 0 {
|
||
return nil, gerror.New("当前套餐未配置可变更套餐")
|
||
}
|
||
currentCode := strings.TrimSpace(batch.CurrentPackage.PackageCode)
|
||
filteredCodes := make([]string, 0, len(codes))
|
||
for _, code := range codes {
|
||
code = strings.TrimSpace(code)
|
||
if code == "" || code == currentCode {
|
||
continue
|
||
}
|
||
if targetPackageCode != "" && code != targetPackageCode {
|
||
continue
|
||
}
|
||
filteredCodes = append(filteredCodes, code)
|
||
}
|
||
if targetPackageCode != "" && len(filteredCodes) == 0 {
|
||
return nil, gerror.New("目标套餐不在当前套餐可变更范围内")
|
||
}
|
||
|
||
packages, err := s.loadChangePackagesByCodes(ctx, user.TenantId, filteredCodes)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result := make([]*appCardChangeTargetContext, 0, len(filteredCodes))
|
||
for _, code := range filteredCodes {
|
||
pkg := packages[code]
|
||
if pkg == nil {
|
||
continue
|
||
}
|
||
durations, err := appChangeDurationOptions(pkg, batch.CurrentPackage)
|
||
if err != nil {
|
||
return nil, gerror.Newf("目标套餐%s:%v", pkg.PackageName, err)
|
||
}
|
||
price, err := s.resolveAgentRenewUnitPrice(ctx, user, batch.Items[0].Card, pkg)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if price.UnitPrice < 0 {
|
||
return nil, gerror.Newf("目标套餐%s价格配置不正确", pkg.PackageName)
|
||
}
|
||
result = append(result, &appCardChangeTargetContext{
|
||
Package: pkg,
|
||
Price: price,
|
||
Durations: durations,
|
||
})
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (s *sAppCard) loadChangePackagesByCodes(ctx context.Context, tenantId string, codes []string) (map[string]*entity.TSimFlowPackageInfo, error) {
|
||
result := make(map[string]*entity.TSimFlowPackageInfo, len(codes))
|
||
if len(codes) == 0 {
|
||
return result, nil
|
||
}
|
||
var rows []*entity.TSimFlowPackageInfo
|
||
err := dao.TSimFlowPackageInfo.Ctx(ctx).
|
||
Where(dao.TSimFlowPackageInfo.Columns().TenantId, tenantId).
|
||
WhereIn(dao.TSimFlowPackageInfo.Columns().PackageCode, codes).
|
||
Scan(&rows)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询可变更套餐失败")
|
||
}
|
||
for _, row := range rows {
|
||
if row != nil && strings.TrimSpace(row.PackageCode) != "" && row.Enabled != 0 {
|
||
result[strings.TrimSpace(row.PackageCode)] = row
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (batch *appCardChangeBatchContext) findTarget(packageCode string) *appCardChangeTargetContext {
|
||
packageCode = strings.TrimSpace(packageCode)
|
||
for _, target := range batch.TargetOptions {
|
||
if target != nil && target.Package != nil && strings.TrimSpace(target.Package.PackageCode) == packageCode {
|
||
return target
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) insertAppChangePrepareBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appCardChangeBatchContext, 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: appChangePrepareBatchInfo(batch, batchNo),
|
||
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 _, item := range batch.Items {
|
||
details = append(details, do.BatchDetail{
|
||
BatchNo: batchNo,
|
||
BatchType: consts.BatchTypePrepare,
|
||
DetailStatus: consts.BatchDetailStatusSuccess,
|
||
BlankOutStatus: consts.BlankOutStatusNo,
|
||
RowNo: item.RowNo,
|
||
ItemId: item.Card.Iccid,
|
||
Snapshot: appChangePrepareDetailSnapshot(batch, item),
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
})
|
||
}
|
||
if _, err = dao.BatchDetail.Ctx(ctx).TX(tx).Data(details).Insert(); err != nil {
|
||
return gerror.Wrap(err, "写入套餐变更预处理明细失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) lockAppChangePrepareBatch(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.BatchTypeChange {
|
||
return nil, gerror.New("批次不是套餐变更预处理批次")
|
||
}
|
||
return batch, nil
|
||
}
|
||
|
||
func (s *sAppCard) loadAppChangePrepareBatchCards(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) != "" {
|
||
cardValues = append(cardValues, strings.TrimSpace(detail.ItemId))
|
||
}
|
||
}
|
||
return cardValues, nil
|
||
}
|
||
|
||
func (s *sAppCard) prepareAppChangeExecution(ctx context.Context, tx gdb.TX, batch *appCardChangeBatchContext, target *appCardChangeTargetContext, durationMonths int, now *gtime.Time) (*appCardChangeExecutionContext, error) {
|
||
exec := &appCardChangeExecutionContext{
|
||
OldBindsByIccid: make(map[string]*entity.TSimCardBindPkg, len(batch.Items)),
|
||
NewBindsByIccid: make(map[string]*entity.TSimCardBindPkg, len(batch.Items)),
|
||
ServiceBeginAt: now,
|
||
ServiceEndAt: now.AddDate(0, durationMonths, 0),
|
||
CardPeriods: make([]map[string]any, 0, len(batch.Items)),
|
||
}
|
||
for _, item := range batch.Items {
|
||
lockedCard, err := s.lockRenewCardForSubmit(ctx, tx, item.Card, item.ExpectedCurrentPackageCode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
latestBind, err := s.lockLatestRenewBindForSubmit(ctx, tx, lockedCard, &appRenewPackageContext{
|
||
Card: item.Card,
|
||
Package: batch.CurrentPackage,
|
||
ExpectedCurrentPackageCode: item.ExpectedCurrentPackageCode,
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
exec.OldBindsByIccid[item.Card.Iccid] = latestBind
|
||
item.LatestBind = latestBind
|
||
exec.CardPeriods = append(exec.CardPeriods, map[string]any{
|
||
"rowNo": item.RowNo,
|
||
"cardId": item.Card.Id,
|
||
"iccid": item.Card.Iccid,
|
||
"msisdn": item.Card.Msisdn,
|
||
"oldSubscriptionNo": latestBind.SubscriptionNo,
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"newPackageCode": target.Package.PackageCode,
|
||
"oldExpectedExpireTime": appRenewFormatTime(latestBind.ExpectedExpireTime),
|
||
"newEffectiveTime": appRenewFormatTime(exec.ServiceBeginAt),
|
||
"newExpectedExpireTime": appRenewFormatTime(exec.ServiceEndAt),
|
||
})
|
||
}
|
||
return exec, nil
|
||
}
|
||
|
||
func (s *sAppCard) completeAppChangeSubscriptions(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appCardChangeBatchContext, target *appCardChangeTargetContext, exec *appCardChangeExecutionContext, batchNo string, orderSn string, durationMonths int, now *gtime.Time) error {
|
||
if err := s.archiveAppChangeOldBinds(ctx, tx, user, batch, exec, batchNo, now); err != nil {
|
||
return err
|
||
}
|
||
rows := make([]do.TSimCardBindPkg, 0, len(batch.Items))
|
||
for _, item := range batch.Items {
|
||
subscriptionNo := appOrderNo("SUB", user.UserId, now)
|
||
row := do.TSimCardBindPkg{
|
||
SubscriptionNo: subscriptionNo,
|
||
BatchNo: batchNo,
|
||
Iccid: item.Card.Iccid,
|
||
Msisdn: strings.TrimSpace(item.Card.Msisdn),
|
||
DeptId: fmt.Sprintf("%d", batch.Party.CustomerAgentId),
|
||
PackageType: target.Package.PackageType,
|
||
PackageCode: target.Package.PackageCode,
|
||
PreviousPackageCode: batch.CurrentPackage.PackageCode,
|
||
BusinessType: consts.PackageBusinessTypeChange,
|
||
EffectiveStatus: consts.EffectiveStatusEffective,
|
||
HandleTime: now,
|
||
EffectiveTime: exec.ServiceBeginAt,
|
||
ExpectedExpireTime: exec.ServiceEndAt,
|
||
DurationMonths: durationMonths,
|
||
GiftDurationMonths: 0,
|
||
OrderCode: orderSn,
|
||
Remark: batchNo,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}
|
||
rows = append(rows, row)
|
||
exec.NewBindsByIccid[item.Card.Iccid] = &entity.TSimCardBindPkg{
|
||
SubscriptionNo: subscriptionNo,
|
||
BatchNo: batchNo,
|
||
Iccid: item.Card.Iccid,
|
||
Msisdn: strings.TrimSpace(item.Card.Msisdn),
|
||
DeptId: fmt.Sprintf("%d", batch.Party.CustomerAgentId),
|
||
PackageType: target.Package.PackageType,
|
||
PackageCode: target.Package.PackageCode,
|
||
PreviousPackageCode: batch.CurrentPackage.PackageCode,
|
||
BusinessType: consts.PackageBusinessTypeChange,
|
||
EffectiveStatus: consts.EffectiveStatusEffective,
|
||
HandleTime: now,
|
||
EffectiveTime: exec.ServiceBeginAt,
|
||
ExpectedExpireTime: exec.ServiceEndAt,
|
||
DurationMonths: durationMonths,
|
||
OrderCode: orderSn,
|
||
Remark: batchNo,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: batch.Party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}
|
||
}
|
||
if _, err := dao.TSimCardBindPkg.Ctx(ctx).TX(tx).Data(rows).Insert(); err != nil {
|
||
return gerror.Wrap(err, "写入套餐变更新订购记录失败")
|
||
}
|
||
return s.updateCardsAfterAppChange(ctx, tx, user, batch, target, exec, now)
|
||
}
|
||
|
||
func (s *sAppCard) archiveAppChangeOldBinds(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appCardChangeBatchContext, exec *appCardChangeExecutionContext, batchNo string, now *gtime.Time) error {
|
||
logRows := make([]do.TSimBindPackageLogs, 0, len(batch.Items))
|
||
subscriptionNos := make([]string, 0, len(batch.Items))
|
||
for _, item := range batch.Items {
|
||
bind := exec.OldBindsByIccid[item.Card.Iccid]
|
||
if bind == nil || strings.TrimSpace(bind.SubscriptionNo) == "" {
|
||
return gerror.Newf("ICCID当前订购记录不完整:%s", item.Card.Iccid)
|
||
}
|
||
subscriptionNos = append(subscriptionNos, bind.SubscriptionNo)
|
||
logRows = append(logRows, do.TSimBindPackageLogs{
|
||
SubscriptionNo: bind.SubscriptionNo,
|
||
BatchNo: bind.BatchNo,
|
||
Iccid: bind.Iccid,
|
||
Msisdn: bind.Msisdn,
|
||
DeptId: bind.DeptId,
|
||
PackageType: bind.PackageType,
|
||
PackageCode: bind.PackageCode,
|
||
PreviousPackageCode: bind.PreviousPackageCode,
|
||
BusinessType: bind.BusinessType,
|
||
EffectiveStatus: consts.EffectiveStatusTerminated,
|
||
HandleTime: bind.HandleTime,
|
||
EffectiveTime: bind.EffectiveTime,
|
||
ExpectedExpireTime: bind.ExpectedExpireTime,
|
||
ActualExpireTime: now,
|
||
DurationMonths: bind.DurationMonths,
|
||
GiftDurationMonths: bind.GiftDurationMonths,
|
||
OrderCode: bind.OrderCode,
|
||
Remark: appAppendRemark(bind.Remark, fmt.Sprintf("changeBatchNo=%s", batchNo)),
|
||
TenantId: bind.TenantId,
|
||
CreatedDept: bind.CreatedDept,
|
||
CreatedBy: bind.CreatedBy,
|
||
CreatedAt: bind.CreatedAt,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
})
|
||
}
|
||
if len(logRows) > 0 {
|
||
if _, err := dao.TSimBindPackageLogs.Ctx(ctx).TX(tx).Data(logRows).OnConflict(dao.TSimBindPackageLogs.Columns().SubscriptionNo).Save(); err != nil {
|
||
return gerror.Wrap(err, "归档原套餐订购记录失败")
|
||
}
|
||
}
|
||
result, err := dao.TSimCardBindPkg.Ctx(ctx).TX(tx).
|
||
WhereIn(dao.TSimCardBindPkg.Columns().SubscriptionNo, subscriptionNos).
|
||
Delete()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "删除原套餐订购记录失败")
|
||
}
|
||
affected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "读取原套餐订购记录删除数量失败")
|
||
}
|
||
if int(affected) != len(subscriptionNos) {
|
||
return gerror.New("部分当前订购记录已变化,请重新校验")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) updateCardsAfterAppChange(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appCardChangeBatchContext, target *appCardChangeTargetContext, exec *appCardChangeExecutionContext, now *gtime.Time) error {
|
||
for _, item := range batch.Items {
|
||
result, err := dao.TSimCardInfo.Ctx(ctx).TX(tx).
|
||
Where(dao.TSimCardInfo.Columns().TenantId, item.Card.TenantId).
|
||
Where(dao.TSimCardInfo.Columns().Iccid, item.Card.Iccid).
|
||
Where(dao.TSimCardInfo.Columns().CurrentPackageCode, item.ExpectedCurrentPackageCode).
|
||
Data(do.TSimCardInfo{
|
||
CurrentPackageCode: target.Package.PackageCode,
|
||
PackageStatus: consts.PackageStatusEffective,
|
||
FlowPkgBeginAt: exec.ServiceBeginAt,
|
||
FlowPkgPresentAt: nil,
|
||
FlowPkgRealPresentAt: nil,
|
||
FlowPkgEndAt: exec.ServiceEndAt,
|
||
DataServiceStatus: 1,
|
||
PackageTotalFlowMb: target.Package.FlowMb,
|
||
UsedFlowMb: 0,
|
||
TrafficCutoffStatus: 0,
|
||
TrafficCutoffThreshold: 0,
|
||
SmsServiceStatus: 0,
|
||
PackageTotalSms: 0,
|
||
UsedSms: 0,
|
||
VoiceServiceStatus: 0,
|
||
PackageTotalVoice: 0,
|
||
UsedVoice: 0,
|
||
RoamServiceStatus: 0,
|
||
PackageTotalRoamFlowMb: 0,
|
||
UsedRoamFlowMb: 0,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrapf(err, "更新卡%s套餐失败", item.Card.Iccid)
|
||
}
|
||
affected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "读取套餐变更卡更新数量失败")
|
||
}
|
||
if affected != 1 {
|
||
return gerror.Newf("卡%s套餐已变化,请刷新后重试", item.Card.Iccid)
|
||
}
|
||
item.Card.CurrentPackageCode = target.Package.PackageCode
|
||
item.Card.PackageStatus = consts.PackageStatusEffective
|
||
item.Card.FlowPkgBeginAt = exec.ServiceBeginAt
|
||
item.Card.FlowPkgEndAt = exec.ServiceEndAt
|
||
item.Card.PackageTotalFlowMb = target.Package.FlowMb
|
||
item.Card.UsedFlowMb = 0
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) insertAppChangeOrder(ctx context.Context, tx gdb.TX, user *entity.SysUser, party *appRenewOrderParty, buyerNote string, orderSn string, totalAmount float64, now *gtime.Time) (int64, error) {
|
||
orderId, err := dao.TOrder.Ctx(ctx).TX(tx).Data(do.TOrder{
|
||
OrderSn: orderSn,
|
||
ParentOrderId: 0,
|
||
ParentOrderSn: "",
|
||
CustomerType: party.CustomerType,
|
||
CustomerAgentId: party.CustomerAgentId,
|
||
EndUserId: party.EndUserId,
|
||
AgentId: party.AgentId,
|
||
OrderType: consts.OrderTypeCardSubscribe,
|
||
OrderStatus: consts.OrderStatusCompleted,
|
||
ShippingStatus: 0,
|
||
ReturnStatus: 0,
|
||
Consignee: "",
|
||
Mobile: "",
|
||
Email: "",
|
||
Address: "",
|
||
AreaCode: "",
|
||
BuyerNote: strings.TrimSpace(buyerNote),
|
||
AdminNote: "",
|
||
Remark: "小程序套餐变更余额扣款订单",
|
||
ShippingMethod: consts.ShippingMethodNone,
|
||
LogisticsId: 0,
|
||
LogisticsName: "",
|
||
ShippingTypeId: 0,
|
||
ShippingTypeName: "",
|
||
TrackingNo: "",
|
||
PayType: consts.PayTypeBalance,
|
||
PayStatus: consts.PayStatusPaid,
|
||
PayOrderSn: "",
|
||
PayTime: now,
|
||
TotalAmount: totalAmount,
|
||
ProductAmount: totalAmount,
|
||
ShippingFee: 0,
|
||
ServiceFee: 0,
|
||
InvoiceFee: 0,
|
||
CouponAmount: 0,
|
||
DiscountAmount: 0,
|
||
Balance: totalAmount,
|
||
PaidAmount: totalAmount,
|
||
OnlinePaidAmount: 0,
|
||
OfflinePaidAmount: 0,
|
||
UnpaidAmount: 0,
|
||
UnrefundAmount: 0,
|
||
IsNeedCommission: 0,
|
||
DistributionStatus: 0,
|
||
TenantId: user.TenantId,
|
||
CreatedDept: party.CreatedDept,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).InsertAndGetId()
|
||
if err != nil {
|
||
return 0, gerror.Wrap(err, "创建套餐变更订单失败")
|
||
}
|
||
return orderId, nil
|
||
}
|
||
|
||
func (s *sAppCard) insertAppChangeOrderItem(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appCardChangeBatchContext, target *appCardChangeTargetContext, orderId int64, orderSn string, batchNo string, durationMonths int, unitAmount float64, totalAmount float64, beginAt, endAt *gtime.Time, cardPeriods []map[string]any, now *gtime.Time) error {
|
||
skuData := appRenewMarshalJSON([]map[string]any{
|
||
{"skuName": "原套餐", "skuValue": batch.CurrentPackage.PackageName, "packageCode": batch.CurrentPackage.PackageCode},
|
||
{"skuName": "目标套餐", "skuValue": target.Package.PackageName, "packageCode": target.Package.PackageCode},
|
||
{"skuName": "变更卡数", "skuValue": fmt.Sprintf("%d张", len(batch.Items)), "quantity": len(batch.Items)},
|
||
{"skuName": "套餐时长", "skuValue": appRenewDurationLabel(durationMonths), "durationMonths": durationMonths},
|
||
})
|
||
extraSkuData := appRenewMarshalJSON(map[string]any{
|
||
"business": "miniapp_card_change_batch",
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"newPackageCode": target.Package.PackageCode,
|
||
"durationMonths": durationMonths,
|
||
"unitPrice": target.Price.UnitPrice,
|
||
"unitAmount": unitAmount,
|
||
"totalAmount": totalAmount,
|
||
"quantity": len(batch.Items),
|
||
"priceSource": target.Price.PriceSource,
|
||
"agentDefaultPriceId": target.Price.AgentDefaultPriceId,
|
||
"targetPriceId": target.Price.TargetPriceId,
|
||
"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: unitAmount,
|
||
Quantity: len(batch.Items),
|
||
ProductId: target.Package.Id,
|
||
ProductName: target.Package.PackageName,
|
||
ProductSn: target.Package.PackageCode,
|
||
PicThumb: "",
|
||
SkuId: 0,
|
||
SkuData: skuData,
|
||
DeliveryQuantity: 0,
|
||
ProductType: consts.ProductTypeCard,
|
||
IsGift: 0,
|
||
AgentId: batch.Party.AgentId,
|
||
PrepayPrice: 0,
|
||
Commission: "",
|
||
OriginPrice: target.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 (s *sAppCard) markAppChangePrepareBatchConfirmed(ctx context.Context, tx gdb.TX, user *entity.SysUser, prepareBatch *entity.BatchMain, batch *appCardChangeBatchContext, target *appCardChangeTargetContext, exec *appCardChangeExecutionContext, orderSn string, durationMonths int, unitAmount float64, totalAmount float64, buyerNote string, remark string, now *gtime.Time) error {
|
||
batchNo := strings.TrimSpace(prepareBatch.BatchNo)
|
||
for _, item := range batch.Items {
|
||
_, err := dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||
Where(dao.BatchDetail.Columns().ItemId, item.Card.Iccid).
|
||
Data(do.BatchDetail{
|
||
BatchType: consts.BatchTypeChange,
|
||
Snapshot: appChangeSubmitDetailSnapshot(batch, target, exec, item, batchNo, orderSn, durationMonths, unitAmount),
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "更新套餐变更批次明细失败")
|
||
}
|
||
}
|
||
|
||
_, err := dao.BatchMain.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchMain.Columns().Id, prepareBatch.Id).
|
||
Data(do.BatchMain{
|
||
BatchType: consts.BatchTypeChange,
|
||
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: appChangeSubmitBatchInfo(batch, target, exec, batchNo, orderSn, durationMonths, unitAmount, totalAmount, buyerNote, remark, now),
|
||
EndTime: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "更新套餐变更批次主表失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) buildChangePrepareModel(ctx context.Context, user *entity.SysUser, batch *appCardChangeBatchContext) *model.AppCardChangePrepareModel {
|
||
targets := make([]*model.AppCardChangePackageOptionModel, 0, len(batch.TargetOptions))
|
||
quantity := len(batch.Items)
|
||
for _, target := range batch.TargetOptions {
|
||
options := make([]*model.AppRenewOptionModel, 0, len(target.Durations))
|
||
recommendIndex := appRenewRecommendIndex(target.Durations)
|
||
for index, month := range target.Durations {
|
||
totalAmount := appRoundAmount(target.Price.UnitPrice * float64(month) * float64(quantity))
|
||
options = append(options, &model.AppRenewOptionModel{
|
||
DurationMonths: month,
|
||
Label: appRenewDurationLabel(month),
|
||
UnitPrice: target.Price.UnitPrice,
|
||
TotalAmount: totalAmount,
|
||
PriceText: fmt.Sprintf("¥%.2f", totalAmount),
|
||
ExpireAfter: gtime.Now().AddDate(0, month, 0),
|
||
Recommended: index == recommendIndex,
|
||
})
|
||
}
|
||
targets = append(targets, &model.AppCardChangePackageOptionModel{
|
||
Package: s.renewPackageModel(batch.Items[0].Card, target.Package, batch.OwnerType),
|
||
Options: options,
|
||
UnitPrice: target.Price.UnitPrice,
|
||
PriceText: fmt.Sprintf("¥%.2f/月", target.Price.UnitPrice),
|
||
})
|
||
}
|
||
return &model.AppCardChangePrepareModel{
|
||
Quantity: quantity,
|
||
Cards: s.changeBatchCardModels(ctx, user, batch),
|
||
CurrentPackage: s.renewPackageModel(batch.Items[0].Card, batch.CurrentPackage, batch.OwnerType),
|
||
TargetPackages: targets,
|
||
EffectiveRule: appChangeEffectiveRule(),
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) changeBatchCardModels(ctx context.Context, user *entity.SysUser, batch *appCardChangeBatchContext) []*model.AppCardModel {
|
||
cards := make([]*model.AppCardModel, 0, len(batch.Items))
|
||
for _, item := range batch.Items {
|
||
cards = append(cards, s.cardRowToModel(ctx, item.Card, user, batch.OwnerType, true))
|
||
}
|
||
return cards
|
||
}
|
||
|
||
func appChangePrepareBatchInfo(batch *appCardChangeBatchContext, batchNo string) *gjson.Json {
|
||
return gjson.New(map[string]any{
|
||
"source": "app",
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"sourceBatchNo": strings.TrimSpace(batchNo),
|
||
"targetBatchType": consts.BatchTypeChange,
|
||
"targetBatchName": "套餐变更",
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"oldPackageName": batch.CurrentPackage.PackageName,
|
||
"oldPackageType": batch.CurrentPackage.PackageType,
|
||
"quantity": len(batch.Items),
|
||
"targetPackages": appChangeTargetSnapshotList(batch),
|
||
"cards": appChangeCardSnapshotList(batch),
|
||
})
|
||
}
|
||
|
||
func appChangeSubmitBatchInfo(batch *appCardChangeBatchContext, target *appCardChangeTargetContext, exec *appCardChangeExecutionContext, batchNo string, orderSn string, durationMonths int, unitAmount float64, totalAmount float64, buyerNote string, remark string, now *gtime.Time) *gjson.Json {
|
||
return gjson.New(map[string]any{
|
||
"source": "app",
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"sourceBatchNo": strings.TrimSpace(batchNo),
|
||
"orderSn": strings.TrimSpace(orderSn),
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"oldPackageName": batch.CurrentPackage.PackageName,
|
||
"newPackageCode": target.Package.PackageCode,
|
||
"newPackageName": target.Package.PackageName,
|
||
"durationMonths": durationMonths,
|
||
"giftDurationMonths": 0,
|
||
"effectiveMode": "immediate",
|
||
"offlineContractCompleted": true,
|
||
"changeUnitPrice": target.Price.UnitPrice,
|
||
"singleCardAmount": unitAmount,
|
||
"amount": fmt.Sprintf("%.2f", totalAmount),
|
||
"quantity": len(batch.Items),
|
||
"buyerNote": strings.TrimSpace(buyerNote),
|
||
"remark": strings.TrimSpace(remark),
|
||
"priceSource": target.Price.PriceSource,
|
||
"agentDefaultPriceId": target.Price.AgentDefaultPriceId,
|
||
"targetPriceId": target.Price.TargetPriceId,
|
||
"packageBeginTime": appRenewFormatTime(exec.ServiceBeginAt),
|
||
"packageEndTime": appRenewFormatTime(exec.ServiceEndAt),
|
||
"changeTime": now,
|
||
"cards": exec.CardPeriods,
|
||
})
|
||
}
|
||
|
||
func appChangePrepareDetailSnapshot(batch *appCardChangeBatchContext, item *appCardChangeItemContext) *gjson.Json {
|
||
return gjson.New(map[string]any{
|
||
"ICCID": item.Card.Iccid,
|
||
"MSISDN": item.Card.Msisdn,
|
||
"IMSI": item.Card.Imsi,
|
||
"rowNo": item.RowNo,
|
||
"cardId": item.Card.Id,
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"oldPackageName": batch.CurrentPackage.PackageName,
|
||
"oldSubscriptionNo": item.LatestBind.SubscriptionNo,
|
||
"oldExpectedExpire": appRenewFormatTime(item.LatestBind.ExpectedExpireTime),
|
||
"targetPackageCodes": appChangeTargetPackageCodes(batch),
|
||
})
|
||
}
|
||
|
||
func appChangeSubmitDetailSnapshot(batch *appCardChangeBatchContext, target *appCardChangeTargetContext, exec *appCardChangeExecutionContext, item *appCardChangeItemContext, batchNo string, orderSn string, durationMonths int, unitAmount float64) *gjson.Json {
|
||
oldBind := exec.OldBindsByIccid[item.Card.Iccid]
|
||
newBind := exec.NewBindsByIccid[item.Card.Iccid]
|
||
return gjson.New(map[string]any{
|
||
"ICCID": item.Card.Iccid,
|
||
"MSISDN": item.Card.Msisdn,
|
||
"IMSI": item.Card.Imsi,
|
||
"rowNo": item.RowNo,
|
||
"cardId": item.Card.Id,
|
||
"changeBatchNo": strings.TrimSpace(batchNo),
|
||
"orderSn": strings.TrimSpace(orderSn),
|
||
"customerAgentId": batch.Party.CustomerAgentId,
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"oldPackageName": batch.CurrentPackage.PackageName,
|
||
"newPackageCode": target.Package.PackageCode,
|
||
"newPackageName": target.Package.PackageName,
|
||
"oldSubscriptionNo": appBindSubscriptionNo(oldBind),
|
||
"newSubscriptionNo": appBindSubscriptionNo(newBind),
|
||
"durationMonths": durationMonths,
|
||
"changeUnitPrice": target.Price.UnitPrice,
|
||
"singleCardAmount": unitAmount,
|
||
"newEffectiveTime": appRenewFormatTime(exec.ServiceBeginAt),
|
||
"newExpectedExpireTime": appRenewFormatTime(exec.ServiceEndAt),
|
||
})
|
||
}
|
||
|
||
func appChangePrepareSnapshotFromBatch(batch *entity.BatchMain) (*appCardChangePrepareSnapshot, error) {
|
||
if batch == nil || batch.BatchInfo == nil {
|
||
return nil, gerror.New("套餐变更预处理批次缺少快照")
|
||
}
|
||
snapshot := &appCardChangePrepareSnapshot{
|
||
CustomerAgentId: batch.BatchInfo.Get("customerAgentId").Int64(),
|
||
OldPackageCode: strings.TrimSpace(batch.BatchInfo.Get("oldPackageCode").String()),
|
||
Quantity: batch.BatchInfo.Get("quantity").Int(),
|
||
Targets: make(map[string]*appCardChangeTargetSnapshot),
|
||
}
|
||
for _, raw := range batch.BatchInfo.Get("targetPackages").Interfaces() {
|
||
item := gjson.New(raw)
|
||
code := strings.TrimSpace(item.Get("packageCode").String())
|
||
if code == "" {
|
||
continue
|
||
}
|
||
durations := item.Get("durations").Ints()
|
||
sort.Ints(durations)
|
||
snapshot.Targets[code] = &appCardChangeTargetSnapshot{
|
||
PackageCode: code,
|
||
PackageName: strings.TrimSpace(item.Get("packageName").String()),
|
||
UnitPrice: appRoundAmount(item.Get("unitPrice").Float64()),
|
||
PriceSource: strings.TrimSpace(item.Get("priceSource").String()),
|
||
AgentDefaultPriceId: item.Get("agentDefaultPriceId").Int64(),
|
||
TargetPriceId: item.Get("targetPriceId").Int64(),
|
||
Durations: durations,
|
||
}
|
||
}
|
||
if snapshot.CustomerAgentId <= 0 || snapshot.OldPackageCode == "" || snapshot.Quantity <= 0 || len(snapshot.Targets) == 0 {
|
||
return nil, gerror.New("套餐变更预处理批次快照不完整")
|
||
}
|
||
return snapshot, nil
|
||
}
|
||
|
||
func appSameChangePrepareSnapshot(snapshot *appCardChangePrepareSnapshot, batch *appCardChangeBatchContext) bool {
|
||
if snapshot == nil || batch == nil || batch.CurrentPackage == nil || batch.Party == nil {
|
||
return false
|
||
}
|
||
if snapshot.CustomerAgentId != batch.Party.CustomerAgentId ||
|
||
snapshot.OldPackageCode != strings.TrimSpace(batch.CurrentPackage.PackageCode) ||
|
||
snapshot.Quantity != len(batch.Items) {
|
||
return false
|
||
}
|
||
for _, target := range batch.TargetOptions {
|
||
if target == nil || target.Package == nil || target.Price == nil {
|
||
return false
|
||
}
|
||
saved := snapshot.Targets[strings.TrimSpace(target.Package.PackageCode)]
|
||
if saved == nil || !appSameChangeTargetSnapshot(saved, target) {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func appSameChangeTargetSnapshot(snapshot *appCardChangeTargetSnapshot, target *appCardChangeTargetContext) bool {
|
||
durations := append([]int(nil), target.Durations...)
|
||
sort.Ints(durations)
|
||
return snapshot.PackageCode == strings.TrimSpace(target.Package.PackageCode) &&
|
||
appRoundAmount(snapshot.UnitPrice) == appRoundAmount(target.Price.UnitPrice) &&
|
||
snapshot.PriceSource == strings.TrimSpace(target.Price.PriceSource) &&
|
||
snapshot.AgentDefaultPriceId == target.Price.AgentDefaultPriceId &&
|
||
snapshot.TargetPriceId == target.Price.TargetPriceId &&
|
||
appSameIntSlice(snapshot.Durations, durations)
|
||
}
|
||
|
||
func appChangeTargetSnapshotList(batch *appCardChangeBatchContext) []map[string]any {
|
||
result := make([]map[string]any, 0, len(batch.TargetOptions))
|
||
for _, target := range batch.TargetOptions {
|
||
result = append(result, map[string]any{
|
||
"packageCode": target.Package.PackageCode,
|
||
"packageName": target.Package.PackageName,
|
||
"packageType": target.Package.PackageType,
|
||
"flowMb": target.Package.FlowMb,
|
||
"unitPrice": target.Price.UnitPrice,
|
||
"priceSource": target.Price.PriceSource,
|
||
"agentDefaultPriceId": target.Price.AgentDefaultPriceId,
|
||
"targetPriceId": target.Price.TargetPriceId,
|
||
"durations": target.Durations,
|
||
})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appChangeCardSnapshotList(batch *appCardChangeBatchContext) []map[string]any {
|
||
result := make([]map[string]any, 0, len(batch.Items))
|
||
for _, item := range batch.Items {
|
||
result = append(result, map[string]any{
|
||
"cardId": item.Card.Id,
|
||
"iccid": item.Card.Iccid,
|
||
"msisdn": item.Card.Msisdn,
|
||
"oldPackageCode": batch.CurrentPackage.PackageCode,
|
||
"oldSubscriptionNo": item.LatestBind.SubscriptionNo,
|
||
})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appChangeTargetPackageCodes(batch *appCardChangeBatchContext) []string {
|
||
result := make([]string, 0, len(batch.TargetOptions))
|
||
for _, target := range batch.TargetOptions {
|
||
if target != nil && target.Package != nil {
|
||
result = append(result, strings.TrimSpace(target.Package.PackageCode))
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appChangePackageCodes(value *gjson.Json) []string {
|
||
if value == nil || value.IsNil() {
|
||
return nil
|
||
}
|
||
return appNormalizePackageCodes(appChangePackageCodesFromValue(value.Interface()))
|
||
}
|
||
|
||
func appChangePackageCodesFromValue(value any) []string {
|
||
switch typed := value.(type) {
|
||
case nil:
|
||
return nil
|
||
case []any:
|
||
result := make([]string, 0, len(typed))
|
||
for _, item := range typed {
|
||
result = append(result, appChangePackageCodesFromValue(item)...)
|
||
}
|
||
return result
|
||
case map[string]any:
|
||
keys := []string{"packageCode", "code", "value", "package_code"}
|
||
for _, key := range keys {
|
||
if value, ok := typed[key]; ok {
|
||
return appChangePackageCodesFromValue(value)
|
||
}
|
||
}
|
||
listKeys := []string{"list", "items", "options", "values", "data", "changeablePackageCodes"}
|
||
result := make([]string, 0)
|
||
for _, key := range listKeys {
|
||
if value, ok := typed[key]; ok {
|
||
result = append(result, appChangePackageCodesFromValue(value)...)
|
||
}
|
||
}
|
||
return result
|
||
case string:
|
||
return appChangePackageCodesFromString(typed)
|
||
default:
|
||
if result := appChangePackageCodesFromReflectValue(value); len(result) > 0 {
|
||
return result
|
||
}
|
||
if code := strings.TrimSpace(gconv.String(value)); code != "" {
|
||
return []string{code}
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func appChangePackageCodesFromString(value string) []string {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return nil
|
||
}
|
||
if strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") {
|
||
var decoded any
|
||
if err := json.Unmarshal([]byte(value), &decoded); err == nil {
|
||
return appChangePackageCodesFromValue(decoded)
|
||
}
|
||
}
|
||
parts := strings.FieldsFunc(value, func(r rune) bool {
|
||
switch r {
|
||
case ',', ',', ';', ';', '|', '/', '、', '\n', '\r', '\t', ' ':
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
})
|
||
if len(parts) == 0 {
|
||
parts = []string{value}
|
||
}
|
||
return parts
|
||
}
|
||
|
||
func appChangePackageCodesFromReflectValue(value any) []string {
|
||
rv := reflect.ValueOf(value)
|
||
if !rv.IsValid() {
|
||
return nil
|
||
}
|
||
switch rv.Kind() {
|
||
case reflect.Slice, reflect.Array:
|
||
result := make([]string, 0, rv.Len())
|
||
for i := 0; i < rv.Len(); i++ {
|
||
result = append(result, appChangePackageCodesFromValue(rv.Index(i).Interface())...)
|
||
}
|
||
return result
|
||
case reflect.Map:
|
||
values := make(map[string]any, rv.Len())
|
||
iter := rv.MapRange()
|
||
for iter.Next() {
|
||
values[gconv.String(iter.Key().Interface())] = iter.Value().Interface()
|
||
}
|
||
return appChangePackageCodesFromValue(values)
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func appNormalizePackageCodes(values []string) []string {
|
||
seen := make(map[string]struct{}, len(values))
|
||
result := make([]string, 0, len(values))
|
||
for _, raw := range values {
|
||
value := strings.TrimSpace(raw)
|
||
if value == "" {
|
||
continue
|
||
}
|
||
if _, exists := seen[value]; exists {
|
||
continue
|
||
}
|
||
seen[value] = struct{}{}
|
||
result = append(result, value)
|
||
}
|
||
return result
|
||
}
|
||
|
||
func appChangeDurationOptions(target *entity.TSimFlowPackageInfo, current *entity.TSimFlowPackageInfo) ([]int, error) {
|
||
durations, err := appRenewableDurationOptions(target)
|
||
if err == nil {
|
||
return durations, nil
|
||
}
|
||
if current != nil {
|
||
if fallback, fallbackErr := appRenewableDurationOptions(current); fallbackErr == nil {
|
||
return fallback, nil
|
||
}
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
func appChangeRequestedPackageCode(values ...string) string {
|
||
for _, value := range values {
|
||
if strings.TrimSpace(value) != "" {
|
||
return strings.TrimSpace(value)
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func appBindSubscriptionNo(bind *entity.TSimCardBindPkg) string {
|
||
if bind == nil {
|
||
return ""
|
||
}
|
||
return strings.TrimSpace(bind.SubscriptionNo)
|
||
}
|
||
|
||
func appAppendRemark(old string, addition string) string {
|
||
old = strings.TrimSpace(old)
|
||
addition = strings.TrimSpace(addition)
|
||
if old == "" {
|
||
return addition
|
||
}
|
||
if addition == "" {
|
||
return old
|
||
}
|
||
return old + "; " + addition
|
||
}
|
||
|
||
func appChangeEffectiveRule() string {
|
||
return "立即变更,原套餐终止后新套餐立即生效"
|
||
}
|