809 lines
26 KiB
Go
809 lines
26 KiB
Go
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
|
||
}
|