package app import ( "context" "fmt" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchmetadata" "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/batchimport" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/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/os/gtime" ) type appAllocateBatchContext struct { Target *entity.SysDept Cards []*appAllocateCardContext TotalCount int ValidCount int FailCount int FailReason string } type appAllocateCardContext struct { Card *appCardQueryRow ItemId string RowNo int SourceDeptId int64 ErrorReason string } // AllocateTargets 查询当前企业用户可划拨的目标客户。 func (s *sAppCard) AllocateTargets(ctx context.Context, param *model.AppCardAllocateTargetListParam) (items []*model.AppCardAllocateTargetModel, total int, err error) { if param == nil { param = &model.AppCardAllocateTargetListParam{} } user, err := appCurrentUser(ctx) if err != nil { return nil, 0, err } if s.isTerminalUser(ctx, user) { return nil, 0, gerror.New("终端用户不能使用卡划拨功能") } if user.DeptId <= 0 { return nil, 0, gerror.New("当前账号未绑定企业,不能使用卡划拨功能") } if param.Page <= 0 { param.Page = 1 } if param.PageSize <= 0 { param.PageSize = 50 } if param.PageSize > 1000 { param.PageSize = 1000 } var rows []*entity.SysDept rows, total, err = s.loadAllocateTargetDepts(ctx, user, strings.TrimSpace(param.Keyword), param.Page, param.PageSize) if err != nil { return nil, 0, gerror.Wrap(err, "查询可划拨目标客户失败") } items = make([]*model.AppCardAllocateTargetModel, 0, len(rows)) for _, row := range rows { items = append(items, appAllocateTargetModel(row, user.DeptId)) } return items, total, nil } func (s *sAppCard) loadAllocateTargetDepts(ctx context.Context, user *entity.SysUser, keyword string, page int, pageSize int) ([]*entity.SysDept, int, error) { if user == nil || user.DeptId <= 0 { return []*entity.SysDept{}, 0, nil } if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = 50 } var children []*entity.SysDept err := dao.SysDept.Ctx(ctx). Where(dao.SysDept.Columns().TenantId, user.TenantId). Where(dao.SysDept.Columns().ParentId, user.DeptId). Where(dao.SysDept.Columns().Status, "0"). WhereNull(dao.SysDept.Columns().DeletedAt). OrderAsc(dao.SysDept.Columns().OrderNum). OrderAsc(dao.SysDept.Columns().DeptId). Scan(&children) if err != nil { return nil, 0, err } rows := make([]*entity.SysDept, 0, len(children)) for _, child := range children { if child == nil || child.DeptId <= 0 || !appAllocateDeptMatchesKeyword(child, keyword) { continue } rows = append(rows, child) } rows, err = appFilterDeptsByEnterpriseDataScope(ctx, user, rows) if err != nil { return nil, 0, err } total := len(rows) start := (page - 1) * pageSize if start >= total { return []*entity.SysDept{}, total, nil } end := start + pageSize if end > total { end = total } return rows[start:end], total, nil } func appAllocateDeptMatchesKeyword(dept *entity.SysDept, keyword string) bool { keyword = strings.TrimSpace(keyword) if keyword == "" { return true } if dept == nil { return false } return strings.Contains(strings.TrimSpace(dept.DeptName), keyword) || strings.Contains(strings.TrimSpace(dept.DeptKey), keyword) || strings.Contains(fmt.Sprintf("%d", dept.DeptId), keyword) } // AllocateBatchPrepare 校验待划拨卡片,并写入划拨预处理批次。 func (s *sAppCard) AllocateBatchPrepare(ctx context.Context, param *model.AppCardAllocateBatchPrepareParam) (res *model.AppCardAllocateBatchModel, err error) { if param == nil { param = &model.AppCardAllocateBatchPrepareParam{} } user, err := appCurrentUser(ctx) if err != nil { return nil, err } batch, err := s.buildAllocateBatchContext(ctx, user, param.CardValues, param.TargetDeptId) if err != nil { return nil, err } if param.ExecutionMode == batchmetadata.Single && len(batch.Cards) != 1 { return nil, gerror.New("单卡划拨只能选择一张卡") } ctx = appContextWithUser(ctx, user) processor := newAppAllocateBatchProcessor(batch) rows := appAllocateBatchRows(batch) req := batchimport.SubmitRequest{ BizParams: param, SourceType: consts.BatchSourceTypeBatchSelect, SourceChannel: batchmetadata.MiniProgram, ExecutionMode: appAllocateExecutionMode(param.ExecutionMode), BatchType: consts.BatchTypePrepare, FailureBatchType: consts.BatchTypeAllocate, SkipExecute: true, TargetDeptId: batch.Target.DeptId, Operator: batchimport.OperatorContext{ TenantId: user.TenantId, DeptId: user.DeptId, UserId: user.UserId, }, } result, err := batchimport.RunRows(ctx, req, processor, rows, "miniapp-allocate-prepare") if err != nil { return nil, gerror.Wrap(err, "写入划拨预处理批次失败") } status := "prepared" batchNo := "" if result != nil { batchNo = result.BatchNo } if batch.FailCount > 0 || result == nil || result.ImportStatus != consts.ImportStatusSuccess { status = "failed" } return s.buildAllocateBatchModel(ctx, user, batch, batchNo, "", consts.BatchTypePrepare, status), nil } // AllocateBatchSubmit 确认预处理批次并提交 worker 执行。 func (s *sAppCard) AllocateBatchSubmit(ctx context.Context, param *model.AppCardAllocateBatchSubmitParam) (res *model.AppCardAllocateBatchModel, err error) { if err := appEnsureFeatureAccess(ctx, "card:allocate"); err != nil { return nil, err } if param == nil { param = &model.AppCardAllocateBatchSubmitParam{} } user, err := appCurrentUser(ctx) if err != nil { return nil, err } if s.isTerminalUser(ctx, user) { return nil, gerror.New("终端用户不能使用卡划拨功能") } if strings.TrimSpace(param.BatchNo) == "" { if strings.TrimSpace(param.RequestId) != "" { return s.submitAppAllocateRequest(ctx, user, param) } return s.createAppAllocatePending(ctx, user, param) } if !appWorkerBatchAsyncEnabled(ctx) { return nil, gerror.New("划拨执行服务未启用,请联系管理员") } var batch *appAllocateBatchContext var sourceBatchNo string batchNo := strings.TrimSpace(param.BatchNo) err = dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { prepareBatch, err := s.lockAppAllocatePrepareBatch(ctx, tx, user, batchNo) if err != nil { return err } if prepareBatch.CreatedDept != user.DeptId { return gerror.New("当前机构与划拨批次创建机构不一致") } if prepareBatch.BatchType == consts.BatchTypeAllocate { if prepareBatch.BatchInfo.Get("jobRole").String() != "allocate_parent" { return gerror.New("旧划拨批次不能重新提交,请查看执行日志") } if err := appValidateAllocateRetry(prepareBatch, param); err != nil { return err } res = appAllocateQueuedResponse(prepareBatch) return nil } sourceBatchNo = appAllocateSourceBatchNoFromBatch(prepareBatch) targetDeptId := prepareBatch.TargetDeptId if param.TargetDeptId > 0 && param.TargetDeptId != targetDeptId { return gerror.New("提交目标客户与预处理批次不一致,请重新校验") } target, err := s.loadAllocateTarget(ctx, user, targetDeptId) if err != nil { return err } details, err := s.loadAppAllocatePrepareBatchDetails(ctx, tx, prepareBatch.BatchNo) if err != nil { return err } if len(details) == 0 { return gerror.New("划拨预处理批次没有可确认的卡") } cardValues := make([]string, 0, len(details)) for _, detail := range details { cardValues = append(cardValues, strings.TrimSpace(detail.ItemId)) } if len(param.CardValues) > 0 && !appSameStringSet(appNormalizeAllocateCardValues(param.CardValues), cardValues) { return gerror.New("提交卡片与预处理批次不一致,请重新校验") } batch = &appAllocateBatchContext{ Target: target, Cards: make([]*appAllocateCardContext, 0, len(details)), } for _, detail := range details { card, err := s.lockAllocateCardForSubmit(ctx, tx, user, detail, target.DeptId) if err != nil { return err } sourceDeptId := card.DeptId if detail.Snapshot != nil && detail.Snapshot.Get("sourceDeptId").Int64() > 0 { sourceDeptId = detail.Snapshot.Get("sourceDeptId").Int64() } batch.Cards = append(batch.Cards, &appAllocateCardContext{ Card: card, ItemId: card.Iccid, RowNo: detail.RowNo, SourceDeptId: sourceDeptId, }) } batch.refreshCounts() if err = s.queueAppAllocateBatch(ctx, tx, user, prepareBatch, batch, strings.TrimSpace(param.Remark)); err != nil { return err } return nil }) if err != nil { return nil, err } if res == nil { if batch == nil { return nil, gerror.New("划拨提交结果不完整") } res = s.buildAllocateBatchModel(ctx, user, batch, batchNo, sourceBatchNo, consts.BatchTypeAllocate, "pending") } if res.Status == "pending" || res.Status == "processing" { if err = s.publishAppAllocateBatch(ctx, user, batchNo); err != nil { res.FailReason = "批次已创建,投递失败,请保留批次号并重试提交" } } return res, nil } func (s *sAppCard) buildAllocateBatchContext(ctx context.Context, user *entity.SysUser, cardValues []string, targetDeptId int64) (*appAllocateBatchContext, error) { if s.isTerminalUser(ctx, user) { return nil, gerror.New("终端用户不能使用卡划拨功能") } if user.DeptId <= 0 { return nil, gerror.New("当前账号未绑定企业,不能使用卡划拨功能") } values := appNormalizeAllocateCardValues(cardValues) if len(values) == 0 { return nil, gerror.New("请选择需要划拨的卡") } if len(values) > 100 { return nil, gerror.New("单次批量划拨最多支持100张卡") } target, err := s.loadAllocateTarget(ctx, user, targetDeptId) if err != nil { return nil, err } batch := &appAllocateBatchContext{ Target: target, Cards: make([]*appAllocateCardContext, 0, len(values)), TotalCount: len(values), } seenIccid := make(map[string]struct{}, len(values)) for index, value := range values { item := &appAllocateCardContext{ ItemId: strings.TrimSpace(value), RowNo: index + 2, } card, err := s.findVisibleCard(ctx, user, value) if err != nil { return nil, err } if card == nil { item.ErrorReason = fmt.Sprintf("第%d张卡不存在或无权划拨:%s", index+1, value) batch.Cards = append(batch.Cards, item) continue } item.Card = card item.ItemId = strings.TrimSpace(card.Iccid) item.SourceDeptId = card.DeptId iccid := strings.TrimSpace(card.Iccid) if _, exists := seenIccid[iccid]; exists { item.ErrorReason = fmt.Sprintf("批量划拨卡重复:%s", iccid) batch.Cards = append(batch.Cards, item) continue } seenIccid[iccid] = struct{}{} if err = s.ensureAllocateCardOwnedByOperator(ctx, nil, user, card); err != nil { item.ErrorReason = err.Error() } else if err = s.validateAllocateCardBusinessRules(ctx, nil, card); err != nil { item.ErrorReason = err.Error() } else if card.DeptId == target.DeptId { item.ErrorReason = fmt.Sprintf("卡%s已属于目标客户,无需重复划拨", appFirstNotEmpty(card.Iccid, value)) } batch.Cards = append(batch.Cards, item) } batch.refreshCounts() return batch, nil } func (batch *appAllocateBatchContext) refreshCounts() { if batch == nil { return } batch.TotalCount = len(batch.Cards) batch.ValidCount = 0 batch.FailCount = 0 batch.FailReason = "" for _, item := range batch.Cards { if item == nil { batch.FailCount++ continue } if strings.TrimSpace(item.ErrorReason) != "" { batch.FailCount++ continue } batch.ValidCount++ } if batch.FailCount > 0 { batch.FailReason = "存在校验失败数据,本次未写入" } } func (item *appAllocateCardContext) allocateItemId() string { if item == nil { return "" } if item.Card != nil && strings.TrimSpace(item.Card.Iccid) != "" { return strings.TrimSpace(item.Card.Iccid) } return strings.TrimSpace(item.ItemId) } func (s *sAppCard) lockAppAllocatePrepareBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, batchNo string) (*entity.BatchMain, error) { batchNo = strings.TrimSpace(batchNo) if batchNo == "" { return nil, gerror.New("预处理批次号不能为空") } var batch *entity.BatchMain columns := dao.BatchMain.Columns() err := dao.BatchMain.Ctx(ctx).TX(tx). Where(columns.BatchNo, batchNo). WhereIn(columns.BatchType, []int{consts.BatchTypePrepare, consts.BatchTypeAllocate}). Where(columns.TenantId, user.TenantId). Where(columns.CreatedBy, user.UserId). LockUpdate(). Limit(1). Scan(&batch) if err != nil { return nil, gerror.Wrap(err, "查询划拨预处理批次失败") } if batch == nil || batch.Id == 0 { return nil, gerror.New("划拨预处理批次不存在或无权确认") } if batch.BatchInfo == nil || batch.BatchInfo.Get("targetBatchType").Int() != consts.BatchTypeAllocate { return nil, gerror.New("批次不是卡划拨预处理批次") } if batch.BatchType == consts.BatchTypeAllocate { return batch, nil } if batch.RunResult != consts.BatchRunResultSuccess || batch.FailCount > 0 { return nil, gerror.New("划拨预处理批次存在校验失败数据,请重新校验后再提交") } return batch, nil } func (s *sAppCard) loadAppAllocatePrepareBatchDetails(ctx context.Context, tx gdb.TX, batchNo string) ([]*entity.BatchDetail, error) { var details []*entity.BatchDetail columns := dao.BatchDetail.Columns() err := dao.BatchDetail.Ctx(ctx).TX(tx). Where(columns.BatchNo, strings.TrimSpace(batchNo)). Where(columns.BatchType, consts.BatchTypePrepare). Where(columns.DetailStatus, consts.BatchDetailStatusSuccess). Where(columns.BlankOutStatus, consts.BlankOutStatusNo). OrderAsc(columns.RowNo). Scan(&details) if err != nil { return nil, gerror.Wrap(err, "查询划拨预处理明细失败") } return details, nil } func (s *sAppCard) lockAllocateCardForSubmit(ctx context.Context, tx gdb.TX, user *entity.SysUser, detail *entity.BatchDetail, targetDeptId int64) (*appCardQueryRow, error) { if detail == nil || strings.TrimSpace(detail.ItemId) == "" { return nil, gerror.New("划拨批次明细缺少卡号") } expectedSourceDeptId := int64(0) if detail.Snapshot != nil { expectedSourceDeptId = detail.Snapshot.Get("sourceDeptId").Int64() } var card *appCardQueryRow err := s.cardQueryBase(ctx, false).TX(tx). Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().TenantId, user.TenantId). Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().Iccid, strings.TrimSpace(detail.ItemId)). LockUpdate(). Limit(1). Scan(&card) if err != nil { return nil, gerror.Wrapf(err, "锁定划拨卡%s失败", detail.ItemId) } if card == nil || card.Id == 0 { return nil, gerror.Newf("卡%s不存在,不能划拨", detail.ItemId) } if err = s.attachCardChannelInfo(ctx, card); err != nil { return nil, err } if expectedSourceDeptId > 0 && card.DeptId != expectedSourceDeptId { return nil, gerror.Newf("卡%s归属已变化,请重新校验", card.Iccid) } if err = s.ensureAllocateCardVisible(ctx, tx, user, card); err != nil { return nil, err } if err = s.ensureAllocateCardOwnedByOperator(ctx, tx, user, card); err != nil { return nil, err } if err = s.validateAllocateCardBusinessRules(ctx, tx, card); err != nil { return nil, err } if card.DeptId == targetDeptId { return nil, gerror.Newf("卡%s已属于目标客户,无需重复划拨", card.Iccid) } return card, nil } func (s *sAppCard) validateAllocateCardBusinessRules(ctx context.Context, tx gdb.TX, card *appCardQueryRow) error { if card == nil || strings.TrimSpace(card.Iccid) == "" { return gerror.New("划拨卡信息不完整") } cardLabel := appFirstNotEmpty(card.Iccid, card.Msisdn, card.Imsi, card.Imei) if card.BossCardStatus == 4 { return gerror.Newf("卡%s当前为停机状态,不能划拨", cardLabel) } if card.BossCardStatus == 9 { return gerror.Newf("卡%s已销户,不能划拨", cardLabel) } if card.PackageStatus == consts.PackageStatusExpired || (card.FlowPkgEndAt != nil && !card.FlowPkgEndAt.IsZero() && card.FlowPkgEndAt.Before(gtime.Now())) { return gerror.Newf("卡%s套餐已过期,不能划拨", cardLabel) } return nil } func (s *sAppCard) loadAllocateTarget(ctx context.Context, user *entity.SysUser, targetDeptId int64) (*entity.SysDept, error) { if targetDeptId <= 0 { return nil, gerror.New("请选择划拨目标客户") } target, err := s.loadDeptById(ctx, user.TenantId, targetDeptId) if err != nil { return nil, err } if target == nil || target.DeptId == 0 { return nil, gerror.New("划拨目标客户不存在") } if target.Status != "0" { return nil, gerror.New("划拨目标客户未启用") } if target.DeptId == user.DeptId { return nil, gerror.New("划拨目标不能选择当前企业") } if target.ParentId != user.DeptId { return nil, gerror.New("划拨目标客户不在当前企业直属下级范围内") } visible, err := appEnterpriseDeptVisibleByDataScope(ctx, user, target.DeptId) if err != nil { return nil, err } if !visible { return nil, gerror.New("划拨目标客户不在当前角色可操作范围内") } return target, nil } func (s *sAppCard) ensureAllocateCardVisible(ctx context.Context, tx gdb.TX, user *entity.SysUser, card *appCardQueryRow) error { if card == nil || card.Id <= 0 || strings.TrimSpace(card.Iccid) == "" { return gerror.New("划拨卡信息不完整") } var visible *appCardQueryRow if err := s.visibleCardModel(ctx, user).TX(tx). Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().Id, card.Id). Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().Iccid, card.Iccid). Limit(1). Scan(&visible); err != nil { return gerror.Wrapf(err, "校验卡%s可操作范围失败", card.Iccid) } if visible == nil || visible.Id == 0 { return gerror.Newf("卡%s不在当前企业可操作范围内", card.Iccid) } return nil } func (s *sAppCard) ensureAllocateCardOwnedByOperator(ctx context.Context, tx gdb.TX, user *entity.SysUser, card *appCardQueryRow) error { if user == nil || user.DeptId <= 0 { return gerror.New("当前用户未绑定机构,不能划拨卡片") } if card == nil || strings.TrimSpace(card.Iccid) == "" { return gerror.New("划拨卡信息不完整") } if card.DeptId != user.DeptId { cardLabel := appFirstNotEmpty(card.Iccid, card.Msisdn, card.Imsi, card.Imei) return gerror.Newf("卡%s当前归属不在本企业名下,划拨权已归当前归属客户", cardLabel) } return nil } func (s *sAppCard) loadDeptById(ctx context.Context, tenantId string, deptId int64) (*entity.SysDept, error) { var dept *entity.SysDept err := dao.SysDept.Ctx(ctx). Where(dao.SysDept.Columns().TenantId, tenantId). Where(dao.SysDept.Columns().DeptId, deptId). WhereNull(dao.SysDept.Columns().DeletedAt). Limit(1). Scan(&dept) if err != nil { return nil, gerror.Wrap(err, "查询客户信息失败") } return dept, nil } func (s *sAppCard) buildAllocateBatchModel(ctx context.Context, user *entity.SysUser, batch *appAllocateBatchContext, batchNo string, sourceBatchNo string, batchType int, status string) *model.AppCardAllocateBatchModel { batch.refreshCounts() cards := make([]*model.AppCardModel, 0, len(batch.Cards)) details := make([]*model.AppCardAllocateBatchDetailModel, 0, len(batch.Cards)) for _, item := range batch.Cards { if item == nil { continue } if item.Card != nil && strings.TrimSpace(item.ErrorReason) == "" { cards = append(cards, s.cardRowToModel(ctx, item.Card, user, appCardOwnerAgent, true)) } details = append(details, &model.AppCardAllocateBatchDetailModel{ RowNo: item.RowNo, ItemId: item.allocateItemId(), Status: appAllocateDetailStatusText(item.ErrorReason), ErrorReason: strings.TrimSpace(item.ErrorReason), }) } return &model.AppCardAllocateBatchModel{ BatchNo: strings.TrimSpace(batchNo), SourceBatchNo: strings.TrimSpace(sourceBatchNo), BatchType: batchType, Quantity: batch.ValidCount, TotalCount: batch.TotalCount, ValidCount: batch.ValidCount, FailCount: batch.FailCount, FailReason: batch.FailReason, Target: appAllocateTargetModel(batch.Target, user.DeptId), Cards: cards, Details: details, Status: status, } } func appAllocateTargetModel(dept *entity.SysDept, currentDeptId int64) *model.AppCardAllocateTargetModel { if dept == nil { return nil } label := strings.TrimSpace(dept.DeptName) if strings.TrimSpace(dept.DeptKey) != "" { label = fmt.Sprintf("%s(%s)", label, strings.TrimSpace(dept.DeptKey)) } scope := "self" scopeName := "当前机构" if currentDeptId > 0 && dept.DeptId != currentDeptId && dept.ParentId == currentDeptId { scope = "direct_child" scopeName = "直属下级" } return &model.AppCardAllocateTargetModel{ DeptId: dept.DeptId, ParentId: dept.ParentId, DeptKey: strings.TrimSpace(dept.DeptKey), DeptName: strings.TrimSpace(dept.DeptName), DeptCategory: strings.TrimSpace(dept.DeptCategory), TargetScope: scope, TargetScopeName: scopeName, Label: label, } } func appAllocatePrepareBatchInfo(batch *appAllocateBatchContext, batchNo string) *gjson.Json { return gjson.New(map[string]any{ "source": "app", "batchNo": strings.TrimSpace(batchNo), "sourceBatchNo": strings.TrimSpace(batchNo), "targetBatchType": consts.BatchTypeAllocate, "targetBatchName": "卡划拨", "targetDeptId": batch.Target.DeptId, "targetDeptName": batch.Target.DeptName, "quantity": batch.ValidCount, "totalCount": batch.TotalCount, "validCount": batch.ValidCount, "failCount": batch.FailCount, "failReason": batch.FailReason, "cards": appAllocateCardSnapshotList(batch), }) } func appAllocateSubmitBatchInfo(batch *appAllocateBatchContext, batchNo string, sourceBatchNo string, remark string, now *gtime.Time) *gjson.Json { return gjson.New(map[string]any{ "source": "app", "batchNo": strings.TrimSpace(batchNo), "sourceBatchNo": strings.TrimSpace(sourceBatchNo), "targetBatchType": consts.BatchTypeAllocate, "targetBatchName": "卡划拨", "targetDeptId": batch.Target.DeptId, "targetDeptName": batch.Target.DeptName, "quantity": len(batch.Cards), "remark": strings.TrimSpace(remark), "allocateTime": now, "cards": appAllocateCardSnapshotList(batch), }) } func appAllocatePrepareDetailSnapshot(batch *appAllocateBatchContext, item *appAllocateCardContext) *gjson.Json { if item == nil { return gjson.New(map[string]any{}) } card := item.Card cardId := int64(0) if card != nil { cardId = card.Id } return gjson.New(map[string]any{ "ICCID": appAllocateSnapshotICCID(item), "MSISDN": appAllocateSnapshotField(card, func(c *appCardQueryRow) string { return c.Msisdn }), "IMSI": appAllocateSnapshotField(card, func(c *appCardQueryRow) string { return c.Imsi }), "IMEI": appAllocateSnapshotField(card, func(c *appCardQueryRow) string { return c.Imei }), "rowNo": item.RowNo, "cardId": cardId, "sourceDeptId": item.SourceDeptId, "targetDeptId": batch.Target.DeptId, "targetDeptName": batch.Target.DeptName, "currentPackageCode": appCardCurrentPackageCode(card), "packageName": appCardPackageName(card), "errorReason": strings.TrimSpace(item.ErrorReason), }) } func appAllocateSnapshotICCID(item *appAllocateCardContext) string { if item == nil { return "" } if item.Card != nil && strings.TrimSpace(item.Card.Iccid) != "" { return strings.TrimSpace(item.Card.Iccid) } return strings.TrimSpace(item.ItemId) } func appAllocateSnapshotField(card *appCardQueryRow, getter func(*appCardQueryRow) string) string { if card == nil || getter == nil { return "" } return strings.TrimSpace(getter(card)) } func appAllocateDetailStatusText(reason string) string { if strings.TrimSpace(reason) != "" { return "failed" } return "success" } func appAllocateSubmitDetailSnapshot(batch *appAllocateBatchContext, item *appAllocateCardContext, batchNo string, sourceBatchNo string) *gjson.Json { card := item.Card return gjson.New(map[string]any{ "ICCID": card.Iccid, "MSISDN": card.Msisdn, "IMSI": card.Imsi, "IMEI": card.Imei, "rowNo": item.RowNo, "cardId": card.Id, "allocateBatchNo": strings.TrimSpace(batchNo), "sourceBatchNo": strings.TrimSpace(sourceBatchNo), "sourceDeptId": item.SourceDeptId, "targetDeptId": batch.Target.DeptId, "targetDeptName": batch.Target.DeptName, "currentPackageCode": appCardCurrentPackageCode(card), "packageName": appCardPackageName(card), }) } func appAllocateCardSnapshotList(batch *appAllocateBatchContext) []map[string]any { cards := make([]map[string]any, 0, len(batch.Cards)) for _, item := range batch.Cards { if item == nil { continue } cardId := int64(0) iccid := item.allocateItemId() msisdn := "" currentPackageCode := "" if item.Card != nil { cardId = item.Card.Id msisdn = item.Card.Msisdn currentPackageCode = appCardCurrentPackageCode(item.Card) } cards = append(cards, map[string]any{ "cardId": cardId, "iccid": iccid, "msisdn": msisdn, "sourceDeptId": item.SourceDeptId, "targetDeptId": batch.Target.DeptId, "currentPackageCode": currentPackageCode, "errorReason": strings.TrimSpace(item.ErrorReason), }) } return cards } func appAllocateSourceBatchNoFromBatch(batch *entity.BatchMain) string { if batch == nil { return "" } if batch.BatchInfo != nil { if sourceBatchNo := strings.TrimSpace(batch.BatchInfo.Get("sourceBatchNo").String()); sourceBatchNo != "" { return sourceBatchNo } } return strings.TrimSpace(batch.SourceBatchNo) } func appNormalizeAllocateCardValues(values []string) []string { seen := map[string]struct{}{} result := make([]string, 0, len(values)) for _, raw := range values { parts := strings.FieldsFunc(raw, func(r rune) bool { switch r { case ',', ',', ';', ';', '\n', '\r', '\t', ' ': return true default: return false } }) for _, part := range parts { value := strings.TrimSpace(part) if value == "" { continue } if _, exists := seen[value]; exists { continue } seen[value] = struct{}{} result = append(result, value) } } return result }