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 }