Files
iotlcm/server/internal/logic/app/app_card_allocate.go
T

809 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}