330 lines
11 KiB
Go
330 lines
11 KiB
Go
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
|
|
}
|