chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)

This commit is contained in:
MiFaZhan
2026-11-02 13:27:59 +08:00
1250 changed files with 154111 additions and 0 deletions
@@ -0,0 +1,329 @@
package change
import (
"context"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/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/frame/g"
)
type Processor struct {
repo *Repository
config batchimport.ProcessorConfig
}
func NewProcessor() *Processor {
return &Processor{
repo: NewRepository(),
config: batchimport.ProcessorConfig{
Code: "sim_card_change",
BatchType: consts.BatchTypeChange,
BatchNoPrefix: consts.BatchNoPrefixChange,
TaskName: consts.ResourceBatchDownloadTaskSimCardChange,
ExportScene: consts.ResourceExportSceneChange,
ImportDir: consts.SimCardChangeSubDirImportExcel,
ResultDir: consts.SimCardChangeSubDirResultExcel,
MaxFileSize: consts.SimCardChangeMaxFileSize,
TemplateHeaders: []string{"ICCID", "MSISDN", "IMSI"},
RequiredHeaders: []string{"ICCID"},
},
}
}
func (p *Processor) Config() batchimport.ProcessorConfig {
return p.config
}
func (p *Processor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) {
return nil, gerror.New("sim card change does not support file import on base processor")
}
func (p *Processor) ValidateRows(ctx context.Context, rows []batchimport.Row) error {
changeRows := make([]*Row, 0, len(rows))
for _, item := range rows {
row, ok := item.(*Row)
if !ok || row == nil {
item.AddErrorReason("change row type is invalid")
continue
}
changeRows = append(changeRows, row)
}
return validateRows(changeRows)
}
func (p *Processor) Execute(ctx context.Context, tx gdb.TX, req *batchimport.SubmitRequest, rows []batchimport.Row) error {
return gerror.New("sim card change must be submitted from prepare batch")
}
func (p *Processor) BuildBatchInfo(ctx context.Context, req batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadId int64) *gjson.Json {
info := g.Map{}
if resultOss != nil {
info["resultFileOssId"] = resultOss.OssId
}
if downloadId > 0 {
info["resultDownloadId"] = downloadId
}
if in, ok := req.BizParams.(*model.TSimCardChangeBatchPrepareParam); ok && in != nil {
if sourceBatchNo := strings.TrimSpace(in.SourceBatchNo); sourceBatchNo != "" {
info["sourceBatchNo"] = sourceBatchNo
}
}
if in, ok := req.BizParams.(*model.TSimCardChangeBatchSubmitParam); ok && in != nil {
if newPackageCode := strings.TrimSpace(in.NewPackageCode); newPackageCode != "" {
info["newPackageCode"] = newPackageCode
}
if in.DurationMonths > 0 {
info["durationMonths"] = in.DurationMonths
}
if in.GiftDurationMonths >= 0 {
info["giftDurationMonths"] = in.GiftDurationMonths
}
if effectiveMode := strings.TrimSpace(in.EffectiveMode); effectiveMode != "" {
info["effectiveMode"] = effectiveMode
}
info["offlineContractCompleted"] = in.OfflineContractCompleted
}
if snapshot, ok := req.ExecutionResult.(*model.TSimCardChangePrepareSnapshot); ok && snapshot != nil {
if result != nil && result.BatchType == consts.BatchTypePrepare {
info["targetBatchType"] = consts.BatchTypeChange
info["targetBatchName"] = "sim_card_change"
}
if snapshot.SourceBatchNo != "" {
info["sourceBatchNo"] = snapshot.SourceBatchNo
}
info["customerAgentId"] = snapshot.CustomerAgentId
info["customerName"] = snapshot.CustomerName
info["oldPackageCode"] = snapshot.OldPackageCode
info["oldPackageName"] = snapshot.OldPackageName
info["oldPackageType"] = snapshot.OldPackageType
info["oldPackageFlowMb"] = snapshot.OldPackageFlowMb
info["quantity"] = snapshot.Quantity
}
if output, ok := req.ExecutionResult.(*CompleteOutput); ok && output != nil {
if output.SourceBatchNo != "" {
info["sourceBatchNo"] = output.SourceBatchNo
}
info["sourceMode"] = output.SourceMode
info["orderSn"] = output.OrderSn
info["customerAgentId"] = output.CustomerAgentId
info["customerName"] = output.CustomerName
info["oldPackageCode"] = output.OldPackageCode
info["oldPackageName"] = output.OldPackageName
info["newPackageCode"] = output.NewPackageCode
info["newPackageName"] = output.NewPackageName
info["durationMonths"] = output.DurationMonths
info["giftDurationMonths"] = output.GiftDurationMonths
info["effectiveMode"] = output.EffectiveMode
info["offlineContractCompleted"] = output.OfflineContractCompleted
info["changeUnitPrice"] = output.ChangeUnitPrice
info["amount"] = output.Amount
info["quantity"] = output.Quantity
info["packageBeginTime"] = output.PackageBeginTime
info["packageEndTime"] = output.PackageEndTime
info["changeTime"] = output.ChangeTime
}
return gjson.New(info)
}
func (p *Processor) ValidatePrepareContext(ctx context.Context, tx gdb.TX, in *model.TSimCardChangeBatchPrepareParam, operator batchimport.OperatorContext, rows []*Row) (*PrepareContext, error) {
if in == nil {
in = &model.TSimCardChangeBatchPrepareParam{}
}
sourceBatchNo := strings.TrimSpace(in.SourceBatchNo)
iccids := rowsToIccids(rows)
cardsByIccid, err := p.repo.LockCardsByIccids(ctx, tx, iccids)
if err != nil {
return nil, gerror.Wrap(err, "validate change cards failed")
}
bindsByIccid, err := p.repo.LockCurrentActiveBindsByIccids(ctx, tx, iccids)
if err != nil {
return nil, gerror.Wrap(err, "validate change subscriptions failed")
}
packageCodes := make(map[string]struct{})
customerAgentIds := make(map[int64]struct{})
oldPackageCode := ""
customerAgentId := int64(0)
for _, row := range rows {
if row == nil || row.Reason != "" {
continue
}
card := cardsByIccid[row.Iccid]
bind := bindsByIccid[row.Iccid]
reason := validateChangeRow(sourceBatchNo, operator, row, card, bind)
if reason != "" {
row.AddReason(reason)
continue
}
packageCode := strings.TrimSpace(bind.PackageCode)
packageCodes[packageCode] = struct{}{}
customerAgentIds[card.DeptId] = struct{}{}
oldPackageCode = packageCode
customerAgentId = card.DeptId
row.Msisdn = strings.TrimSpace(card.Msisdn)
row.Imsi = strings.TrimSpace(card.Imsi)
row.OldPackageCode = packageCode
row.OldSubscriptionNo = strings.TrimSpace(bind.SubscriptionNo)
row.OldOrderSn = strings.TrimSpace(bind.OrderCode)
row.OldEffectiveTime = bind.EffectiveTime
row.OldExpireTime = bind.ExpectedExpireTime
}
if len(packageCodes) > 1 {
markRowsWithoutReason(rows, "current batch contains multiple old packages and cannot be changed together")
}
if len(customerAgentIds) > 1 {
markRowsWithoutReason(rows, "current batch contains multiple customers and cannot be changed together")
}
if hasInvalidRows(rows) {
markRemainingRowsSkipped(rows)
return nil, gerror.New("some cards do not meet change conditions, batch was not executed")
}
if oldPackageCode == "" || customerAgentId <= 0 {
return nil, gerror.New("change prepare result is incomplete")
}
pkg, err := loadPackageByCode(ctx, tx, operator.TenantId, oldPackageCode)
if err != nil {
return nil, err
}
if pkg.ChangeablePackageCodes == nil || pkg.ChangeablePackageCodes.IsNil() || len(pkg.ChangeablePackageCodes.Interfaces()) == 0 {
return nil, gerror.New("current package has no changeable packages configured")
}
customerName, err := loadDeptName(ctx, tx, operator.TenantId, customerAgentId)
if err != nil {
return nil, err
}
for _, row := range rows {
if row != nil {
row.OldPackageName = pkg.PackageName
}
}
return &PrepareContext{
CustomerAgentId: customerAgentId,
CustomerName: customerName,
OldPackage: pkg,
Quantity: len(iccids),
}, nil
}
func validateChangeRow(sourceBatchNo string, operator batchimport.OperatorContext, row *Row, card *entity.TSimCardInfo, bind *entity.TSimCardBindPkg) string {
if card == nil {
return "ICCID does not exist as formal card"
}
if bind == nil {
return "ICCID current subscription data is missing or invalid"
}
if card.DeptId != operator.DeptId && card.CreatedDept != operator.DeptId {
return "ICCID does not belong to current department"
}
if strings.TrimSpace(card.CurrentPackageCode) == "" || card.PackageStatus == consts.PackageStatusNone {
return "ICCID has no current package"
}
if strings.TrimSpace(card.CurrentPackageCode) != strings.TrimSpace(bind.PackageCode) {
return "ICCID current package and subscription package do not match"
}
if sourceBatchNo != "" && strings.TrimSpace(bind.BatchNo) != sourceBatchNo {
return "ICCID current subscription does not belong to sourceBatchNo"
}
if row != nil {
if msisdn := strings.TrimSpace(row.Msisdn); msisdn != "" && msisdn != strings.TrimSpace(card.Msisdn) {
return "MSISDN does not match card fact"
}
if imsi := strings.TrimSpace(row.Imsi); imsi != "" && imsi != strings.TrimSpace(card.Imsi) {
return "IMSI does not match card fact"
}
}
return ""
}
func loadPackageByCode(ctx context.Context, tx gdb.TX, tenantId, packageCode string) (*entity.TSimFlowPackageInfo, error) {
pkg := &entity.TSimFlowPackageInfo{}
columns := dao.TSimFlowPackageInfo.Columns()
err := tx.Ctx(ctx).Model(dao.TSimFlowPackageInfo.Table()).
Where(columns.TenantId, tenantId).
Where(columns.PackageCode, strings.TrimSpace(packageCode)).
Scan(pkg)
if err != nil {
return nil, gerror.Wrap(err, "load package failed")
}
if pkg.Id == 0 {
return nil, gerror.New("package not found")
}
return pkg, nil
}
func loadDeptName(ctx context.Context, tx gdb.TX, tenantId string, deptId int64) (string, error) {
dept := &entity.SysDept{}
columns := dao.SysDept.Columns()
err := tx.Ctx(ctx).Model(dao.SysDept.Table()).
Where(columns.TenantId, tenantId).
Where(columns.DeptId, deptId).
Scan(dept)
if err != nil {
return "", gerror.Wrap(err, "load customer failed")
}
if dept.DeptId == 0 {
return "", gerror.New("customer not found")
}
return strings.TrimSpace(dept.DeptName), nil
}
func hasInvalidRows(rows []*Row) bool {
for _, row := range rows {
if row != nil && row.Reason != "" {
return true
}
}
return false
}
func markRowsWithoutReason(rows []*Row, reason string) {
for _, row := range rows {
if row != nil && row.Reason == "" {
row.AddReason(reason)
}
}
}
func markRemainingRowsSkipped(rows []*Row) {
for _, row := range rows {
if row != nil && row.Reason == "" {
row.AddReason("same batch has rows failing change conditions, this row was skipped")
}
}
}
func validateRows(rows []*Row) error {
firstByIccid := make(map[string]*Row, len(rows))
for _, row := range rows {
if row == nil {
continue
}
row.Iccid = strings.TrimSpace(row.Iccid)
row.Msisdn = strings.TrimSpace(row.Msisdn)
row.Imsi = strings.TrimSpace(row.Imsi)
if row.Iccid == "" {
row.AddReason("ICCID cannot be empty")
continue
}
if first, ok := firstByIccid[row.Iccid]; ok {
row.AddReason("ICCID duplicated")
first.AddReason("ICCID duplicated")
continue
}
firstByIccid[row.Iccid] = row
}
return nil
}