package change import ( "context" "strings" "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/model" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" ) type Processor struct { repo *Repository config batchimport.ProcessorConfig } func NewProcessor() *Processor { return &Processor{ repo: NewRepository(), config: batchimport.ProcessorConfig{ Code: "sim_card_change", BatchType: consts.BatchTypeChange, BatchNoPrefix: consts.BatchNoPrefixChange, TaskName: consts.ResourceBatchDownloadTaskSimCardChange, ExportScene: consts.ResourceExportSceneChange, ImportDir: consts.SimCardChangeSubDirImportExcel, ResultDir: consts.SimCardChangeSubDirResultExcel, MaxFileSize: consts.SimCardChangeMaxFileSize, TemplateHeaders: []string{"ICCID", "MSISDN", "IMSI"}, RequiredHeaders: []string{"ICCID"}, }, } } func (p *Processor) Config() batchimport.ProcessorConfig { return p.config } func (p *Processor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) { return nil, gerror.New("sim card change does not support file import on base processor") } func (p *Processor) ValidateRows(ctx context.Context, rows []batchimport.Row) error { changeRows := make([]*Row, 0, len(rows)) for _, item := range rows { row, ok := item.(*Row) if !ok || row == nil { item.AddErrorReason("change row type is invalid") continue } changeRows = append(changeRows, row) } return validateRows(changeRows) } func (p *Processor) Execute(ctx context.Context, tx gdb.TX, req *batchimport.SubmitRequest, rows []batchimport.Row) error { return gerror.New("sim card change must be submitted from prepare batch") } func (p *Processor) BuildBatchInfo(ctx context.Context, req batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadId int64) *gjson.Json { info := g.Map{} if resultOss != nil { info["resultFileOssId"] = resultOss.OssId } if downloadId > 0 { info["resultDownloadId"] = downloadId } if in, ok := req.BizParams.(*model.TSimCardChangeBatchPrepareParam); ok && in != nil { if sourceBatchNo := strings.TrimSpace(in.SourceBatchNo); sourceBatchNo != "" { info["sourceBatchNo"] = sourceBatchNo } } if in, ok := req.BizParams.(*model.TSimCardChangeBatchSubmitParam); ok && in != nil { if newPackageCode := strings.TrimSpace(in.NewPackageCode); newPackageCode != "" { info["newPackageCode"] = newPackageCode } if in.DurationMonths > 0 { info["durationMonths"] = in.DurationMonths } if in.GiftDurationMonths >= 0 { info["giftDurationMonths"] = in.GiftDurationMonths } if effectiveMode := strings.TrimSpace(in.EffectiveMode); effectiveMode != "" { info["effectiveMode"] = effectiveMode } info["offlineContractCompleted"] = in.OfflineContractCompleted } if snapshot, ok := req.ExecutionResult.(*model.TSimCardChangePrepareSnapshot); ok && snapshot != nil { if result != nil && result.BatchType == consts.BatchTypePrepare { info["targetBatchType"] = consts.BatchTypeChange info["targetBatchName"] = "sim_card_change" } if snapshot.SourceBatchNo != "" { info["sourceBatchNo"] = snapshot.SourceBatchNo } info["customerAgentId"] = snapshot.CustomerAgentId info["customerName"] = snapshot.CustomerName info["oldPackageCode"] = snapshot.OldPackageCode info["oldPackageName"] = snapshot.OldPackageName info["oldPackageType"] = snapshot.OldPackageType info["oldPackageFlowMb"] = snapshot.OldPackageFlowMb info["quantity"] = snapshot.Quantity } if output, ok := req.ExecutionResult.(*CompleteOutput); ok && output != nil { if output.SourceBatchNo != "" { info["sourceBatchNo"] = output.SourceBatchNo } info["sourceMode"] = output.SourceMode info["orderSn"] = output.OrderSn info["customerAgentId"] = output.CustomerAgentId info["customerName"] = output.CustomerName info["oldPackageCode"] = output.OldPackageCode info["oldPackageName"] = output.OldPackageName info["newPackageCode"] = output.NewPackageCode info["newPackageName"] = output.NewPackageName info["durationMonths"] = output.DurationMonths info["giftDurationMonths"] = output.GiftDurationMonths info["effectiveMode"] = output.EffectiveMode info["offlineContractCompleted"] = output.OfflineContractCompleted info["changeUnitPrice"] = output.ChangeUnitPrice info["amount"] = output.Amount info["quantity"] = output.Quantity info["packageBeginTime"] = output.PackageBeginTime info["packageEndTime"] = output.PackageEndTime info["changeTime"] = output.ChangeTime } return gjson.New(info) } func (p *Processor) ValidatePrepareContext(ctx context.Context, tx gdb.TX, in *model.TSimCardChangeBatchPrepareParam, operator batchimport.OperatorContext, rows []*Row) (*PrepareContext, error) { if in == nil { in = &model.TSimCardChangeBatchPrepareParam{} } sourceBatchNo := strings.TrimSpace(in.SourceBatchNo) iccids := rowsToIccids(rows) cardsByIccid, err := p.repo.LockCardsByIccids(ctx, tx, iccids) if err != nil { return nil, gerror.Wrap(err, "validate change cards failed") } bindsByIccid, err := p.repo.LockCurrentActiveBindsByIccids(ctx, tx, iccids) if err != nil { return nil, gerror.Wrap(err, "validate change subscriptions failed") } packageCodes := make(map[string]struct{}) customerAgentIds := make(map[int64]struct{}) oldPackageCode := "" customerAgentId := int64(0) for _, row := range rows { if row == nil || row.Reason != "" { continue } card := cardsByIccid[row.Iccid] bind := bindsByIccid[row.Iccid] reason := validateChangeRow(sourceBatchNo, operator, row, card, bind) if reason != "" { row.AddReason(reason) continue } packageCode := strings.TrimSpace(bind.PackageCode) packageCodes[packageCode] = struct{}{} customerAgentIds[card.DeptId] = struct{}{} oldPackageCode = packageCode customerAgentId = card.DeptId row.Msisdn = strings.TrimSpace(card.Msisdn) row.Imsi = strings.TrimSpace(card.Imsi) row.OldPackageCode = packageCode row.OldSubscriptionNo = strings.TrimSpace(bind.SubscriptionNo) row.OldOrderSn = strings.TrimSpace(bind.OrderCode) row.OldEffectiveTime = bind.EffectiveTime row.OldExpireTime = bind.ExpectedExpireTime } if len(packageCodes) > 1 { markRowsWithoutReason(rows, "current batch contains multiple old packages and cannot be changed together") } if len(customerAgentIds) > 1 { markRowsWithoutReason(rows, "current batch contains multiple customers and cannot be changed together") } if hasInvalidRows(rows) { markRemainingRowsSkipped(rows) return nil, gerror.New("some cards do not meet change conditions, batch was not executed") } if oldPackageCode == "" || customerAgentId <= 0 { return nil, gerror.New("change prepare result is incomplete") } pkg, err := loadPackageByCode(ctx, tx, operator.TenantId, oldPackageCode) if err != nil { return nil, err } if pkg.ChangeablePackageCodes == nil || pkg.ChangeablePackageCodes.IsNil() || len(pkg.ChangeablePackageCodes.Interfaces()) == 0 { return nil, gerror.New("current package has no changeable packages configured") } customerName, err := loadDeptName(ctx, tx, operator.TenantId, customerAgentId) if err != nil { return nil, err } for _, row := range rows { if row != nil { row.OldPackageName = pkg.PackageName } } return &PrepareContext{ CustomerAgentId: customerAgentId, CustomerName: customerName, OldPackage: pkg, Quantity: len(iccids), }, nil } func validateChangeRow(sourceBatchNo string, operator batchimport.OperatorContext, row *Row, card *entity.TSimCardInfo, bind *entity.TSimCardBindPkg) string { if card == nil { return "ICCID does not exist as formal card" } if bind == nil { return "ICCID current subscription data is missing or invalid" } if card.DeptId != operator.DeptId && card.CreatedDept != operator.DeptId { return "ICCID does not belong to current department" } if strings.TrimSpace(card.CurrentPackageCode) == "" || card.PackageStatus == consts.PackageStatusNone { return "ICCID has no current package" } if strings.TrimSpace(card.CurrentPackageCode) != strings.TrimSpace(bind.PackageCode) { return "ICCID current package and subscription package do not match" } if sourceBatchNo != "" && strings.TrimSpace(bind.BatchNo) != sourceBatchNo { return "ICCID current subscription does not belong to sourceBatchNo" } if row != nil { if msisdn := strings.TrimSpace(row.Msisdn); msisdn != "" && msisdn != strings.TrimSpace(card.Msisdn) { return "MSISDN does not match card fact" } if imsi := strings.TrimSpace(row.Imsi); imsi != "" && imsi != strings.TrimSpace(card.Imsi) { return "IMSI does not match card fact" } } return "" } func loadPackageByCode(ctx context.Context, tx gdb.TX, tenantId, packageCode string) (*entity.TSimFlowPackageInfo, error) { pkg := &entity.TSimFlowPackageInfo{} columns := dao.TSimFlowPackageInfo.Columns() err := tx.Ctx(ctx).Model(dao.TSimFlowPackageInfo.Table()). Where(columns.TenantId, tenantId). Where(columns.PackageCode, strings.TrimSpace(packageCode)). Scan(pkg) if err != nil { return nil, gerror.Wrap(err, "load package failed") } if pkg.Id == 0 { return nil, gerror.New("package not found") } return pkg, nil } func loadDeptName(ctx context.Context, tx gdb.TX, tenantId string, deptId int64) (string, error) { dept := &entity.SysDept{} columns := dao.SysDept.Columns() err := tx.Ctx(ctx).Model(dao.SysDept.Table()). Where(columns.TenantId, tenantId). Where(columns.DeptId, deptId). Scan(dept) if err != nil { return "", gerror.Wrap(err, "load customer failed") } if dept.DeptId == 0 { return "", gerror.New("customer not found") } return strings.TrimSpace(dept.DeptName), nil } func hasInvalidRows(rows []*Row) bool { for _, row := range rows { if row != nil && row.Reason != "" { return true } } return false } func markRowsWithoutReason(rows []*Row, reason string) { for _, row := range rows { if row != nil && row.Reason == "" { row.AddReason(reason) } } } func markRemainingRowsSkipped(rows []*Row) { for _, row := range rows { if row != nil && row.Reason == "" { row.AddReason("same batch has rows failing change conditions, this row was skipped") } } } func validateRows(rows []*Row) error { firstByIccid := make(map[string]*Row, len(rows)) for _, row := range rows { if row == nil { continue } row.Iccid = strings.TrimSpace(row.Iccid) row.Msisdn = strings.TrimSpace(row.Msisdn) row.Imsi = strings.TrimSpace(row.Imsi) if row.Iccid == "" { row.AddReason("ICCID cannot be empty") continue } if first, ok := firstByIccid[row.Iccid]; ok { row.AddReason("ICCID duplicated") first.AddReason("ICCID duplicated") continue } firstByIccid[row.Iccid] = row } return nil }