package app import ( "context" "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/os/gtime" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/allocationunits" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchimport" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchmetadata" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" ) func appAllocateParentInfo(batch *appAllocateBatchContext, remark string) *gjson.Json { return gjson.New(map[string]any{ "operation": "sim_card_allocate", "targetBatchType": consts.BatchTypeAllocate, "jobRole": "allocate_parent", "autoSplit": true, "splitCompleted": true, "chunkCount": 1, "chunkSize": 100, "parsedCount": len(batch.Cards), "targetDeptId": batch.Target.DeptId, "targetDeptName": batch.Target.DeptName, "cards": appAllocateCardSnapshotList(batch), "remark": remark, "resultFile": map[string]any{"status": "pending"}, }) } func (s *sAppCard) queueAppAllocateBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, prepare *entity.BatchMain, batch *appAllocateBatchContext, remark string) error { units, err := allocationunits.Snapshot(ctx, user.TenantId, user.DeptId, batch.Target.DeptId, tx) if err != nil { return err } childNo, err := batchimport.NextBatchNoContext(ctx, consts.BatchNoPrefixAllocate) if err != nil { return err } mode := batchmetadata.Bulk if prepare.ExecutionMode == batchmetadata.Single { mode = batchmetadata.Single } parentInfo := allocationunits.Merge(appAllocateParentInfo(batch, remark), units) if prepare.BatchInfo != nil { for _, key := range []string{"requestId", "requestHash"} { if value := prepare.BatchInfo.Get(key).String(); value != "" { parentInfo.Set(key, value) } } } childInfo := gjson.New(map[string]any{ "operation": "sim_card_allocate", "jobRole": "allocate_chunk", "autoSplit": true, "targetBatchType": consts.BatchTypeAllocate, "targetDeptId": batch.Target.DeptId, "parentBatchNo": prepare.BatchNo, "chunkNo": 1, "remark": remark, }) if _, err := dao.BatchMain.Ctx(ctx).TX(tx).Data(do.BatchMain{ BatchNo: childNo, BatchType: consts.BatchTypeAllocate, BatchStatus: consts.BatchAsyncStatusPending, BatchAsyncStatus: consts.BatchAsyncStatusPending, SourceType: consts.BatchSourceTypeBatchSelect, SourceBatchNo: prepare.BatchNo, SourceChannel: batchmetadata.MiniProgram, ExecutionMode: mode, TargetDeptId: batch.Target.DeptId, DeptId: user.DeptId, TotalCount: len(batch.Cards), ValidCount: 0, SuccessCount: 0, FailCount: 0, RunResult: consts.BatchRunResultPending, BatchInfo: allocationunits.Merge(childInfo, units), TenantId: user.TenantId, CreatedDept: user.DeptId, CreatedBy: user.UserId, UpdatedBy: user.UserId, }).Insert(); err != nil { return gerror.Wrap(err, "创建划拨执行子批次失败") } details := make([]do.BatchDetail, 0, len(batch.Cards)) for index, item := range batch.Cards { details = append(details, do.BatchDetail{ BatchNo: childNo, BatchType: consts.BatchTypeAllocate, DetailStatus: consts.BatchDetailStatusPending, BlankOutStatus: consts.BlankOutStatusNo, RowNo: index + 1, ItemId: item.Card.Iccid, Snapshot: gjson.New(map[string]any{ "iccid": item.Card.Iccid, "msisdn": item.Card.Msisdn, "imsi": item.Card.Imsi, "targetDeptId": batch.Target.DeptId, "targetDeptName": units.Get("targetDeptName").String(), "targetDeptKey": units.Get("targetDeptKey").String(), "sourceDeptId": item.Card.DeptId, "sourceDeptName": units.Get("sourceDeptName").String(), "sourceDeptKey": units.Get("sourceDeptKey").String(), "remark": remark, }), TenantId: user.TenantId, CreatedDept: user.DeptId, CreatedBy: user.UserId, UpdatedBy: user.UserId, }) } if _, err := dao.BatchDetail.Ctx(ctx).TX(tx).Data(details).Insert(); err != nil { return gerror.Wrap(err, "创建划拨执行明细失败") } if _, err := dao.BatchDetail.Ctx(ctx).TX(tx). Where(dao.BatchDetail.Columns().BatchNo, prepare.BatchNo). Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypePrepare). Where(dao.BatchDetail.Columns().TenantId, user.TenantId). Where(dao.BatchDetail.Columns().CreatedBy, user.UserId).Delete(); err != nil { return gerror.Wrap(err, "清理划拨预处理明细失败") } _, err = dao.BatchMain.Ctx(ctx).TX(tx).Where(dao.BatchMain.Columns().Id, prepare.Id).Data(do.BatchMain{ BatchType: consts.BatchTypeAllocate, SourceBatchNo: "", ResultOssId: 0, BatchStatus: consts.BatchAsyncStatusPending, BatchAsyncStatus: consts.BatchAsyncStatusPending, RunResult: consts.BatchRunResultPending, ValidCount: 0, SuccessCount: 0, FailCount: 0, FailReason: "", SourceChannel: batchmetadata.MiniProgram, ExecutionMode: mode, BatchInfo: parentInfo, UpdatedBy: user.UserId, StartTime: new(gtime.Time), EndTime: new(gtime.Time), }).Update() if err != nil { return gerror.Wrap(err, "创建划拨父批次失败") } return nil } func (s *sAppCard) createAppAllocatePending(ctx context.Context, user *entity.SysUser, param *model.AppCardAllocateBatchSubmitParam) (*model.AppCardAllocateBatchModel, error) { if !appWorkerBatchAsyncEnabled(ctx) { return nil, gerror.New("划拨执行服务未启用,请联系管理员") } if user.DeptId <= 0 || s.isTerminalUser(ctx, user) { return nil, gerror.New("当前账号不能使用卡划拨功能") } values := appNormalizeAllocateCardValues(param.CardValues) mode := appAllocateExecutionMode(param.ExecutionMode) if len(values) == 0 || len(values) > 100 || (mode == batchmetadata.Single && len(values) != 1) { return nil, gerror.New("请选择1至100张卡,单卡划拨只能选择一张卡") } target, err := s.loadAllocateTarget(ctx, user, param.TargetDeptId) if err != nil { return nil, err } batch := &appAllocateBatchContext{Target: target, TotalCount: len(values)} for index, value := range values { card, err := s.findVisibleCard(ctx, user, value) if err != nil { return nil, err } if card == nil || card.DeptId != user.DeptId || strings.TrimSpace(card.Iccid) == "" { return nil, gerror.Newf("第%d张卡不在当前企业可划拨范围内", index+1) } batch.Cards = append(batch.Cards, &appAllocateCardContext{Card: card, ItemId: card.Iccid, RowNo: index + 1, SourceDeptId: card.DeptId}) } parentNo, err := batchimport.NextBatchNoContext(ctx, consts.BatchNoPrefixAllocate) if err != nil { return nil, err } parent, err := s.persistAppAllocatePending(ctx, user, parentNo, mode, batch, strings.TrimSpace(param.Remark)) if err != nil { return nil, err } response := appAllocateQueuedResponse(parent) if err := s.publishAppAllocateBatch(ctx, user, parentNo); err != nil { response.FailReason = "任务已受理,投递尚未确认,请保留批次号并稍后刷新" } return response, nil } func (s *sAppCard) persistAppAllocatePending(ctx context.Context, user *entity.SysUser, parentNo string, mode int, batch *appAllocateBatchContext, remark string) (*entity.BatchMain, error) { parent := &entity.BatchMain{ BatchNo: parentNo, BatchType: consts.BatchTypeAllocate, ExecutionMode: uint(mode), BatchStatus: consts.BatchAsyncStatusPending, BatchAsyncStatus: consts.BatchAsyncStatusPending, TotalCount: len(batch.Cards), TargetDeptId: batch.Target.DeptId, BatchInfo: appAllocateParentInfo(batch, remark), } err := dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { id, err := dao.BatchMain.Ctx(ctx).TX(tx).Data(do.BatchMain{ BatchNo: parentNo, BatchType: consts.BatchTypeAllocate, SourceBatchNo: "", BatchStatus: consts.BatchAsyncStatusPending, BatchAsyncStatus: consts.BatchAsyncStatusPending, RunResult: consts.BatchRunResultPending, SourceType: consts.BatchSourceTypeBatchSelect, SourceChannel: batchmetadata.MiniProgram, ExecutionMode: mode, TotalCount: len(batch.Cards), TargetDeptId: batch.Target.DeptId, DeptId: user.DeptId, TenantId: user.TenantId, CreatedDept: user.DeptId, CreatedBy: user.UserId, UpdatedBy: user.UserId, }).InsertAndGetId() if err != nil { return gerror.Wrap(err, "创建划拨待处理批次失败") } parent.Id = id return s.queueAppAllocateBatch(ctx, tx, user, parent, batch, remark) }) if err != nil { return nil, err } return parent, nil } func (s *sAppCard) publishAppAllocateBatch(ctx context.Context, user *entity.SysUser, batchNo string) error { var children []*entity.BatchMain columns := dao.BatchMain.Columns() if err := dao.BatchMain.Ctx(ctx).Where(columns.SourceBatchNo, batchNo). Where(columns.BatchType, consts.BatchTypeAllocate).Where(columns.TenantId, user.TenantId). Where(columns.CreatedBy, user.UserId).Scan(&children); err != nil { return err } for _, child := range children { if child.BatchAsyncStatus == consts.BatchAsyncStatusPending || child.BatchAsyncStatus == consts.BatchAsyncStatusProcessing { if err := s.publishWorkerBatchExecute(ctx, child.BatchNo, consts.BatchTypeAllocate, true); err != nil { return err } } } return s.publishWorkerBatchExecute(ctx, batchNo, consts.BatchTypeAllocate, true) } func appAllocateQueuedResponse(batch *entity.BatchMain) *model.AppCardAllocateBatchModel { status := appLifecycleBatchStatus(batch.BatchStatus, batch.BatchAsyncStatus) target := &model.AppCardAllocateTargetModel{DeptId: batch.TargetDeptId} if batch.BatchInfo != nil { target.DeptName = strings.TrimSpace(batch.BatchInfo.Get("targetDeptName").String()) } return &model.AppCardAllocateBatchModel{ BatchNo: batch.BatchNo, BatchType: consts.BatchTypeAllocate, Quantity: batch.TotalCount, TotalCount: batch.TotalCount, ValidCount: batch.ValidCount, FailCount: batch.FailCount, FailReason: appLifecycleFailReasonForResponse(status, batch.FailReason), Status: status, Target: target, Cards: []*model.AppCardModel{}, Details: []*model.AppCardAllocateBatchDetailModel{}, } } func appAllocateExecutionMode(requested int) int { if requested == batchmetadata.Single { return batchmetadata.Single } return batchmetadata.Bulk } func appValidateAllocateRetry(batch *entity.BatchMain, param *model.AppCardAllocateBatchSubmitParam) error { if param.ExecutionMode > 0 && uint(param.ExecutionMode) != batch.ExecutionMode { return gerror.New("提交规模与原划拨批次不一致") } if strings.TrimSpace(param.Remark) != "" && strings.TrimSpace(param.Remark) != batch.BatchInfo.Get("remark").String() { return gerror.New("提交备注与原划拨批次不一致") } if strings.TrimSpace(param.RequestId) != "" && strings.TrimSpace(param.RequestId) != batch.BatchInfo.Get("requestId").String() { return gerror.New("请求标识与原划拨批次不一致") } if param.TargetDeptId > 0 && param.TargetDeptId != batch.TargetDeptId { return gerror.New("提交目标客户与原划拨批次不一致") } if len(param.CardValues) > 0 { var expected []string for _, card := range batch.BatchInfo.Get("cards").Array() { expected = append(expected, gjson.New(card).Get("iccid").String()) } if !appSameStringSet(appNormalizeAllocateCardValues(param.CardValues), expected) { return gerror.New("提交卡片与原划拨批次不一致") } } return nil }