Files
iotlcm/admin-svr/server/internal/logic/iot/t_sim_card_renew.go
T

1597 lines
54 KiB
Go

package iot
import (
"context"
"database/sql"
"errors"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchmetadata"
"math"
"sort"
"strings"
"time"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rabbitmq"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/packageduration"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/renew"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sIotTSimCardRenew struct{}
const (
renewPriceChangedMessage = "续费套餐定价已更新,确认页已刷新,请核对最新单价及应付金额后重新提交"
renewPrepareChangedMessage = "续费条件或套餐定价已更新,预处理结果已刷新,请核对最新有效卡数量、失败卡及应付金额后重新提交"
)
func NewIotTSimCardRenew() *sIotTSimCardRenew {
return &sIotTSimCardRenew{}
}
func init() {
service.RegisterIotTSimCardRenew(NewIotTSimCardRenew())
}
func (s *sIotTSimCardRenew) PrepareBatch(ctx context.Context, in *model.TSimCardRenewBatchPrepareParam) (*model.TSimCardRenewBatchPrepareRes, error) {
if in == nil {
in = &model.TSimCardRenewBatchPrepareParam{}
}
sourceBatchNo := strings.TrimSpace(in.SourceBatchNo)
if sourceBatchNo == "" {
return nil, gerror.New("来源批次号不能为空")
}
submitParam := &model.TSimCardRenewBatchSubmitParam{
SourceBatchNo: sourceBatchNo,
}
rows, err := s.buildRenewRows(ctx, submitParam)
if err != nil {
return nil, err
}
return s.prepareRows(ctx, submitParam, rows, consts.BatchSourceTypeBatchSelect, sourceBatchNo, sourceBatchNo, in)
}
func (s *sIotTSimCardRenew) PrepareBatchAll(ctx context.Context, in *model.TSimCardRenewBatchPrepareAllParam) (*model.TSimCardRenewBatchPrepareRes, error) {
if in == nil {
in = &model.TSimCardRenewBatchPrepareAllParam{}
}
sourceBatchNo := strings.TrimSpace(in.SourceBatchNo)
if sourceBatchNo == "" {
return nil, gerror.New("来源批次号不能为空")
}
submitParam := &model.TSimCardRenewBatchSubmitParam{
SourceBatchNo: sourceBatchNo,
}
rows, err := s.buildRenewRows(ctx, submitParam)
if err != nil {
return nil, err
}
return s.prepareRows(ctx, submitParam, rows, consts.BatchSourceTypeBatchSelect, sourceBatchNo, sourceBatchNo, in)
}
func (s *sIotTSimCardRenew) PrepareInput(ctx context.Context, in *model.TSimCardRenewInputPrepareParam) (*model.TSimCardRenewBatchPrepareRes, error) {
if in == nil {
in = &model.TSimCardRenewInputPrepareParam{}
}
rows, err := renew.ParseInputRows(ctx, in.Input)
if err != nil {
return nil, err
}
renewRows, err := batchRowsToRenewRows(rows)
if err != nil {
return nil, err
}
return s.prepareRows(ctx, &model.TSimCardRenewBatchSubmitParam{}, renewRows, consts.BatchSourceTypeBatchInput, "", "input-prepare", nil)
}
// prepareRows 异步构造卡续费预处理批次:batch_type=4、batch_async_status=Pending、targetBatchType=101。
func (s *sIotTSimCardRenew) prepareRows(ctx context.Context, submitParam *model.TSimCardRenewBatchSubmitParam, rows []*renew.Row, sourceType int, sourceBatchNo string, sourceName string, bizParam any) (*model.TSimCardRenewBatchPrepareRes, error) {
if submitParam == nil {
submitParam = &model.TSimCardRenewBatchSubmitParam{}
}
submitParam.SourceBatchNo = strings.TrimSpace(submitParam.SourceBatchNo)
processor := renew.NewPrepareProcessor()
importRows := renewRowsToBatchRows(rows)
sourceBatchNo = strings.TrimSpace(sourceBatchNo)
sourceName = strings.TrimSpace(sourceName)
bizForBuild := bizParam
if bizForBuild == nil {
bizForBuild = submitParam
}
req := batchimport.SubmitRequest{
SourceChannel: batchmetadata.Console,
ExecutionMode: batchmetadata.Bulk,
BizParams: bizForBuild,
SourceType: sourceType,
SourceBatchNo: sourceBatchNo,
Operator: currentOperator(ctx),
}
result, err := batchimport.CreatePendingRows(ctx, req, processor, importRows, batchimport.PendingOptions{
PrepareRows: func(ctx context.Context, rows []batchimport.Row) error {
// 行级校验复用 processor.ValidateRows;批量数据为 nil 时跳过。
if len(rows) == 0 {
return nil
}
return processor.ValidateRows(ctx, rows)
},
})
if err != nil {
return nil, err
}
res := &model.TSimCardRenewBatchPrepareRes{
BatchNo: result.BatchNo,
BatchType: result.BatchType,
TargetBatchType: consts.BatchTypeRenew,
Status: pendingStatus(result.ImportStatus),
BatchAsyncStatus: consts.BatchAsyncStatusPending,
RunResult: consts.BatchRunResultPending,
Total: result.TotalRows,
ValidCount: result.ValidRows,
FailCount: result.ErrorRows,
ResultDownloadId: result.ResultDownloadId,
ResultFileOssId: result.ResultFileOssId,
}
if snapshot, ok := result.ExecutionResult.(*model.TSimCardRenewPriceSnapshot); ok && snapshot != nil {
fillPreparePrice(res, snapshot)
res.Quantity = result.ValidRows
}
publishPendingExecute(ctx, res.BatchNo, res.BatchType, contexts.GetTenantId(ctx))
return res, nil
}
func (s *sIotTSimCardRenew) ConfirmBatch(ctx context.Context, in *model.TSimCardRenewBatchConfirmParam) (*model.TSimCardRenewBatchConfirmRes, error) {
if in == nil {
in = &model.TSimCardRenewBatchConfirmParam{}
}
in.BatchNo = strings.TrimSpace(in.BatchNo)
if in.BatchNo == "" {
return nil, gerror.New("批次号不能为空")
}
durationValue := in.DurationYears
if durationValue <= 0 {
durationValue = normalizeLegacyRenewYears(in.DurationMonths)
}
if durationValue <= 0 {
return nil, gerror.New("续费时长必须大于0")
}
in.DurationYears = durationValue
in.DurationMonths = packageduration.RenewMonths(durationValue)
if in.GiftDurationMonths < 0 {
return nil, gerror.New("赠送时长不能小于0")
}
if err := s.discardRenewPeriodOverrideForNonTop(ctx, in); err != nil {
return nil, err
}
renewDate, err := parseRenewDate(in.RenewDate)
if err != nil {
return nil, err
}
operator := currentOperator(ctx)
var snapshot *model.TSimCardRenewPriceSnapshot
var paymentSnapshot *model.TSimCardRenewPaymentSnapshot
var sourceBatchNo string
var packageBeginTime string
var packageEndTime string
var converted *renew.AsyncConfirmResult
var prepared *renew.PrepareContext
var revalidatedRows []*renew.Row
var prepareChanged bool
var priceChanged bool
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
batch, err := s.lockPrepareBatchForConfirm(ctx, tx, in.BatchNo)
if err != nil {
return err
}
baseSnapshot, err := snapshotFromBatch(batch)
if err != nil {
return err
}
prepared, revalidatedRows, prepareChanged, err = s.revalidateRenewBatchForConfirm(ctx, tx, batch, baseSnapshot, operator)
if err != nil {
return err
}
if prepared == nil || prepared.Quantity <= 0 {
prepareChanged = true
return s.refreshRenewPrepareState(ctx, tx, batch, revalidatedRows, baseSnapshot)
}
snapshot, priceChanged, err = s.resolveRenewConfirmSnapshot(ctx, tx, prepared, baseSnapshot, in.PackageCode)
if err != nil {
return err
}
if prepareChanged || priceChanged {
return s.refreshRenewPrepareState(ctx, tx, batch, revalidatedRows, snapshot)
}
if err = validateRenewConfirmContract(snapshot, durationValue); err != nil {
return err
}
packageBeginTime, packageEndTime, err = s.normalizeRenewConfirmPeriod(ctx, snapshot, in)
if err != nil {
return err
}
quantity := batch.SuccessCount
if prepared != nil && prepared.Quantity > 0 {
quantity = prepared.Quantity
}
if quantity <= 0 {
quantity = batch.ValidCount
}
if quantity <= 0 {
quantity = batch.TotalCount
}
if quantity <= 0 {
return gerror.New("续费预处理批次没有可续费的卡")
}
if err = s.ensureNoUnfinishedRenewBatchForConfirm(ctx, tx, batch.BatchNo); err != nil {
return err
}
paymentSnapshot, err = s.buildRenewPaymentSnapshot(ctx, tx, batch.BatchNo, snapshot, quantity, in)
if err != nil {
return err
}
prepareSourceBatchNo := renewSourceBatchNoFromBatch(batch)
sourceBatchNo = prepareSourceBatchNo
converted, err = renew.ConvertPrepareToAsync(ctx, tx, &renew.AsyncConfirmInput{
BatchNo: in.BatchNo,
TargetBatchType: consts.BatchTypeRenew,
DurationYears: durationValue,
DurationMonths: packageduration.RenewMonths(durationValue),
GiftDurationMonths: in.GiftDurationMonths,
RenewDate: renewDateString(renewDate),
PackageBeginTime: packageBeginTime,
PackageEndTime: packageEndTime,
OfflineContract: in.OfflineContractCompleted,
BuyerNote: in.BuyerNote,
AdminNote: in.AdminNote,
Remark: in.Remark,
PriceSnapshot: snapshot,
PaymentSnapshot: paymentSnapshot,
SourceBatchNo: prepareSourceBatchNo,
Operator: operator,
})
return err
})
if err != nil {
return nil, err
}
if prepareChanged || priceChanged {
message := renewPriceChangedMessage
if prepareChanged {
message = renewPrepareChangedMessage
}
return &model.TSimCardRenewBatchConfirmRes{
BatchNo: in.BatchNo,
PackageCode: renewSnapshotPackageCode(snapshot),
PackageName: renewSnapshotPackageName(snapshot),
Status: "prepare_refreshed",
PrepareChanged: prepareChanged,
PriceChanged: priceChanged,
Message: message,
}, nil
}
if converted == nil || snapshot == nil {
return nil, gerror.New("续费确认结果为空")
}
if paymentSnapshot == nil {
return nil, gerror.New("续费付款结果为空")
}
publishPendingExecute(ctx, converted.BatchNo, converted.BatchType, contexts.GetTenantId(ctx))
return &model.TSimCardRenewBatchConfirmRes{
BatchNo: converted.BatchNo,
BatchType: converted.BatchType,
BatchAsyncStatus: converted.BatchAsyncStatus,
SourceBatchNo: sourceBatchNo,
PackageCode: snapshot.PackageCode,
PackageName: snapshot.PackageName,
DurationYears: durationValue,
DurationMonths: packageduration.RenewMonths(durationValue),
GiftDurationMonths: in.GiftDurationMonths,
Status: "pending",
PaymentMode: paymentSnapshot.PaymentMode,
PayType: paymentSnapshot.PayType,
PayAmount: paymentSnapshot.PayAmount,
AvailableBalance: paymentSnapshot.BalanceAfter,
BalanceDetailSerialNo: paymentSnapshot.BalanceDetailSerialNo,
PackageBeginTime: packageBeginTime,
PackageEndTime: packageEndTime,
}, nil
}
func validateRenewConfirmContract(snapshot *model.TSimCardRenewPriceSnapshot, durationMonths int) error {
if snapshot == nil {
return gerror.New("续费预处理批次缺少报价信息")
}
if !containsInt(snapshot.RenewableDurationOptions, durationMonths) {
return gerror.New("续费时长不在当前套餐可续费范围内")
}
return nil
}
func (s *sIotTSimCardRenew) discardRenewPeriodOverrideForNonTop(ctx context.Context, in *model.TSimCardRenewBatchConfirmParam) error {
if in == nil {
return nil
}
if strings.TrimSpace(in.RenewDate) == "" &&
strings.TrimSpace(in.PackageBeginTime) == "" &&
strings.TrimSpace(in.PackageEndTime) == "" {
return nil
}
isTop, _, err := service.AgentDept().IsTopDept(ctx)
if err != nil {
return gerror.Wrap(err, "判断当前组织类型失败")
}
if isTop {
return nil
}
in.RenewDate = ""
in.PackageBeginTime = ""
in.PackageEndTime = ""
return nil
}
func renewDurationUnit(snapshot *model.TSimCardRenewPriceSnapshot) string {
return consts.RenewDurationUnitYear
}
func addRenewDurationByUnit(beginAt time.Time, _ string, value int) time.Time {
if value <= 0 {
return beginAt
}
return addRenewCalendarMonths(beginAt, packageduration.RenewMonths(value))
}
func addRenewCalendarMonths(beginAt time.Time, months int) time.Time {
if months <= 0 {
return beginAt
}
targetMonth := time.Date(
beginAt.Year(),
beginAt.Month()+time.Month(months),
1,
beginAt.Hour(),
beginAt.Minute(),
beginAt.Second(),
beginAt.Nanosecond(),
beginAt.Location(),
)
day := beginAt.Day()
lastDay := time.Date(
targetMonth.Year(),
targetMonth.Month()+1,
0,
beginAt.Hour(),
beginAt.Minute(),
beginAt.Second(),
beginAt.Nanosecond(),
beginAt.Location(),
).Day()
if day > lastDay {
day = lastDay
}
return renewDayEndTime(time.Date(
targetMonth.Year(),
targetMonth.Month(),
day,
beginAt.Hour(),
beginAt.Minute(),
beginAt.Second(),
beginAt.Nanosecond(),
beginAt.Location(),
))
}
func renewDayEndTime(t time.Time) time.Time {
return time.Date(
t.Year(),
t.Month(),
t.Day(),
23,
59,
59,
0,
t.Location(),
)
}
func (s *sIotTSimCardRenew) normalizeRenewConfirmPeriod(ctx context.Context, snapshot *model.TSimCardRenewPriceSnapshot, in *model.TSimCardRenewBatchConfirmParam) (string, string, error) {
beginValue := strings.TrimSpace(in.PackageBeginTime)
endValue := strings.TrimSpace(in.PackageEndTime)
if beginValue == "" && endValue == "" {
return "", "", nil
}
isTop, _, err := service.AgentDept().IsTopDept(ctx)
if err != nil {
return "", "", gerror.Wrap(err, "判断当前组织类型失败")
}
if !isTop {
return "", "", gerror.New("当前组织不能修改续费日期")
}
if beginValue == "" && snapshot != nil {
beginValue = strings.TrimSpace(snapshot.RenewBeginDate)
}
if beginValue == "" {
return "", "", gerror.New("续费开始日期不能为空")
}
beginAt, err := parseRenewPeriodDate(beginValue, "续费开始日期")
if err != nil {
return "", "", err
}
durationValue := in.DurationYears
if durationValue <= 0 {
durationValue = normalizeLegacyRenewYears(in.DurationMonths)
}
giftValue := in.GiftDurationMonths
expectedEndAt := addRenewCalendarMonths(beginAt, packageduration.RenewMonths(durationValue)+giftValue)
if endValue == "" {
endValue = expectedEndAt.Format("2006-01-02")
} else {
endAt, err := parseRenewPeriodDate(endValue, "续费结束日期")
if err != nil {
return "", "", err
}
if !sameDate(endAt, expectedEndAt) {
return "", "", gerror.New("续费结束日期与续费时长不一致")
}
}
return beginAt.Format("2006-01-02"), endValue, nil
}
// PrepareResult 查询预处理批次的当前异步状态。
func (s *sIotTSimCardRenew) PrepareResult(ctx context.Context, batchNo string) (*model.TSimCardRenewPrepareResultRes, error) {
res, err := loadPrepareResult(ctx, batchNo, consts.BatchTypeRenew, renewPriceSnapshotFromBatch, buildRenewResultFields)
if err != nil {
return nil, err
}
if err = s.fillRenewPrepareFailedItems(ctx, res); err != nil {
return nil, err
}
s.fillRenewPrepareResultPaymentView(ctx, res)
return res, nil
}
func (s *sIotTSimCardRenew) fillRenewPrepareFailedItems(ctx context.Context, res *model.TSimCardRenewPrepareResultRes) error {
if res == nil || strings.TrimSpace(res.BatchNo) == "" || res.FailCount <= 0 {
return nil
}
const displayLimit = 20
details := make([]*entity.BatchDetail, 0, displayLimit)
columns := dao.BatchDetail.Columns()
if err := dao.BatchDetail.Ctx(ctx).
Fields(columns.ItemId, columns.ErrorReason).
Where(columns.BatchNo, res.BatchNo).
Where(columns.BatchType, consts.BatchTypePrepare).
Where(columns.DetailStatus, consts.BatchDetailStatusFailed).
OrderAsc(columns.RowNo).
Limit(displayLimit).
Scan(&details); err != nil {
return gerror.Wrap(err, "查询续费预处理失败卡明细失败")
}
res.FailedItems = make([]model.TSimCardRenewPrepareFailedItem, 0, len(details))
for _, detail := range details {
if detail == nil {
continue
}
res.FailedItems = append(res.FailedItems, model.TSimCardRenewPrepareFailedItem{
Iccid: strings.TrimSpace(detail.ItemId),
Reason: strings.TrimSpace(detail.ErrorReason),
})
}
res.FailedItemsTruncated = res.FailCount > len(res.FailedItems)
return nil
}
func (s *sIotTSimCardRenew) buildRenewRows(ctx context.Context, in *model.TSimCardRenewBatchSubmitParam) ([]*renew.Row, error) {
if in == nil {
return nil, gerror.New("续费参数不能为空")
}
in.SourceBatchNo = strings.TrimSpace(in.SourceBatchNo)
repo := renew.NewRepository()
sourceBatch, err := repo.GetSourceBatch(ctx, in.SourceBatchNo)
if err != nil {
return nil, gerror.Wrap(err, "查询来源批次失败")
}
if sourceBatch == nil {
return nil, gerror.New("来源批次不存在")
}
if tenantId := contexts.GetTenantId(ctx); tenantId != "" && sourceBatch.TenantId != tenantId {
return nil, gerror.New("来源批次不存在")
}
if sourceBatch.BatchType != consts.BatchTypeOutbound {
return nil, gerror.New("来源批次不是出库批次")
}
if sourceBatch.RunResult != consts.BatchRunResultSuccess {
return nil, gerror.New("来源批次不是成功批次")
}
rows, err := repo.GetSourceBatchRows(ctx, in.SourceBatchNo)
if err != nil {
return nil, gerror.Wrap(err, "查询来源批次卡明细失败")
}
if len(rows) == 0 {
return nil, gerror.New("来源批次没有可续费卡")
}
return rows, nil
}
func (s *sIotTSimCardRenew) lockPrepareBatchForConfirm(ctx context.Context, tx gdb.TX, batchNo string) (*entity.BatchMain, error) {
columns := dao.BatchMain.Columns()
batch := &entity.BatchMain{}
err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
Where(columns.BatchNo, batchNo).
Where(columns.BatchType, consts.BatchTypePrepare).
Where(columns.BatchAsyncStatus, consts.BatchAsyncStatusSuccess).
Where(columns.RunResult, consts.BatchRunResultSuccess).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
Where(columns.CreatedDept, contexts.GetDeptId(ctx)).
LockUpdate().
Scan(batch)
if err != nil {
return nil, gerror.Wrap(err, "查询续费预处理批次失败")
}
if batch.Id == 0 {
return nil, gerror.New("续费预处理批次不存在或尚未完成")
}
if batch.BatchInfo == nil {
return nil, gerror.New("续费预处理批次缺少报价信息")
}
if batch.BatchInfo.Get("targetBatchType").Int() != consts.BatchTypeRenew {
return nil, gerror.New("批次不是卡续费预处理批次")
}
return batch, nil
}
func (s *sIotTSimCardRenew) ensureNoUnfinishedRenewBatchForConfirm(ctx context.Context, tx gdb.TX, batchNo string) error {
repo := renew.NewRepository()
iccids, err := repo.BatchDetailItemIDs(ctx, tx, batchNo, consts.BatchTypePrepare, []int{consts.BatchDetailStatusSuccess})
if err != nil {
return gerror.Wrap(err, "查询续费预处理明细失败")
}
if len(iccids) == 0 {
return gerror.New("续费预处理批次没有可续费的卡")
}
conflictingBatches, err := repo.UnfinishedRenewBatchNosByIccids(ctx, tx, contexts.GetTenantId(ctx), iccids, "")
if err != nil {
return gerror.Wrap(err, "校验未完成续费批次失败")
}
if len(conflictingBatches) > 0 {
return gerror.New("存在未完成续费批次,不能重复续费")
}
return nil
}
func (s *sIotTSimCardRenew) revalidateRenewBatchForConfirm(ctx context.Context, tx gdb.TX, batch *entity.BatchMain, snapshot *model.TSimCardRenewPriceSnapshot, operator batchimport.OperatorContext) (*renew.PrepareContext, []*renew.Row, bool, error) {
if batch == nil || strings.TrimSpace(batch.BatchNo) == "" {
return nil, nil, false, gerror.New("renew prepare batch is empty")
}
repo := renew.NewRepository()
rows, err := repo.GetRenewBatchRowsTx(ctx, tx, batch.BatchNo, consts.BatchTypePrepare)
if err != nil {
return nil, nil, false, gerror.Wrap(err, "query renew prepare details failed")
}
if len(rows) == 0 {
return &renew.PrepareContext{}, rows, true, nil
}
submitParam := &model.TSimCardRenewBatchSubmitParam{
SourceBatchNo: renewSourceBatchNoFromBatch(batch),
}
prepared, prepareChanged, err := renew.NewProcessor().RevalidatePrepareContext(ctx, tx, submitParam, operator, rows, batch.BatchNo)
if err != nil {
return nil, rows, false, err
}
if prepared == nil {
return nil, rows, false, gerror.New("renew prepare validation result is empty")
}
if snapshot != nil {
if snapshot.CustomerAgentId > 0 && prepared.CustomerAgentId > 0 && snapshot.CustomerAgentId != prepared.CustomerAgentId {
prepareChanged = true
}
fillRenewBeginSnapshot(snapshot, prepared)
}
return prepared, rows, prepareChanged, nil
}
func (s *sIotTSimCardRenew) resolveRenewConfirmSnapshot(ctx context.Context, tx gdb.TX, prepared *renew.PrepareContext, baseSnapshot *model.TSimCardRenewPriceSnapshot, selectedPackageCode string) (*model.TSimCardRenewPriceSnapshot, bool, error) {
if prepared == nil {
return nil, false, gerror.New("renew prepare validation result is empty")
}
currentPackageCode := strings.TrimSpace(prepared.PackageCode)
if currentPackageCode == "" {
return nil, false, gerror.New("当前套餐不能为空")
}
selectedPackageCode = strings.TrimSpace(selectedPackageCode)
if selectedPackageCode == "" && baseSnapshot != nil {
selectedPackageCode = strings.TrimSpace(baseSnapshot.PackageCode)
}
if selectedPackageCode == "" {
selectedPackageCode = currentPackageCode
}
renewableCodes, renewablePackages, err := s.loadRenewablePackageOptions(ctx, tx, prepared.CustomerAgentId, currentPackageCode)
if err != nil {
return nil, false, err
}
packageSelectionChanged := false
if !containsString(renewableCodes, selectedPackageCode) {
selectedPackageCode = currentPackageCode
if !containsString(renewableCodes, selectedPackageCode) && len(renewableCodes) > 0 {
selectedPackageCode = renewableCodes[0]
}
if selectedPackageCode == "" || !containsString(renewableCodes, selectedPackageCode) {
return nil, false, gerror.New("当前套餐已无可续费套餐,请重新发起预处理")
}
packageSelectionChanged = true
}
snapshot, err := s.buildRenewPriceSnapshot(ctx, tx, prepared.CustomerAgentId, selectedPackageCode)
if err != nil {
return nil, false, err
}
snapshot.RenewablePackageCodes = renewableCodes
snapshot.RenewablePackages = renewablePackages
fillRenewBeginSnapshot(snapshot, prepared)
priceChanged := packageSelectionChanged
if baseSnapshot != nil {
if strings.TrimSpace(baseSnapshot.PackageCode) == selectedPackageCode {
if !sameRenewPriceSnapshot(baseSnapshot, snapshot) {
priceChanged = true
}
} else {
oldOption, ok := renewPackageOptionByCode(baseSnapshot.RenewablePackages, selectedPackageCode)
if !ok || !sameRenewPackageOption(oldOption, snapshot) {
priceChanged = true
}
}
if !sameRenewPackageOptions(baseSnapshot.RenewablePackages, renewablePackages) {
priceChanged = true
}
}
return snapshot, priceChanged, nil
}
func (s *sIotTSimCardRenew) refreshRenewPrepareState(ctx context.Context, tx gdb.TX, batch *entity.BatchMain, rows []*renew.Row, snapshot *model.TSimCardRenewPriceSnapshot) error {
if batch == nil || batch.Id == 0 {
return gerror.New("续费预处理批次信息不完整")
}
info := prepareInfoMap(batch.BatchInfo)
if info == nil {
info = map[string]any{}
}
if snapshot != nil {
info["customerAgentId"] = snapshot.CustomerAgentId
info["packageCode"] = snapshot.PackageCode
info["packageName"] = snapshot.PackageName
info["billingMethod"] = snapshot.BillingMethod
info["renewDurationUnit"] = snapshot.RenewDurationUnit
info["renewDurationUnitText"] = snapshot.RenewDurationUnitText
info["priceSource"] = snapshot.PriceSource
info["priceOwnerDeptId"] = snapshot.PriceOwnerDeptId
info["agentDefaultPriceId"] = snapshot.AgentDefaultPriceId
info["targetPriceId"] = snapshot.TargetPriceId
info["renewUnitPrice"] = snapshot.RenewUnitPrice
info["renewableDurationOptions"] = snapshot.RenewableDurationOptions
info["renewablePackageCodes"] = snapshot.RenewablePackageCodes
info["renewablePackages"] = snapshot.RenewablePackages
info["renewBeginDate"] = snapshot.RenewBeginDate
info["renewBeginDateMin"] = snapshot.RenewBeginDateMin
info["renewBeginDateMax"] = snapshot.RenewBeginDateMax
info["sameRenewBeginDate"] = snapshot.SameRenewBeginDate
info["priceSnapshot"] = snapshot
}
successCount := 0
for _, row := range rows {
if row == nil {
continue
}
detailStatus := consts.BatchDetailStatusSuccess
if !row.Valid() {
detailStatus = consts.BatchDetailStatusFailed
} else {
successCount++
}
if _, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
Where(dao.BatchDetail.Columns().BatchNo, batch.BatchNo).
Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypePrepare).
Where(dao.BatchDetail.Columns().RowNo, row.RowNo).
Data(do.BatchDetail{
DetailStatus: detailStatus,
ErrorReason: strings.TrimSpace(row.Reason),
UpdatedBy: contexts.GetUserId(ctx),
}).
Update(); err != nil {
return gerror.Wrap(err, "刷新续费预处理明细失败")
}
}
totalCount := batch.TotalCount
if minimumTotal := successCount + batch.FailCount; totalCount < minimumTotal {
totalCount = minimumTotal
}
failCount := totalCount - successCount
if failCount < 0 {
failCount = 0
}
info["quantity"] = successCount
updatedInfo := gjson.New(info)
mainData := do.BatchMain{
TotalCount: totalCount,
ValidCount: successCount,
SuccessCount: successCount,
FailCount: failCount,
FailReason: renewPrepareFailReason(failCount),
BatchInfo: updatedInfo,
UpdatedBy: contexts.GetUserId(ctx),
}
if snapshot != nil && snapshot.CustomerAgentId > 0 {
mainData.TargetDeptId = snapshot.CustomerAgentId
}
if _, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
Where(dao.BatchMain.Columns().Id, batch.Id).
Data(mainData).
Update(); err != nil {
return gerror.Wrap(err, "刷新续费预处理结果失败")
}
batch.BatchInfo = updatedInfo
batch.TotalCount = totalCount
batch.ValidCount = successCount
batch.SuccessCount = successCount
batch.FailCount = failCount
return nil
}
func renewPrepareFailReason(failCount int) string {
if failCount <= 0 {
return ""
}
return "续费条件已更新,部分卡已不满足续费条件"
}
func renewSnapshotPackageCode(snapshot *model.TSimCardRenewPriceSnapshot) string {
if snapshot == nil {
return ""
}
return snapshot.PackageCode
}
func renewSnapshotPackageName(snapshot *model.TSimCardRenewPriceSnapshot) string {
if snapshot == nil {
return ""
}
return snapshot.PackageName
}
func sameRenewPriceSnapshot(left, right *model.TSimCardRenewPriceSnapshot) bool {
if left == nil || right == nil {
return left == right
}
return strings.TrimSpace(left.PackageCode) == strings.TrimSpace(right.PackageCode) &&
left.BillingMethod == right.BillingMethod &&
math.Abs(left.RenewUnitPrice-right.RenewUnitPrice) < 0.000001 &&
strings.TrimSpace(left.PriceSource) == strings.TrimSpace(right.PriceSource) &&
left.PriceOwnerDeptId == right.PriceOwnerDeptId &&
left.AgentDefaultPriceId == right.AgentDefaultPriceId &&
left.TargetPriceId == right.TargetPriceId &&
sameRenewDurationOptions(left.RenewableDurationOptions, right.RenewableDurationOptions) &&
sameRenewPackageOptions(left.RenewablePackages, right.RenewablePackages)
}
func renewPackageOptionByCode(options []model.TSimCardRenewPackageOption, packageCode string) (model.TSimCardRenewPackageOption, bool) {
packageCode = strings.TrimSpace(packageCode)
for _, option := range options {
if strings.TrimSpace(option.PackageCode) == packageCode {
return option, true
}
}
return model.TSimCardRenewPackageOption{}, false
}
func sameRenewPackageOption(option model.TSimCardRenewPackageOption, snapshot *model.TSimCardRenewPriceSnapshot) bool {
if snapshot == nil {
return false
}
return strings.TrimSpace(option.PackageCode) == strings.TrimSpace(snapshot.PackageCode) &&
math.Abs(option.RenewUnitPrice-snapshot.RenewUnitPrice) < 0.000001 &&
sameRenewDurationOptions(option.RenewableDurationOptions, snapshot.RenewableDurationOptions)
}
func sameRenewDurationOptions(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 sameRenewPackageOptions(left, right []model.TSimCardRenewPackageOption) bool {
if len(left) != len(right) {
return false
}
for index := range left {
if strings.TrimSpace(left[index].PackageCode) != strings.TrimSpace(right[index].PackageCode) ||
strings.TrimSpace(left[index].PackageName) != strings.TrimSpace(right[index].PackageName) ||
math.Abs(left[index].RenewUnitPrice-right[index].RenewUnitPrice) >= 0.000001 ||
!sameRenewDurationOptions(left[index].RenewableDurationOptions, right[index].RenewableDurationOptions) {
return false
}
}
return true
}
func (s *sIotTSimCardRenew) loadRenewablePackageOptions(ctx context.Context, tx gdb.TX, customerAgentId int64, currentPackageCode string) ([]string, []model.TSimCardRenewPackageOption, error) {
currentPackageCode = strings.TrimSpace(currentPackageCode)
if currentPackageCode == "" {
return nil, nil, gerror.New("当前套餐不能为空")
}
columns := dao.TSimFlowPackageInfo.Columns()
current := &entity.TSimFlowPackageInfo{}
if err := tx.Ctx(ctx).Model(dao.TSimFlowPackageInfo.Table()).
Fields(columns.PackageCode, columns.PackageName, columns.RenewablePackageCodes).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
Where(columns.PackageCode, currentPackageCode).
Scan(current); err != nil {
return nil, nil, gerror.Wrap(err, "查询可续费套餐配置失败")
}
if current.Id == 0 && strings.TrimSpace(current.PackageCode) == "" {
return nil, nil, gerror.New("当前套餐不存在")
}
var codes []string
if current.RenewablePackageCodes != nil {
_ = current.RenewablePackageCodes.Scan(&codes)
}
codes = uniqueRenewStrings(codes)
if len(codes) == 0 {
return nil, nil, gerror.New("当前套餐未配置可续费套餐")
}
var packages []*entity.TSimFlowPackageInfo
if err := tx.Ctx(ctx).Model(dao.TSimFlowPackageInfo.Table()).
Fields(
columns.PackageCode,
columns.PackageName,
columns.CostPrice,
columns.TerminalCost,
columns.SysDefaultRatio,
columns.SysDefaultProfit,
columns.SysDefaultTerminalRatio,
columns.SysDefaultTerminalProfit,
columns.RenewableDurationConfig,
).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
WhereIn(columns.PackageCode, codes).
Scan(&packages); err != nil {
return nil, nil, gerror.Wrap(err, "查询可续费套餐列表失败")
}
nameByCode := make(map[string]string, len(packages))
packageByCode := make(map[string]*entity.TSimFlowPackageInfo, len(packages))
for _, item := range packages {
if item == nil {
continue
}
code := strings.TrimSpace(item.PackageCode)
nameByCode[code] = strings.TrimSpace(item.PackageName)
packageByCode[code] = item
}
priceOwnerDeptID, err := s.loadRenewPriceOwnerDeptID(ctx, tx, customerAgentId)
if err != nil {
return nil, nil, err
}
options := make([]model.TSimCardRenewPackageOption, 0, len(codes))
for _, code := range codes {
name, ok := nameByCode[code]
if !ok {
continue
}
pkg := packageByCode[code]
salePrice, err := resolvePackageEffectiveSalePrice(ctx, tx, contexts.GetTenantId(ctx), priceOwnerDeptID, customerAgentId, packagePriceSourceFromEntity(pkg))
if err != nil {
return nil, nil, err
}
unitPrice := salePrice.AgentShipmentPrice
durationOptions, err := renewableDurationOptions(pkg)
if err != nil {
return nil, nil, err
}
options = append(options, model.TSimCardRenewPackageOption{
PackageCode: code,
PackageName: name,
RenewUnitPrice: unitPrice,
RenewableDurationOptions: durationOptions,
})
}
return codes, options, nil
}
func (s *sIotTSimCardRenew) buildRenewPriceSnapshot(ctx context.Context, tx gdb.TX, customerAgentId int64, packageCode string) (*model.TSimCardRenewPriceSnapshot, error) {
packageCode = strings.TrimSpace(packageCode)
if customerAgentId <= 0 {
return nil, gerror.New("客户不能为空")
}
if packageCode == "" {
return nil, gerror.New("套餐不能为空")
}
pkg := &entity.TSimFlowPackageInfo{}
pkgColumns := dao.TSimFlowPackageInfo.Columns()
if err := tx.Ctx(ctx).Model(dao.TSimFlowPackageInfo.Table()).
Where(pkgColumns.TenantId, contexts.GetTenantId(ctx)).
Where(pkgColumns.PackageCode, packageCode).
Scan(pkg); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, gerror.New("续费套餐不存在")
}
return nil, gerror.Wrap(err, "查询续费套餐失败")
}
if pkg.Id == 0 {
return nil, gerror.New("续费套餐不存在")
}
options, err := renewableDurationOptions(pkg)
if err != nil {
return nil, err
}
durationUnit := consts.RenewDurationUnitYear
snapshot := &model.TSimCardRenewPriceSnapshot{
CustomerAgentId: customerAgentId,
PackageCode: pkg.PackageCode,
PackageName: pkg.PackageName,
BillingMethod: consts.BillingMethodMonth,
RenewDurationUnit: durationUnit,
RenewDurationUnitText: consts.RenewDurationUnitText(durationUnit),
PriceSource: "system",
RenewUnitPrice: roundAmount(pkg.CostPrice*normalizePriceRatio(pkg.SysDefaultRatio) + pkg.SysDefaultProfit),
RenewableDurationOptions: options,
}
priceOwnerDeptID, err := s.loadRenewPriceOwnerDeptID(ctx, tx, customerAgentId)
if err != nil {
return nil, err
}
snapshot.PriceOwnerDeptId = priceOwnerDeptID
salePrice, err := resolvePackageEffectiveSalePrice(ctx, tx, contexts.GetTenantId(ctx), priceOwnerDeptID, customerAgentId, packagePriceSourceFromEntity(pkg))
if err != nil {
return nil, err
}
snapshot.PriceSource = salePrice.Rule.PriceSource
snapshot.AgentDefaultPriceId = salePrice.Rule.AgentDefaultPriceId
snapshot.TargetPriceId = salePrice.Rule.TargetPriceId
snapshot.RenewUnitPrice = salePrice.AgentShipmentPrice
return snapshot, nil
}
func (s *sIotTSimCardRenew) loadRenewPriceOwnerDeptID(ctx context.Context, tx gdb.TX, customerAgentId int64) (int64, error) {
if customerAgentId <= 0 {
return 0, gerror.New("客户不能为零")
}
columns := dao.SysDept.Columns()
dept := &entity.SysDept{}
err := tx.Ctx(ctx).Model(dao.SysDept.Table()).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
Where(columns.DeptId, customerAgentId).
WhereNull(columns.DeletedAt).
Scan(dept)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return 0, gerror.New("客户部门不存在")
}
return 0, gerror.Wrap(err, "查询客户上级部门失败")
}
if dept.DeptId == 0 {
return 0, gerror.New("客户部门不存在")
}
return dept.ParentId, nil
}
func (s *sIotTSimCardRenew) loadRenewPriceOverride(ctx context.Context, tx gdb.TX, deptID int64, scopeType int, targetAgentId int64, packageCode string) (*entity.TSimFlowPackagePrice, error) {
columns := dao.TSimFlowPackagePrice.Columns()
price := &entity.TSimFlowPackagePrice{}
err := tx.Ctx(ctx).Model(dao.TSimFlowPackagePrice.Table()).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
Where(columns.DeptId, deptID).
Where(columns.AgentScopeType, scopeType).
Where(columns.TargetAgentId, targetAgentId).
Where(columns.PackageCode, packageCode).
Scan(price)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, gerror.Wrap(err, "查询代理续费定价失败")
}
if price.Id == 0 {
return nil, nil
}
return price, nil
}
func batchRowsToRenewRows(rows []batchimport.Row) ([]*renew.Row, error) {
result := make([]*renew.Row, 0, len(rows))
for _, item := range rows {
row, ok := item.(*renew.Row)
if !ok || row == nil {
return nil, gerror.New("续费行类型错误")
}
result = append(result, row)
}
return result, nil
}
func renewRowsToBatchRows(rows []*renew.Row) []batchimport.Row {
result := make([]batchimport.Row, 0, len(rows))
for _, row := range rows {
result = append(result, row)
}
return result
}
func fillPreparePrice(res *model.TSimCardRenewBatchPrepareRes, snapshot *model.TSimCardRenewPriceSnapshot) {
res.CustomerAgentId = snapshot.CustomerAgentId
res.PackageCode = snapshot.PackageCode
res.PackageName = snapshot.PackageName
res.BillingMethod = snapshot.BillingMethod
res.RenewDurationUnit = snapshot.RenewDurationUnit
res.RenewDurationUnitText = snapshot.RenewDurationUnitText
res.PriceSource = snapshot.PriceSource
res.PriceOwnerDeptId = snapshot.PriceOwnerDeptId
res.AgentDefaultPriceId = snapshot.AgentDefaultPriceId
res.TargetPriceId = snapshot.TargetPriceId
res.RenewUnitPrice = snapshot.RenewUnitPrice
res.RenewableDurationOptions = snapshot.RenewableDurationOptions
res.RenewablePackageCodes = snapshot.RenewablePackageCodes
res.RenewablePackages = snapshot.RenewablePackages
res.RenewBeginDate = snapshot.RenewBeginDate
res.RenewBeginDateMin = snapshot.RenewBeginDateMin
res.RenewBeginDateMax = snapshot.RenewBeginDateMax
res.SameRenewBeginDate = snapshot.SameRenewBeginDate
}
func fillRenewBeginSnapshot(snapshot *model.TSimCardRenewPriceSnapshot, prepared *renew.PrepareContext) {
if snapshot == nil || prepared == nil {
return
}
snapshot.RenewBeginDate = strings.TrimSpace(prepared.RenewBeginDate)
snapshot.RenewBeginDateMin = strings.TrimSpace(prepared.RenewBeginDateMin)
snapshot.RenewBeginDateMax = strings.TrimSpace(prepared.RenewBeginDateMax)
snapshot.SameRenewBeginDate = prepared.SameRenewBeginDate
}
func renewSourceBatchNoFromBatch(batch *entity.BatchMain) string {
if batch == nil {
return ""
}
if batch.BatchInfo != nil {
return strings.TrimSpace(batch.BatchInfo.Get("sourceBatchNo").String())
}
return strings.TrimSpace(batch.SourceBatchNo)
}
func snapshotFromBatch(batch *entity.BatchMain) (*model.TSimCardRenewPriceSnapshot, error) {
if batch == nil || batch.BatchInfo == nil {
return nil, gerror.New("续费预处理批次缺少报价信息")
}
options := packageduration.NormalizeRenewableYears(jsonInts(batch.BatchInfo, "renewableDurationOptions", "priceSnapshot.renewableDurationOptions"))
sort.Ints(options)
snapshot := &model.TSimCardRenewPriceSnapshot{
CustomerAgentId: jsonInt64(batch.BatchInfo, "customerAgentId", "customer.deptId", "priceSnapshot.customerAgentId"),
PriceOwnerDeptId: jsonInt64(batch.BatchInfo, "priceOwnerDeptId", "priceSnapshot.priceOwnerDeptId"),
PackageCode: jsonString(batch.BatchInfo, "packageCode", "priceSnapshot.packageCode"),
PackageName: jsonString(batch.BatchInfo, "packageName", "priceSnapshot.packageName"),
BillingMethod: consts.BillingMethodMonth,
RenewDurationUnit: jsonString(batch.BatchInfo, "renewDurationUnit", "priceSnapshot.renewDurationUnit"),
RenewDurationUnitText: jsonString(batch.BatchInfo, "renewDurationUnitText", "priceSnapshot.renewDurationUnitText"),
PriceSource: jsonString(batch.BatchInfo, "priceSource", "priceSnapshot.priceSource"),
AgentDefaultPriceId: jsonInt64(batch.BatchInfo, "agentDefaultPriceId", "priceSnapshot.agentDefaultPriceId"),
TargetPriceId: jsonInt64(batch.BatchInfo, "targetPriceId", "priceSnapshot.targetPriceId"),
RenewUnitPrice: roundAmount(jsonFloat64(batch.BatchInfo, "renewUnitPrice", "priceSnapshot.renewUnitPrice")),
RenewableDurationOptions: options,
RenewablePackageCodes: jsonStrings(batch.BatchInfo, "renewablePackageCodes", "priceSnapshot.renewablePackageCodes"),
RenewablePackages: renewPackageOptionsFromValue(jsonField(batch.BatchInfo, "renewablePackages", "priceSnapshot.renewablePackages")),
RenewBeginDate: jsonString(batch.BatchInfo, "renewBeginDate", "priceSnapshot.renewBeginDate"),
RenewBeginDateMin: jsonString(batch.BatchInfo, "renewBeginDateMin", "priceSnapshot.renewBeginDateMin"),
RenewBeginDateMax: jsonString(batch.BatchInfo, "renewBeginDateMax", "priceSnapshot.renewBeginDateMax"),
SameRenewBeginDate: jsonBool(batch.BatchInfo, "sameRenewBeginDate", "priceSnapshot.sameRenewBeginDate"),
}
if snapshot.PackageCode == "" || snapshot.RenewUnitPrice < 0 || len(snapshot.RenewableDurationOptions) == 0 {
return nil, gerror.New("续费预处理批次报价信息不完整")
}
snapshot.RenewDurationUnit = consts.RenewDurationUnitYear
snapshot.RenewDurationUnitText = consts.RenewDurationUnitText(snapshot.RenewDurationUnit)
snapshot.BillingMethod = consts.BillingMethodMonth
return snapshot, nil
}
func renewableDurationOptions(pkg *entity.TSimFlowPackageInfo) ([]int, error) {
return packageduration.RenewableYears(pkg)
}
func parseRenewDate(value string) (*gtime.Time, error) {
value = strings.TrimSpace(value)
if value == "" {
return gtime.Now(), nil
}
t, err := gtime.StrToTime(value)
if err != nil {
return nil, gerror.Wrap(err, "续费办理日期格式不正确")
}
return t, nil
}
func renewDateString(t *gtime.Time) string {
if t == nil {
return ""
}
return t.Format("Y-m-d")
}
func parseRenewPeriodDate(value string, fieldName string) (time.Time, error) {
value = strings.TrimSpace(value)
if value == "" {
return time.Time{}, gerror.Newf("%s不能为空", fieldName)
}
for _, layout := range []string{"2006-01-02", "2006-01-02 15:04:05"} {
if parsed, err := time.ParseInLocation(layout, value, time.Local); err == nil {
return renewDayEndTime(parsed), nil
}
}
return time.Time{}, gerror.Newf("%s格式不正确", fieldName)
}
func sameDate(left time.Time, right time.Time) bool {
leftYear, leftMonth, leftDay := left.Date()
rightYear, rightMonth, rightDay := right.Date()
return leftYear == rightYear && leftMonth == rightMonth && leftDay == rightDay
}
func containsInt(values []int, target int) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
func containsString(values []string, target string) bool {
target = strings.TrimSpace(target)
if target == "" {
return false
}
for _, value := range values {
if strings.TrimSpace(value) == target {
return true
}
}
return false
}
func normalizeLegacyRenewYears(raw int) int {
if raw <= 0 {
return 0
}
if raw >= 12 && raw%12 == 0 {
return raw / 12
}
return raw
}
func currentOperator(ctx context.Context) batchimport.OperatorContext {
return batchimport.OperatorContext{
TenantId: contexts.GetTenantId(ctx),
DeptId: contexts.GetDeptId(ctx),
UserId: contexts.GetUserId(ctx),
}
}
func roundAmount(value float64) float64 {
return math.Round(value*100) / 100
}
func pendingStatus(importStatus string) string {
if importStatus == "" {
return "pending"
}
return importStatus
}
func publishPendingExecute(ctx context.Context, batchNo string, batchType int, tenantId string) {
if strings.TrimSpace(batchNo) == "" || batchType == 0 {
return
}
if err := rabbitmq.Publish(ctx, consts.QueueBatchExecute, batchimport.Message{
BatchNo: batchNo,
BatchType: batchType,
}); err != nil {
g.Log().Errorf(ctx, "renew/change batch async publish failed, batchNo=%s, batchType=%d, tenantId=%s, error=%+v",
batchNo, batchType, tenantId, err)
}
}
type prepareResultBuilder func(ctx context.Context, batch *entity.BatchMain) map[string]any
func loadPrepareResult(
ctx context.Context,
batchNo string,
targetBatchType int,
snapshotFn func(*entity.BatchMain) (*gjson.Json, error),
fieldsFn prepareResultBuilder,
) (*model.TSimCardRenewPrepareResultRes, error) {
batchNo = strings.TrimSpace(batchNo)
if batchNo == "" {
return nil, gerror.New("批次号不能为空")
}
columns := dao.BatchMain.Columns()
batch := &entity.BatchMain{}
if err := dao.BatchMain.Ctx(ctx).
Where(columns.BatchNo, batchNo).
Where(columns.TenantId, contexts.GetTenantId(ctx)).
Where(columns.CreatedDept, contexts.GetDeptId(ctx)).
Scan(batch); err != nil {
return nil, gerror.Wrap(err, "查询预处理批次失败")
}
if batch.Id == 0 {
return nil, gerror.New("预处理批次不存在或不可见")
}
if batch.BatchType != consts.BatchTypePrepare && !(batch.BatchInfo != nil && batch.BatchInfo.Get("targetBatchType").Int() == targetBatchType) {
return nil, gerror.New("批次类型不匹配")
}
asyncStatus := consts.BatchAsyncStatusPending
runResult := consts.BatchRunResultPending
statusKey := "pending"
switch {
case batch.BatchAsyncStatus == consts.BatchAsyncStatusProcessing || batch.RunResult == consts.BatchRunResultRunning:
asyncStatus = consts.BatchAsyncStatusProcessing
runResult = consts.BatchRunResultRunning
statusKey = "processing"
case batch.BatchAsyncStatus == consts.BatchAsyncStatusSuccess && batch.RunResult == consts.BatchRunResultSuccess:
asyncStatus = consts.BatchAsyncStatusSuccess
runResult = consts.BatchRunResultSuccess
statusKey = "success"
case batch.BatchAsyncStatus == consts.BatchAsyncStatusFailed || batch.RunResult == consts.BatchRunResultFailed:
asyncStatus = consts.BatchAsyncStatusFailed
runResult = consts.BatchRunResultFailed
statusKey = "failed"
case batch.BatchAsyncStatus == consts.BatchAsyncStatusPartialSuccess || batch.RunResult == consts.BatchRunResultPartialSuccess:
asyncStatus = consts.BatchAsyncStatusFailed
runResult = consts.BatchRunResultFailed
statusKey = "failed"
}
res := &model.TSimCardRenewPrepareResultRes{
BatchNo: batch.BatchNo,
BatchType: batch.BatchType,
TargetBatchType: targetBatchType,
Status: statusKey,
BatchAsyncStatus: asyncStatus,
RunResult: runResult,
Total: batch.TotalCount,
SuccessCount: batch.SuccessCount,
FailCount: batch.FailCount,
FailReason: strings.TrimSpace(batch.FailReason),
StartTime: batch.StartTime,
EndTime: batch.EndTime,
}
if snapshot, err := snapshotFn(batch); err == nil && snapshot != nil {
res.Snapshot = snapshot
}
if fieldsFn != nil {
if fields := fieldsFn(ctx, batch); fields != nil {
if value, ok := fields["customerAgentId"]; ok {
res.CustomerAgentId = gconv.Int64(value)
}
if value, ok := fields["customerName"]; ok {
res.CustomerName = gconv.String(value)
}
if value, ok := fields["packageCode"]; ok {
res.PackageCode = gconv.String(value)
}
if value, ok := fields["packageName"]; ok {
res.PackageName = gconv.String(value)
}
if value, ok := fields["billingMethod"]; ok {
res.BillingMethod = gconv.Int(value)
}
if value, ok := fields["renewDurationUnit"]; ok {
res.RenewDurationUnit = gconv.String(value)
}
if value, ok := fields["renewDurationUnitText"]; ok {
res.RenewDurationUnitText = gconv.String(value)
}
if value, ok := fields["durationValue"]; ok {
res.DurationValue = gconv.Int(value)
}
if value, ok := fields["durationYears"]; ok {
res.DurationYears = gconv.Int(value)
}
if value, ok := fields["oldPackageCode"]; ok {
res.OldPackageCode = gconv.String(value)
}
if value, ok := fields["oldPackageName"]; ok {
res.OldPackageName = gconv.String(value)
}
if value, ok := fields["oldPackageType"]; ok {
res.OldPackageType = gconv.Int(value)
}
if value, ok := fields["oldPackageFlowMb"]; ok {
res.OldPackageFlowMb = gconv.Float64(value)
}
if value, ok := fields["quantity"]; ok {
res.Quantity = gconv.Int(value)
}
if value, ok := fields["renewUnitPrice"]; ok {
res.RenewUnitPrice = gconv.Float64(value)
}
if value, ok := fields["priceSource"]; ok {
res.PriceSource = gconv.String(value)
}
if value, ok := fields["renewableDurationOptions"]; ok {
res.RenewableDurationOptions = packageduration.NormalizeRenewableYears(gconv.Ints(value))
}
if value, ok := fields["renewablePackageCodes"]; ok {
res.RenewablePackageCodes = uniqueRenewStrings(gconv.Strings(value))
}
if value, ok := fields["renewablePackages"]; ok {
res.RenewablePackages = renewPackageOptionsFromValue(gvar.New(value))
}
if value, ok := fields["agentDefaultPriceId"]; ok {
res.AgentDefaultPriceId = gconv.Int64(value)
}
if value, ok := fields["targetPriceId"]; ok {
res.TargetPriceId = gconv.Int64(value)
}
if value, ok := fields["isTopDept"]; ok {
res.IsTopDept = gconv.Bool(value)
}
if value, ok := fields["defaultPaymentMode"]; ok {
res.DefaultPaymentMode = gconv.String(value)
}
if value, ok := fields["availableBalance"]; ok {
res.AvailableBalance = gconv.Float64(value)
}
if value, ok := fields["renewBeginDate"]; ok {
res.RenewBeginDate = gconv.String(value)
}
if value, ok := fields["renewBeginDateMin"]; ok {
res.RenewBeginDateMin = gconv.String(value)
}
if value, ok := fields["renewBeginDateMax"]; ok {
res.RenewBeginDateMax = gconv.String(value)
}
if value, ok := fields["sameRenewBeginDate"]; ok {
res.SameRenewBeginDate = gconv.Bool(value)
}
}
}
return res, nil
}
func renewPriceSnapshotFromBatch(batch *entity.BatchMain) (*gjson.Json, error) {
if batch == nil {
return nil, nil
}
return gjson.New(buildRenewPrepareFields(batch)), nil
}
func buildRenewResultFields(_ context.Context, batch *entity.BatchMain) map[string]any {
if batch == nil {
return nil
}
return buildRenewPrepareFields(batch)
}
func buildRenewPrepareFields(batch *entity.BatchMain) map[string]any {
if batch == nil {
return nil
}
fields := prepareInfoMap(batch.BatchInfo)
if fields == nil {
fields = map[string]any{}
}
if value := jsonField(batch.BatchInfo, "customerAgentId", "customer.deptId", "priceSnapshot.customerAgentId"); value != nil {
fields["customerAgentId"] = gconv.Int64(value)
}
if value := jsonField(batch.BatchInfo, "customerName", "customer.name"); value != nil {
fields["customerName"] = strings.TrimSpace(value.String())
}
if value := jsonField(batch.BatchInfo, "packageCode", "priceSnapshot.packageCode"); value != nil {
fields["packageCode"] = strings.TrimSpace(value.String())
}
if value := jsonField(batch.BatchInfo, "packageName", "priceSnapshot.packageName"); value != nil {
fields["packageName"] = strings.TrimSpace(value.String())
}
fields["billingMethod"] = consts.BillingMethodMonth
fields["renewDurationUnit"] = consts.RenewDurationUnitYear
fields["renewDurationUnitText"] = consts.RenewDurationUnitText(consts.RenewDurationUnitYear)
if value := jsonField(batch.BatchInfo, "priceSource", "priceSnapshot.priceSource"); value != nil {
fields["priceSource"] = strings.TrimSpace(value.String())
}
if value := jsonField(batch.BatchInfo, "agentDefaultPriceId", "priceSnapshot.agentDefaultPriceId"); value != nil {
fields["agentDefaultPriceId"] = gconv.Int64(value)
}
if value := jsonField(batch.BatchInfo, "targetPriceId", "priceSnapshot.targetPriceId"); value != nil {
fields["targetPriceId"] = gconv.Int64(value)
}
if value := jsonField(batch.BatchInfo, "renewUnitPrice", "priceSnapshot.renewUnitPrice"); value != nil {
fields["renewUnitPrice"] = roundAmount(gconv.Float64(value))
}
if values := jsonInts(batch.BatchInfo, "renewableDurationOptions", "priceSnapshot.renewableDurationOptions"); len(values) > 0 {
fields["renewableDurationOptions"] = packageduration.NormalizeRenewableYears(values)
}
if values := jsonStrings(batch.BatchInfo, "renewablePackageCodes", "priceSnapshot.renewablePackageCodes"); len(values) > 0 {
fields["renewablePackageCodes"] = values
}
if value := jsonField(batch.BatchInfo, "renewablePackages", "priceSnapshot.renewablePackages"); value != nil {
fields["renewablePackages"] = renewPackageOptionsFromValue(value)
}
if value := jsonField(batch.BatchInfo, "renewBeginDate", "priceSnapshot.renewBeginDate"); value != nil {
fields["renewBeginDate"] = strings.TrimSpace(value.String())
}
if value := jsonField(batch.BatchInfo, "renewBeginDateMin", "priceSnapshot.renewBeginDateMin"); value != nil {
fields["renewBeginDateMin"] = strings.TrimSpace(value.String())
}
if value := jsonField(batch.BatchInfo, "renewBeginDateMax", "priceSnapshot.renewBeginDateMax"); value != nil {
fields["renewBeginDateMax"] = strings.TrimSpace(value.String())
}
if value := jsonField(batch.BatchInfo, "sameRenewBeginDate", "priceSnapshot.sameRenewBeginDate"); value != nil {
fields["sameRenewBeginDate"] = gconv.Bool(value)
}
if value := jsonField(batch.BatchInfo, "durationValue", "durationYears"); value != nil {
fields["durationValue"] = gconv.Int(value)
fields["durationYears"] = gconv.Int(value)
}
if value := jsonField(batch.BatchInfo, "quantity"); value != nil {
fields["quantity"] = gconv.Int(value)
} else if batch.TotalCount > 0 {
fields["quantity"] = batch.TotalCount
}
return fields
}
func prepareInfoMap(info *gjson.Json) map[string]any {
if info == nil {
return nil
}
raw := info.Map()
fields := make(map[string]any, len(raw))
for key, value := range raw {
fields[key] = value
}
return fields
}
func jsonField(info *gjson.Json, paths ...string) *gvar.Var {
if info == nil {
return nil
}
for _, path := range paths {
value := info.Get(path)
if value != nil && !value.IsNil() {
return value
}
}
return nil
}
func jsonString(info *gjson.Json, paths ...string) string {
if value := jsonField(info, paths...); value != nil {
return strings.TrimSpace(value.String())
}
return ""
}
func jsonInt64(info *gjson.Json, paths ...string) int64 {
if value := jsonField(info, paths...); value != nil {
return gconv.Int64(value)
}
return 0
}
func jsonInt(info *gjson.Json, paths ...string) int {
if value := jsonField(info, paths...); value != nil {
return gconv.Int(value)
}
return 0
}
func jsonFloat64(info *gjson.Json, paths ...string) float64 {
if value := jsonField(info, paths...); value != nil {
return gconv.Float64(value)
}
return 0
}
func jsonBool(info *gjson.Json, paths ...string) bool {
if value := jsonField(info, paths...); value != nil {
return gconv.Bool(value)
}
return false
}
func jsonStrings(info *gjson.Json, paths ...string) []string {
if info == nil {
return nil
}
for _, path := range paths {
value := info.Get(path)
if value == nil || value.IsNil() {
continue
}
values := value.Strings()
if len(values) > 0 || len(value.Interfaces()) > 0 {
return uniqueRenewStrings(values)
}
}
return nil
}
func jsonInts(info *gjson.Json, paths ...string) []int {
if info == nil {
return nil
}
for _, path := range paths {
value := info.Get(path)
if value == nil || value.IsNil() {
continue
}
values := value.Ints()
if len(values) > 0 || len(value.Interfaces()) > 0 {
return values
}
}
return nil
}
func renewPackageOptionsFromValue(value *gvar.Var) []model.TSimCardRenewPackageOption {
if value == nil || value.IsNil() {
return nil
}
var options []model.TSimCardRenewPackageOption
if err := gconv.Structs(value.Interfaces(), &options); err == nil && len(options) > 0 {
return options
}
return nil
}
func uniqueRenewStrings(list []string) []string {
result := make([]string, 0, len(list))
seen := make(map[string]struct{}, len(list))
for _, item := range list {
item = strings.TrimSpace(item)
if item == "" {
continue
}
if _, ok := seen[item]; ok {
continue
}
seen[item] = struct{}{}
result = append(result, item)
}
return result
}