package common import ( "context" "strconv" "strings" "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" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/batchmetadata" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model" "git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model/entity" ) // BuildRenewBatchNo 生成续费类批次号。 func (s *sCommon) BuildRenewBatchNo(ctx context.Context) (string, error) { return s.BuildBatchNo(ctx, consts.BatchNoPrefixRenew) } // SubmitRenew 创建卡续费批次并投递到 worker 异步执行。 func (s *sCommon) SubmitRenew(ctx context.Context, in *model.CommonRenewParam) (*model.CommonRenewRes, error) { return s.submitRenew(ctx, in, false, batchmetadata.Bulk) } func (s *sCommon) submitRenew(ctx context.Context, in *model.CommonRenewParam, waitForCompletion bool, executionMode int) (*model.CommonRenewRes, error) { if in == nil { return nil, gerror.New("续费参数不能为空") } duration := in.DurationYears if duration <= 0 { duration = in.Validity } if duration <= 0 { return nil, gerror.New("续费有效期必须大于0") } packageID := strings.TrimSpace(in.PackageId) cards, err := normalizeRenewCards(in.Cards) if err != nil { return nil, err } tenantID := strings.TrimSpace(contexts.GetTenantId(ctx)) deptID := contexts.GetAgentDeptId(ctx) userID := contexts.GetUserId(ctx) if tenantID == "" || deptID <= 0 { return nil, gerror.New("当前接口调用方信息不完整") } if userID <= 0 { userID = deptID } deptIDs, err := s.loadRenewAccessibleDeptIds(ctx, tenantID, deptID) if err != nil { return nil, err } identifiers := make([]cardIdentifier, 0, len(cards)) for _, card := range cards { identifiers = append(identifiers, cardIdentifier{Msisdn: card.Msisdn, Iccid: card.Iccid, Imsi: card.Imsi}) } cardMap, err := s.loadCardsByIdentifiers(ctx, tenantID, deptIDs, identifiers) if err != nil { return nil, err } for index := range cards { dbCard := cardMap[cardIdentifierKey(identifiers[index])] if !cardMatchesIdentifier(dbCard, identifiers[index]) { return nil, gerror.New("卡片不存在或无权限") } cards[index].Iccid = strings.TrimSpace(dbCard.Iccid) cards[index].Msisdn = strings.TrimSpace(dbCard.Msisdn) cards[index].Imsi = strings.TrimSpace(dbCard.Imsi) } iccids := make([]string, 0, len(cards)) for _, card := range cards { iccids = append(iccids, card.Iccid) } batchNo, err := s.BuildRenewBatchNo(ctx) if err != nil { return nil, err } now := gtime.Now() remark := strings.TrimSpace(in.Remark) batchInfo := gjson.New(map[string]any{ "durationValue": duration, "durationYears": duration, "durationMonths": duration, "packageId": packageID, "paymentMode": "balance", "remark": remark, "quantity": len(cards), "submittedFrom": "openapi", "submittedAt": now.Format("Y-m-d H:i:s"), "runtimeResolveRenewDetail": true, }) err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { main := &entity.BatchMain{ BatchNo: batchNo, BatchType: consts.BatchTypeRenew, BatchStatus: consts.BatchStatusNormal, BatchAsyncStatus: consts.BatchAsyncStatusPending, SourceType: consts.BatchSourceTypeBatchInput, DeptId: deptID, TotalCount: len(cards), ValidCount: len(cards), RunResult: consts.BatchRunResultPending, BatchInfo: batchInfo, Remark: gjson.New(map[string]any{"remark": remark}), TenantId: tenantID, CreatedDept: deptID, CreatedBy: userID, CreatedAt: now, UpdatedBy: userID, UpdatedAt: now, } if err := s.SaveBatchMain(ctx, tx, main, executionMode); err != nil { return err } details := make([]*entity.BatchDetail, 0, len(cards)) for i, inputCard := range cards { dbCard := cardMap[cardIdentifierKey(cardIdentifier{Msisdn: inputCard.Msisdn, Iccid: inputCard.Iccid, Imsi: inputCard.Imsi})] snapshot := renewDetailSnapshot(inputCard, dbCard) details = append(details, &entity.BatchDetail{ BatchNo: batchNo, BatchType: consts.BatchTypeRenew, DetailStatus: consts.BatchDetailStatusPending, BlankOutStatus: consts.BlankOutStatusNo, RowNo: i + 1, ItemId: inputCard.Iccid, Snapshot: snapshot, TenantId: tenantID, CreatedDept: deptID, CreatedBy: userID, CreatedAt: now, UpdatedBy: userID, UpdatedAt: now, }) } return s.SaveBatchDetails(ctx, tx, details) }) if err != nil { return nil, err } message := model.CommonBatchExecuteMessage{BatchNo: batchNo, BatchType: consts.BatchTypeRenew} if err := s.Publish(ctx, consts.QueueBatchExecute, message); err != nil { return nil, gerror.Wrap(err, "投递续费异步消息失败") } if waitForCompletion { if _, err := s.waitBatchCompletion(ctx, tenantID, deptID, batchNo); err != nil { return nil, err } } return &model.CommonRenewRes{ BatchNo: batchNo, BatchType: consts.BatchTypeRenew, Total: len(cards), }, nil } func buildRenewSubmitRemark(ctx context.Context) string { agent := contexts.GetAgentUser(ctx) if agent == nil { return "OpenAPI续费调用" } deptName := strings.TrimSpace(agent.DeptName) deptKey := strings.TrimSpace(agent.DeptKey) switch { case deptName != "" && deptKey != "": return "OpenAPI续费调用方:" + deptName + "(" + deptKey + ")" case deptName != "": return "OpenAPI续费调用方:" + deptName case deptKey != "": return "OpenAPI续费调用方:" + deptKey default: return "OpenAPI续费调用" } } func normalizeRenewCards(input []model.CommonRenewCard) ([]model.CommonRenewCard, error) { if len(input) == 0 { return nil, gerror.New("续费卡不能为空") } seen := make(map[string]struct{}, len(input)) cards := make([]model.CommonRenewCard, 0, len(input)) for _, item := range input { item.Iccid = strings.TrimSpace(item.Iccid) item.Msisdn = strings.TrimSpace(item.Msisdn) item.Imsi = strings.TrimSpace(item.Imsi) item.PackageId = strings.TrimSpace(item.PackageId) identifier := cardIdentifier{Msisdn: item.Msisdn, Iccid: item.Iccid, Imsi: item.Imsi} if err := validateCardIdentifier(identifier); err != nil { return nil, err } key := cardIdentifierKey(identifier) if _, ok := seen[key]; ok { return nil, gerror.Newf("卡片标识重复:%s", key) } seen[key] = struct{}{} cards = append(cards, item) } return cards, nil } func (s *sCommon) loadRenewAccessibleDeptIds(ctx context.Context, tenantID string, deptID int64) ([]int64, error) { deptIDs := []int64{deptID} columns := dao.SysDept.Columns() array, err := dao.SysDept.Ctx(ctx). Fields(columns.DeptId). Where(columns.TenantId, tenantID). WhereNull(columns.DeletedAt). Where("dept_id = ? OR ancestors LIKE ?", deptID, "%,"+strconv.FormatInt(deptID, 10)+",%"). Array() if err != nil { return nil, gerror.Wrap(err, "查询续费数据权限失败") } if len(array) == 0 { return deptIDs, nil } seen := map[int64]struct{}{} deptIDs = deptIDs[:0] for _, item := range array { id := item.Int64() if id <= 0 { continue } if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} deptIDs = append(deptIDs, id) } if len(deptIDs) == 0 { deptIDs = append(deptIDs, deptID) } return deptIDs, nil } func (s *sCommon) loadRenewCards(ctx context.Context, tenantID string, deptIDs []int64, iccids []string) (map[string]*entity.TSimCardInfo, error) { cardMap := make(map[string]*entity.TSimCardInfo, len(iccids)) if len(iccids) == 0 { return cardMap, nil } columns := dao.TSimCardInfo.Columns() var rows []*entity.TSimCardInfo err := dao.TSimCardInfo.Ctx(ctx). Where(columns.TenantId, tenantID). WhereIn(columns.DeptId, deptIDs). WhereIn(columns.Iccid, iccids). Scan(&rows) if err != nil { return nil, gerror.Wrap(err, "查询续费卡失败") } for _, row := range rows { if row == nil { continue } row.Iccid = strings.TrimSpace(row.Iccid) if row.Iccid != "" { cardMap[row.Iccid] = row } } return cardMap, nil } func renewDetailSnapshot(input model.CommonRenewCard, card *entity.TSimCardInfo) *gjson.Json { data := map[string]any{ "iccid": input.Iccid, "targetCode": input.Iccid, } if input.Msisdn != "" { data["msisdn"] = input.Msisdn } if input.Imsi != "" { data["imsi"] = input.Imsi } if input.PackageId != "" { data["packageId"] = input.PackageId data["packageCode"] = input.PackageId } if card != nil { if input.Msisdn == "" { data["msisdn"] = strings.TrimSpace(card.Msisdn) } if input.Imsi == "" { data["imsi"] = strings.TrimSpace(card.Imsi) } if input.PackageId == "" { data["packageCode"] = strings.TrimSpace(card.CurrentPackageCode) } data["currentPackageCode"] = strings.TrimSpace(card.CurrentPackageCode) data["outboundBatchNo"] = strings.TrimSpace(card.OutboundBatchNo) } return gjson.New(data) } func (s *sCommon) SubmitSingleRenew(ctx context.Context, card model.CommonRenewCard, appID, packageID, remark string, validity int) (*model.CommonSingleRenewRes, error) { card.PackageId = packageID batch, err := s.submitRenew(ctx, &model.CommonRenewParam{ AppId: appID, PackageId: packageID, Validity: validity, DurationYears: validity, Remark: remark, Cards: []model.CommonRenewCard{card}, }, true, batchmetadata.Single) if err != nil { return nil, err } return s.loadSingleRenewResult(ctx, batch.BatchNo, packageID, validity) } func (s *sCommon) SubmitBatchRenew(ctx context.Context, appID string, cards []model.CommonRenewCard, iccids []string, packageID, remark string, validity int) (*model.CommonRenewRes, error) { if len(cards) == 0 { cards = make([]model.CommonRenewCard, 0, len(iccids)) for _, iccid := range iccids { cards = append(cards, model.CommonRenewCard{Iccid: iccid, PackageId: packageID}) } } for index := range cards { if cards[index].PackageId == "" { cards[index].PackageId = packageID } } return s.SubmitRenew(ctx, &model.CommonRenewParam{ AppId: appID, PackageId: packageID, Validity: validity, DurationYears: validity, Remark: remark, Cards: cards, }) }