chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
package addpackage
|
||||
|
||||
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/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"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"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type AsyncConfirmInput struct {
|
||||
BatchNo string
|
||||
TargetBatchType int
|
||||
PackageCode string
|
||||
Remark string
|
||||
PaymentMode string
|
||||
OfflineContractCompleted bool
|
||||
EffectiveMode string
|
||||
Operator batchimport.OperatorContext
|
||||
}
|
||||
|
||||
type AsyncConfirmResult struct {
|
||||
BatchNo string
|
||||
BatchType int
|
||||
BatchAsyncStatus int
|
||||
}
|
||||
|
||||
func ConvertPrepareToAsync(ctx context.Context, tx gdb.TX, in *AsyncConfirmInput) (*AsyncConfirmResult, error) {
|
||||
if in == nil {
|
||||
return nil, gerror.New("加油包确认参数不能为空")
|
||||
}
|
||||
batchNo := strings.TrimSpace(in.BatchNo)
|
||||
if batchNo == "" {
|
||||
return nil, gerror.New("加油包批次号不能为空")
|
||||
}
|
||||
targetBatchType := in.TargetBatchType
|
||||
if targetBatchType != consts.BatchTypeCardAddPackage && targetBatchType != consts.BatchTypePoolAddPackage {
|
||||
return nil, gerror.New("加油包目标批次类型不正确")
|
||||
}
|
||||
|
||||
columns := dao.BatchMain.Columns()
|
||||
main := &entity.BatchMain{}
|
||||
err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
|
||||
Where(columns.BatchNo, batchNo).
|
||||
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(columns.CreatedDept, contexts.GetDeptId(ctx)).
|
||||
LockUpdate().
|
||||
Scan(main)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询加油包预处理批次失败")
|
||||
}
|
||||
if main.Id == 0 {
|
||||
return nil, gerror.New("加油包预处理批次不存在")
|
||||
}
|
||||
if main.BatchType != consts.BatchTypePrepare {
|
||||
return nil, gerror.New("加油包批次不是预处理类型")
|
||||
}
|
||||
if main.BatchAsyncStatus != consts.BatchAsyncStatusSuccess {
|
||||
return nil, gerror.New("加油包预处理批次尚未完成")
|
||||
}
|
||||
if main.RunResult != consts.BatchRunResultSuccess {
|
||||
return nil, gerror.New("加油包预处理批次未成功")
|
||||
}
|
||||
if main.BatchInfo == nil {
|
||||
return nil, gerror.New("加油包预处理批次缺少快照")
|
||||
}
|
||||
if main.BatchInfo.Get("targetBatchType").Int() != targetBatchType {
|
||||
return nil, gerror.New("加油包预处理批次类型不匹配")
|
||||
}
|
||||
|
||||
opts, err := normalizeConfirmOptions(main.BatchInfo, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := gtime.Now()
|
||||
info := mergeConfirmBatchInfo(main.BatchInfo, opts)
|
||||
data := do.BatchMain{
|
||||
BatchType: targetBatchType,
|
||||
BatchAsyncStatus: consts.BatchAsyncStatusPending,
|
||||
RunResult: consts.BatchRunResultPending,
|
||||
StartTime: nil,
|
||||
EndTime: nil,
|
||||
FailReason: "",
|
||||
ValidCount: 0,
|
||||
SuccessCount: 0,
|
||||
FailCount: 0,
|
||||
BatchInfo: gjson.New(info),
|
||||
UpdatedBy: in.Operator.UserId,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
res, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
|
||||
Where(columns.Id, main.Id).
|
||||
Where(columns.BatchType, consts.BatchTypePrepare).
|
||||
Where(columns.BatchAsyncStatus, consts.BatchAsyncStatusSuccess).
|
||||
Where(columns.RunResult, consts.BatchRunResultSuccess).
|
||||
Data(data).
|
||||
Update()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "更新加油包批次状态失败")
|
||||
}
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "读取加油包批次更新结果失败")
|
||||
}
|
||||
if affected != 1 {
|
||||
return nil, gerror.New("加油包批次状态已变化,请重试")
|
||||
}
|
||||
|
||||
detailColumns := dao.BatchDetail.Columns()
|
||||
var details []*entity.BatchDetail
|
||||
if err = tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Fields(detailColumns.Id, detailColumns.RowNo, detailColumns.Snapshot).
|
||||
Where(detailColumns.BatchNo, batchNo).
|
||||
Where(detailColumns.BatchType, consts.BatchTypePrepare).
|
||||
Scan(&details); err != nil {
|
||||
return nil, gerror.Wrap(err, "查询加油包预处理明细失败")
|
||||
}
|
||||
if len(details) == 0 {
|
||||
return nil, gerror.New("加油包预处理批次没有可确认明细")
|
||||
}
|
||||
for _, detail := range details {
|
||||
if detail == nil || detail.Id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err = tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(detailColumns.Id, detail.Id).
|
||||
Where(detailColumns.BatchNo, batchNo).
|
||||
Where(detailColumns.BatchType, consts.BatchTypePrepare).
|
||||
Data(do.BatchDetail{
|
||||
BatchType: targetBatchType,
|
||||
DetailStatus: consts.BatchDetailStatusPending,
|
||||
ErrorCode: "",
|
||||
ErrorReason: "",
|
||||
Snapshot: mergeConfirmDetailSnapshot(detail.Snapshot, opts),
|
||||
UpdatedBy: in.Operator.UserId,
|
||||
UpdatedAt: now,
|
||||
}).
|
||||
Update(); err != nil {
|
||||
return nil, gerror.Wrap(err, "更新加油包批次明细类型失败")
|
||||
}
|
||||
}
|
||||
|
||||
return &AsyncConfirmResult{
|
||||
BatchNo: batchNo,
|
||||
BatchType: targetBatchType,
|
||||
BatchAsyncStatus: consts.BatchAsyncStatusPending,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeConfirmOptions(info *gjson.Json, in *AsyncConfirmInput) (SubmitOptions, error) {
|
||||
opts, err := normalizeOptions(SubmitOptions{
|
||||
PackageCode: in.PackageCode,
|
||||
Remark: in.Remark,
|
||||
PaymentMode: in.PaymentMode,
|
||||
OfflineContractCompleted: in.OfflineContractCompleted,
|
||||
EffectiveMode: in.EffectiveMode,
|
||||
})
|
||||
if err != nil {
|
||||
return SubmitOptions{}, err
|
||||
}
|
||||
preparedPackageCode := strings.TrimSpace(info.Get("packageCode").String())
|
||||
if opts.PackageCode == "" {
|
||||
opts.PackageCode = preparedPackageCode
|
||||
}
|
||||
if opts.PackageCode == "" {
|
||||
return SubmitOptions{}, gerror.New("请选择加油包套餐")
|
||||
}
|
||||
if preparedPackageCode != "" && opts.PackageCode != preparedPackageCode {
|
||||
return SubmitOptions{}, gerror.New("加油包套餐与预处理结果不一致,请重新预处理")
|
||||
}
|
||||
if preparedPackageCode == "" {
|
||||
if err := validatePreparedPackageOption(info, opts.PackageCode); err != nil {
|
||||
return SubmitOptions{}, err
|
||||
}
|
||||
}
|
||||
if err := validatePreparedPaymentEnough(info, opts); err != nil {
|
||||
return SubmitOptions{}, err
|
||||
}
|
||||
preparedEffectiveMode := strings.TrimSpace(info.Get("effectiveMode").String())
|
||||
if preparedEffectiveMode != "" {
|
||||
normalized, err := NormalizeEffectiveMode(preparedEffectiveMode)
|
||||
if err != nil {
|
||||
return SubmitOptions{}, err
|
||||
}
|
||||
if opts.EffectiveMode != normalized {
|
||||
return SubmitOptions{}, gerror.New("加油包生效方式与预处理结果不一致,请重新预处理")
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func validatePreparedPackageOption(info *gjson.Json, packageCode string) error {
|
||||
if info == nil || info.IsNil() {
|
||||
return nil
|
||||
}
|
||||
packageCode = strings.TrimSpace(packageCode)
|
||||
hasOptions := false
|
||||
for _, key := range []string{"availablePackages", "packageOptions"} {
|
||||
value := info.Get(key)
|
||||
if value == nil || value.IsNil() {
|
||||
continue
|
||||
}
|
||||
hasOptions = true
|
||||
for _, raw := range value.Interfaces() {
|
||||
item := gjson.New(raw)
|
||||
code := strings.TrimSpace(item.Get("packageCode").String())
|
||||
if code == "" {
|
||||
code = strings.TrimSpace(item.Get("value").String())
|
||||
}
|
||||
if code != packageCode {
|
||||
continue
|
||||
}
|
||||
if priceAvailable := item.Get("priceAvailable"); priceAvailable != nil && !priceAvailable.IsNil() && !priceAvailable.Bool() {
|
||||
reason := strings.TrimSpace(item.Get("priceReason").String())
|
||||
if reason == "" {
|
||||
reason = "当前套餐暂时无法计算价格,请联系管理员维护定价"
|
||||
}
|
||||
return gerror.New(reason)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if hasOptions {
|
||||
return gerror.New("请选择预处理返回的可购加油包套餐")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePreparedPaymentEnough(info *gjson.Json, opts SubmitOptions) error {
|
||||
if strings.TrimSpace(opts.PaymentMode) != "balance" || info == nil || info.IsNil() {
|
||||
return nil
|
||||
}
|
||||
if option := findPreparedPackageOption(info, opts.PackageCode); option != nil {
|
||||
if err := validatePaymentAccountsEnough(option.Get("paymentAccounts")); err != nil {
|
||||
return err
|
||||
}
|
||||
if hasPaymentAccounts(option.Get("paymentAccounts")) {
|
||||
return nil
|
||||
}
|
||||
totalAmount := option.Get("totalAmount").Float64()
|
||||
availableBalance := option.Get("availableBalance").Float64()
|
||||
if availableBalance <= 0 {
|
||||
availableBalance = info.Get("availableBalance").Float64()
|
||||
}
|
||||
if totalAmount > 0 && availableBalance+0.0001 < totalAmount {
|
||||
return gerror.Newf("账户余额不足,需支付%.2f元,当前余额%.2f元", totalAmount, availableBalance)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := validatePaymentAccountsEnough(info.Get("paymentAccounts")); err != nil {
|
||||
return err
|
||||
}
|
||||
if hasPaymentAccounts(info.Get("paymentAccounts")) {
|
||||
return nil
|
||||
}
|
||||
totalAmount := info.Get("totalAmount").Float64()
|
||||
availableBalance := info.Get("availableBalance").Float64()
|
||||
if totalAmount > 0 && availableBalance+0.0001 < totalAmount {
|
||||
return gerror.Newf("账户余额不足,需支付%.2f元,当前余额%.2f元", totalAmount, availableBalance)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPreparedPackageOption(info *gjson.Json, packageCode string) *gjson.Json {
|
||||
if info == nil || info.IsNil() {
|
||||
return nil
|
||||
}
|
||||
packageCode = strings.TrimSpace(packageCode)
|
||||
for _, key := range []string{"availablePackages", "packageOptions"} {
|
||||
value := info.Get(key)
|
||||
if value == nil || value.IsNil() {
|
||||
continue
|
||||
}
|
||||
for _, raw := range value.Interfaces() {
|
||||
item := gjson.New(raw)
|
||||
code := strings.TrimSpace(item.Get("packageCode").String())
|
||||
if code == "" {
|
||||
code = strings.TrimSpace(item.Get("value").String())
|
||||
}
|
||||
if code == packageCode {
|
||||
return item
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePaymentAccountsEnough(value *gvar.Var) error {
|
||||
if !hasPaymentAccounts(value) {
|
||||
return nil
|
||||
}
|
||||
for _, raw := range value.Interfaces() {
|
||||
account := gjson.New(raw)
|
||||
requiredAmount := account.Get("requiredAmount").Float64()
|
||||
availableBalance := account.Get("availableBalance").Float64()
|
||||
enoughValue := account.Get("balanceEnough")
|
||||
if enoughValue != nil && !enoughValue.IsNil() {
|
||||
if enoughValue.Bool() {
|
||||
continue
|
||||
}
|
||||
} else if availableBalance+0.0001 >= requiredAmount {
|
||||
continue
|
||||
}
|
||||
return gerror.Newf(
|
||||
"账户余额不足,付款部门%v需支付%.2f元,当前余额%.2f元",
|
||||
account.Get("payerDeptId").Val(),
|
||||
requiredAmount,
|
||||
availableBalance,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasPaymentAccounts(value *gvar.Var) bool {
|
||||
return value != nil && !value.IsNil() && len(value.Interfaces()) > 0
|
||||
}
|
||||
|
||||
func mergeConfirmDetailSnapshot(base *gjson.Json, opts SubmitOptions) *gjson.Json {
|
||||
data := g.Map{}
|
||||
if base != nil {
|
||||
for key, value := range base.Map() {
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
data["packageCode"] = strings.TrimSpace(opts.PackageCode)
|
||||
if remark := strings.TrimSpace(opts.Remark); remark != "" {
|
||||
data["remark"] = remark
|
||||
}
|
||||
data["effectiveMode"] = strings.TrimSpace(opts.EffectiveMode)
|
||||
data["effectiveModeText"] = EffectiveModeText(opts.EffectiveMode)
|
||||
return gjson.New(data)
|
||||
}
|
||||
|
||||
func mergeConfirmBatchInfo(base *gjson.Json, opts SubmitOptions) g.Map {
|
||||
data := g.Map{}
|
||||
if base != nil {
|
||||
for key, value := range base.Map() {
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
data["packageCode"] = strings.TrimSpace(opts.PackageCode)
|
||||
data["remark"] = strings.TrimSpace(opts.Remark)
|
||||
data["paymentMode"] = strings.TrimSpace(opts.PaymentMode)
|
||||
data["offlineContractCompleted"] = opts.OfflineContractCompleted
|
||||
data["effectiveMode"] = strings.TrimSpace(opts.EffectiveMode)
|
||||
data["effectiveModeText"] = EffectiveModeText(opts.EffectiveMode)
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package addpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/excel"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/text"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
const (
|
||||
headerPackageCode = "加油包套餐编码"
|
||||
headerEffectiveMode = "生效方式"
|
||||
headerRemark = "备注"
|
||||
)
|
||||
|
||||
func parseExcelRows(ctx context.Context, filePath string, cfg Config, options SubmitOptions) ([]batchimport.Row, error) {
|
||||
parsedRows, err := excel.ParseFile(ctx, filePath, excel.ParseOptions{
|
||||
RequiredHeaders: []string{cfg.TargetHeader, headerPackageCode},
|
||||
SingleSheet: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(parsedRows))
|
||||
for _, parsed := range parsedRows {
|
||||
row := &Row{
|
||||
RowNo: parsed.RowNo,
|
||||
TargetCode: fieldValue(parsed, targetAliases(cfg)...),
|
||||
PackageCode: fieldValue(parsed, headerPackageCode, "加油包编码", "packageCode", "套餐编码"),
|
||||
EffectiveMode: fieldValue(parsed, headerEffectiveMode, "effectiveMode", "生效模式"),
|
||||
Remark: fieldValue(parsed, headerRemark, "remark"),
|
||||
}
|
||||
applyDefaultOptions(row, options)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func parseInputRows(ctx context.Context, input string, options SubmitOptions) ([]batchimport.Row, error) {
|
||||
lines, err := text.ParseNonEmptyLines(ctx, input)
|
||||
if err != nil {
|
||||
if errors.Is(err, text.ErrEmptyInput) {
|
||||
return nil, gerror.New("批量输入内容不能为空")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
row := &Row{RowNo: line.RowNo}
|
||||
fields := parseInputFields(line.Text)
|
||||
if len(fields) != 1 {
|
||||
row.AddErrorReason("格式错误,应为目标编码")
|
||||
} else {
|
||||
row.TargetCode = fields[0]
|
||||
}
|
||||
applyDefaultOptions(row, options)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func fieldValue(row excel.ParsedRow, names ...string) string {
|
||||
for _, name := range names {
|
||||
if value := strings.TrimSpace(excel.Field(row, name)); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func targetAliases(cfg Config) []string {
|
||||
if cfg.Operation == OperationPool {
|
||||
return []string{cfg.TargetHeader, "流量池编码", "流量池编号", "poolNo", "flowPoolCode", "targetCode"}
|
||||
}
|
||||
return []string{cfg.TargetHeader, "卡号", "iccid", "targetCode"}
|
||||
}
|
||||
|
||||
func parseInputFields(value string) []string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(value, "|") {
|
||||
return splitAndTrim(value, "|")
|
||||
}
|
||||
if strings.Contains(value, "\t") {
|
||||
return splitAndTrim(value, "\t")
|
||||
}
|
||||
return strings.Fields(value)
|
||||
}
|
||||
|
||||
func splitAndTrim(value, sep string) []string {
|
||||
parts := strings.Split(value, sep)
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
out = append(out, strings.TrimSpace(part))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func applyDefaultOptions(row *Row, options SubmitOptions) {
|
||||
if row == nil {
|
||||
return
|
||||
}
|
||||
row.TargetCode = strings.TrimSpace(row.TargetCode)
|
||||
row.PackageCode = strings.TrimSpace(row.PackageCode)
|
||||
row.Remark = strings.TrimSpace(row.Remark)
|
||||
row.EffectiveMode = strings.TrimSpace(row.EffectiveMode)
|
||||
|
||||
if options.PackageCode != "" {
|
||||
if row.PackageCode != "" && row.PackageCode != options.PackageCode {
|
||||
row.AddErrorReason("行内加油包编码与页面选择不一致")
|
||||
}
|
||||
row.PackageCode = options.PackageCode
|
||||
}
|
||||
if row.EffectiveMode == "" && options.EffectiveMode != "" {
|
||||
row.EffectiveMode = options.EffectiveMode
|
||||
}
|
||||
if row.Remark == "" {
|
||||
row.Remark = strings.TrimSpace(options.Remark)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeOptions(options SubmitOptions) (SubmitOptions, error) {
|
||||
options.PackageCode = strings.TrimSpace(options.PackageCode)
|
||||
options.Remark = strings.TrimSpace(options.Remark)
|
||||
mode, err := NormalizeEffectiveMode(options.EffectiveMode)
|
||||
if err != nil {
|
||||
return SubmitOptions{}, err
|
||||
}
|
||||
options.EffectiveMode = mode
|
||||
options.PaymentMode = strings.TrimSpace(options.PaymentMode)
|
||||
if options.PaymentMode == "" {
|
||||
options.PaymentMode = "balance"
|
||||
}
|
||||
switch options.PaymentMode {
|
||||
case "balance":
|
||||
options.OfflineContractCompleted = false
|
||||
case "offline":
|
||||
if !options.OfflineContractCompleted {
|
||||
return SubmitOptions{}, gerror.New("请确认线下合同、销售和走账已完成")
|
||||
}
|
||||
default:
|
||||
return SubmitOptions{}, gerror.New("加油包付款方式不正确")
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package addpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type cardTarget struct {
|
||||
Iccid string `orm:"iccid"`
|
||||
CurrentPackageCode string `orm:"current_package_code"`
|
||||
PackageStatus int `orm:"package_status"`
|
||||
}
|
||||
|
||||
type cardLatestBindTarget struct {
|
||||
Iccid string `orm:"iccid"`
|
||||
PackageCode string `orm:"package_code"`
|
||||
EffectiveStatus int `orm:"effective_status"`
|
||||
ExpectedExpireTime *gtime.Time `orm:"expected_expire_time"`
|
||||
}
|
||||
|
||||
type poolTarget struct {
|
||||
FlowPoolCode string `orm:"flow_pool_code"`
|
||||
PoolPackageCode int64 `orm:"pool_package_code"`
|
||||
PoolStatus int `orm:"pool_status"`
|
||||
}
|
||||
|
||||
func (p *Processor) PrepareRows(ctx context.Context, rows []batchimport.Row) error {
|
||||
if p.config.Operation == OperationPool {
|
||||
return p.preparePoolRows(ctx, rows)
|
||||
}
|
||||
return p.prepareCardRows(ctx, rows)
|
||||
}
|
||||
|
||||
func (p *Processor) prepareCardRows(ctx context.Context, rows []batchimport.Row) error {
|
||||
typedRows, targetCodes, err := collectTypedRows(rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(targetCodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cards []*cardTarget
|
||||
cardColumns := dao.TSimCardInfo.Columns()
|
||||
if err = handler.Model(dao.TSimCardInfo.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
|
||||
Fields(cardColumns.Iccid, cardColumns.CurrentPackageCode, cardColumns.PackageStatus, cardColumns.TenantId, cardColumns.DeptId, cardColumns.CreatedDept, cardColumns.CreatedBy).
|
||||
WhereIn(cardColumns.Iccid, targetCodes).
|
||||
Scan(&cards); err != nil {
|
||||
return gerror.Wrap(err, "查询加油包卡信息失败")
|
||||
}
|
||||
|
||||
cardMap := make(map[string]*cardTarget, len(cards))
|
||||
for _, card := range cards {
|
||||
if card == nil {
|
||||
continue
|
||||
}
|
||||
iccid := strings.TrimSpace(card.Iccid)
|
||||
if iccid == "" {
|
||||
continue
|
||||
}
|
||||
cardMap[iccid] = card
|
||||
}
|
||||
|
||||
latestBinds, err := p.loadLatestCardBaseBinds(ctx, targetCodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
latestBindMap := make(map[string]*cardLatestBindTarget, len(latestBinds))
|
||||
for _, bind := range latestBinds {
|
||||
if bind == nil {
|
||||
continue
|
||||
}
|
||||
iccid := strings.TrimSpace(bind.Iccid)
|
||||
if iccid == "" || latestBindMap[iccid] != nil {
|
||||
continue
|
||||
}
|
||||
latestBindMap[iccid] = bind
|
||||
}
|
||||
|
||||
basePackageCodes := make([]string, 0, len(latestBindMap))
|
||||
for _, bind := range latestBindMap {
|
||||
if bind == nil {
|
||||
continue
|
||||
}
|
||||
basePackageCodes = append(basePackageCodes, bind.PackageCode)
|
||||
}
|
||||
|
||||
packages, err := p.loadPackageMap(ctx, append(basePackageCodes, rowPackageCodes(typedRows)...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := gtime.Now()
|
||||
for _, row := range typedRows {
|
||||
card := cardMap[row.TargetCode]
|
||||
if card == nil {
|
||||
row.AddErrorReason("ICCID不存在或无权限")
|
||||
continue
|
||||
}
|
||||
latestBind := latestBindMap[row.TargetCode]
|
||||
if latestBind == nil || strings.TrimSpace(latestBind.PackageCode) == "" {
|
||||
row.AddErrorReason("当前卡订购记录不存在")
|
||||
continue
|
||||
}
|
||||
row.Authorized = true
|
||||
if card.PackageStatus != consts.PackageStatusEffective {
|
||||
row.AddErrorReason("当前卡套餐未生效,不能订购加油包")
|
||||
continue
|
||||
}
|
||||
if bindExpired(latestBind, now) {
|
||||
row.AddErrorReason("当前卡基础套餐已到期,不能订购加油包")
|
||||
continue
|
||||
}
|
||||
if err := p.validateTargetPackage(row, packages, 103); err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
continue
|
||||
}
|
||||
basePackage := packages[strings.TrimSpace(latestBind.PackageCode)]
|
||||
if basePackage == nil {
|
||||
row.AddErrorReason("当前卡基础套餐不存在")
|
||||
continue
|
||||
}
|
||||
if !containsString(packageCodesFromJSON(basePackage.TopupPackageCodes), row.PackageCode) {
|
||||
row.AddErrorReason("当前卡基础套餐不允许订购该加油包")
|
||||
continue
|
||||
}
|
||||
}
|
||||
return rowsValidationError(rows, "加油包卡信息校验失败")
|
||||
}
|
||||
|
||||
func (p *Processor) preparePoolRows(ctx context.Context, rows []batchimport.Row) error {
|
||||
typedRows, targetCodes, err := collectTypedRows(rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(targetCodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pools []*poolTarget
|
||||
poolColumns := dao.TSimFlowPool.Columns()
|
||||
if err = handler.Model(dao.TSimFlowPool.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
|
||||
Fields(poolColumns.FlowPoolCode, poolColumns.PoolPackageCode, poolColumns.PoolStatus, poolColumns.TenantId, poolColumns.DeptId, poolColumns.CreatedDept, poolColumns.CreatedBy).
|
||||
WhereIn(poolColumns.FlowPoolCode, targetCodes).
|
||||
Scan(&pools); err != nil {
|
||||
return gerror.Wrap(err, "查询流量池信息失败")
|
||||
}
|
||||
poolMap := make(map[string]*poolTarget, len(pools))
|
||||
for _, pool := range pools {
|
||||
if pool == nil {
|
||||
continue
|
||||
}
|
||||
code := strings.TrimSpace(pool.FlowPoolCode)
|
||||
poolMap[code] = pool
|
||||
}
|
||||
|
||||
packages, err := p.loadPackageMap(ctx, rowPackageCodes(typedRows))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range typedRows {
|
||||
pool := poolMap[row.TargetCode]
|
||||
if pool == nil {
|
||||
row.AddErrorReason("流量池编号不存在或无权限")
|
||||
continue
|
||||
}
|
||||
row.Authorized = true
|
||||
if pool.PoolStatus != 0 {
|
||||
row.AddErrorReason("流量池状态异常,不能订购加油包")
|
||||
continue
|
||||
}
|
||||
if err := p.validateTargetPackage(row, packages, 104); err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
continue
|
||||
}
|
||||
targetPackage := packages[row.PackageCode]
|
||||
if targetPackage == nil {
|
||||
row.AddErrorReason("加油包不存在")
|
||||
continue
|
||||
}
|
||||
if targetPackage.PoolPackageCode != pool.PoolPackageCode {
|
||||
row.AddErrorReason("加油包不属于当前流量池套餐")
|
||||
continue
|
||||
}
|
||||
}
|
||||
return rowsValidationError(rows, "流量池加油包校验失败")
|
||||
}
|
||||
|
||||
func (p *Processor) loadLatestCardBaseBinds(ctx context.Context, iccids []string) ([]*cardLatestBindTarget, error) {
|
||||
iccids = uniqueStrings(iccids)
|
||||
if len(iccids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var binds []*cardLatestBindTarget
|
||||
if err := handler.Model(dao.TSimCardBindPkg.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
|
||||
Fields(
|
||||
dao.TSimCardBindPkg.Columns().Iccid,
|
||||
dao.TSimCardBindPkg.Columns().PackageCode,
|
||||
dao.TSimCardBindPkg.Columns().EffectiveStatus,
|
||||
dao.TSimCardBindPkg.Columns().ExpectedExpireTime,
|
||||
dao.TSimCardBindPkg.Columns().TenantId,
|
||||
dao.TSimCardBindPkg.Columns().DeptId,
|
||||
dao.TSimCardBindPkg.Columns().CreatedDept,
|
||||
dao.TSimCardBindPkg.Columns().CreatedBy,
|
||||
).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().Iccid, iccids).
|
||||
WhereIn(dao.TSimCardBindPkg.Columns().PackageType, []int{101, 102}).
|
||||
Where(dao.TSimCardBindPkg.Columns().EffectiveStatus, consts.EffectiveStatusEffective).
|
||||
OrderDesc(dao.TSimCardBindPkg.Columns().ExpectedExpireTime).
|
||||
OrderDesc(dao.TSimCardBindPkg.Columns().Id).
|
||||
Scan(&binds); err != nil {
|
||||
return nil, gerror.Wrap(err, "查询卡最新订购记录失败")
|
||||
}
|
||||
return binds, nil
|
||||
}
|
||||
|
||||
func bindExpired(bind *cardLatestBindTarget, now *gtime.Time) bool {
|
||||
if bind == nil {
|
||||
return true
|
||||
}
|
||||
if bind.EffectiveStatus == consts.EffectiveStatusExpired {
|
||||
return true
|
||||
}
|
||||
if bind.ExpectedExpireTime == nil || now == nil {
|
||||
return false
|
||||
}
|
||||
return !bind.ExpectedExpireTime.After(now)
|
||||
}
|
||||
|
||||
func (p *Processor) validateTargetPackage(row *Row, packages map[string]*entity.TSimFlowPackageInfo, packageType int) error {
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
pkg := packages[row.PackageCode]
|
||||
if pkg == nil {
|
||||
return gerror.New("加油包不存在")
|
||||
}
|
||||
if pkg.Enabled != 1 {
|
||||
return gerror.New("加油包未启用")
|
||||
}
|
||||
if pkg.PackageType != packageType {
|
||||
return gerror.Newf("加油包类型错误,应为%d", packageType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) loadPackageMap(ctx context.Context, codes []string) (map[string]*entity.TSimFlowPackageInfo, error) {
|
||||
out := make(map[string]*entity.TSimFlowPackageInfo)
|
||||
codes = uniqueStrings(codes)
|
||||
if len(codes) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
var packages []*entity.TSimFlowPackageInfo
|
||||
columns := dao.TSimFlowPackageInfo.Columns()
|
||||
if err := handler.Model(dao.TSimFlowPackageInfo.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: false}).
|
||||
WhereIn(columns.PackageCode, codes).
|
||||
Scan(&packages); err != nil {
|
||||
return nil, gerror.Wrap(err, "查询加油包套餐信息失败")
|
||||
}
|
||||
for _, pkg := range packages {
|
||||
if pkg == nil {
|
||||
continue
|
||||
}
|
||||
out[strings.TrimSpace(pkg.PackageCode)] = pkg
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func collectTypedRows(rows []batchimport.Row) ([]*Row, []string, error) {
|
||||
typedRows := make([]*Row, 0, len(rows))
|
||||
targetCodes := make([]string, 0, len(rows))
|
||||
for _, item := range rows {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return nil, nil, gerror.New("加油包批次行类型错误")
|
||||
}
|
||||
typedRows = append(typedRows, row)
|
||||
if row.TargetCode != "" {
|
||||
targetCodes = append(targetCodes, row.TargetCode)
|
||||
}
|
||||
}
|
||||
return typedRows, uniqueStrings(targetCodes), nil
|
||||
}
|
||||
|
||||
func rowPackageCodes(rows []*Row) []string {
|
||||
codes := make([]string, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if row != nil {
|
||||
codes = append(codes, row.PackageCode)
|
||||
}
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
func packageCodesFromJSON(jsonValue *gjson.Json) []string {
|
||||
if jsonValue == nil || jsonValue.IsNil() {
|
||||
return nil
|
||||
}
|
||||
values := jsonValue.Interfaces()
|
||||
out := make([]string, 0, len(values))
|
||||
for _, raw := range values {
|
||||
code := strings.TrimSpace(fmt.Sprint(raw))
|
||||
if code != "" {
|
||||
out = append(out, code)
|
||||
}
|
||||
}
|
||||
return uniqueStrings(out)
|
||||
}
|
||||
|
||||
func containsString(values []string, target string) bool {
|
||||
target = strings.TrimSpace(target)
|
||||
if target == "" {
|
||||
return false
|
||||
}
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func uniqueStrings(in []string) []string {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(in))
|
||||
out := make([]string, 0, len(in))
|
||||
for _, value := range in {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
continue
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
out = append(out, value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package addpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type PrepareProcessor struct {
|
||||
inner *Processor
|
||||
config batchimport.ProcessorConfig
|
||||
targetBatchType int
|
||||
}
|
||||
|
||||
func NewPrepareProcessor(operation Operation, options SubmitOptions) (*PrepareProcessor, error) {
|
||||
inner, err := NewProcessor(operation, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := inner.Config()
|
||||
cfg.Code = cfg.Code + "_prepare"
|
||||
cfg.BatchType = consts.BatchTypePrepare
|
||||
return &PrepareProcessor{
|
||||
inner: inner,
|
||||
config: cfg,
|
||||
targetBatchType: inner.config.BatchType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PrepareProcessor) Config() batchimport.ProcessorConfig {
|
||||
return p.config
|
||||
}
|
||||
|
||||
func (p *PrepareProcessor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) {
|
||||
return p.inner.ParseRows(ctx, filePath)
|
||||
}
|
||||
|
||||
func (p *PrepareProcessor) ValidateRows(ctx context.Context, rows []batchimport.Row) error {
|
||||
// Keep submit non-blocking; the worker owns add package prepare validation.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PrepareProcessor) BuildBatchInfo(ctx context.Context, req batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadId int64) *gjson.Json {
|
||||
info := g.Map{}
|
||||
base := p.inner.BuildBatchInfo(ctx, req, result, importOss, resultOss, downloadId)
|
||||
if base != nil {
|
||||
for key, value := range base.Map() {
|
||||
info[key] = value
|
||||
}
|
||||
}
|
||||
info["operation"] = p.config.Code
|
||||
info["batchType"] = consts.BatchTypePrepare
|
||||
info["targetBatchType"] = p.targetBatchType
|
||||
info["targetBatchName"] = addPackageTargetBatchName(p.targetBatchType)
|
||||
info["targetLabel"] = p.inner.config.TargetLabel
|
||||
return gjson.New(info)
|
||||
}
|
||||
|
||||
func addPackageTargetBatchName(batchType int) string {
|
||||
switch batchType {
|
||||
case consts.BatchTypePoolAddPackage:
|
||||
return "流量池加油包"
|
||||
default:
|
||||
return "单卡加油包"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package addpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
config Config
|
||||
options SubmitOptions
|
||||
}
|
||||
|
||||
func NewProcessor(operation Operation, options SubmitOptions) (*Processor, error) {
|
||||
cfg, err := operation.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options, err = normalizeOptions(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Processor{config: cfg, options: options}, nil
|
||||
}
|
||||
|
||||
func (p *Processor) Config() batchimport.ProcessorConfig {
|
||||
maxFileSize := consts.SimCardAddPackageMaxFileSize
|
||||
if p.config.Operation == OperationPool {
|
||||
maxFileSize = consts.SimPoolAddPackageMaxFileSize
|
||||
}
|
||||
return batchimport.ProcessorConfig{
|
||||
Code: p.config.Code,
|
||||
BatchType: p.config.BatchType,
|
||||
BatchNoPrefix: p.config.BatchPrefix,
|
||||
TaskName: p.config.TaskName,
|
||||
ExportScene: p.config.ExportScene,
|
||||
ImportDir: p.config.ImportDir,
|
||||
ResultDir: p.config.ResultDir,
|
||||
MaxFileSize: int64(maxFileSize),
|
||||
TemplateHeaders: p.TemplateHeaders(),
|
||||
RequiredHeaders: []string{p.config.TargetHeader, headerPackageCode},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) TemplateHeaders() []string {
|
||||
return []string{p.config.TargetHeader, headerPackageCode, headerRemark}
|
||||
}
|
||||
|
||||
func (p *Processor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) {
|
||||
return parseExcelRows(ctx, filePath, p.config, p.options)
|
||||
}
|
||||
|
||||
func (p *Processor) ValidateRows(_ context.Context, rows []batchimport.Row) error {
|
||||
firstByTarget := make(map[string]*Row, len(rows))
|
||||
for _, item := range rows {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return gerror.New("加油包批次行类型错误")
|
||||
}
|
||||
row.TargetCode = strings.TrimSpace(row.TargetCode)
|
||||
row.PackageCode = strings.TrimSpace(row.PackageCode)
|
||||
row.Remark = strings.TrimSpace(row.Remark)
|
||||
row.EffectiveMode = strings.TrimSpace(row.EffectiveMode)
|
||||
if row.TargetCode == "" {
|
||||
row.AddErrorReason(p.config.TargetLabel + "不能为空")
|
||||
continue
|
||||
}
|
||||
if first, exists := firstByTarget[row.TargetCode]; exists {
|
||||
row.AddErrorReason(fmt.Sprintf("%s与第%d行重复", p.config.TargetLabel, first.RowNo))
|
||||
first.AddErrorReason(p.config.TargetLabel + "重复")
|
||||
continue
|
||||
}
|
||||
firstByTarget[row.TargetCode] = row
|
||||
if row.PackageCode == "" {
|
||||
row.AddErrorReason("加油包编码不能为空")
|
||||
continue
|
||||
}
|
||||
if _, err := NormalizeEffectiveMode(row.EffectiveMode); err != nil {
|
||||
row.AddErrorReason(err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
return rowsValidationError(rows, "加油包批次基础校验失败")
|
||||
}
|
||||
|
||||
func (p *Processor) BuildBatchInfo(_ context.Context, _ batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadId int64) *gjson.Json {
|
||||
info := g.Map{
|
||||
"operation": p.config.Code,
|
||||
"batchMode": string(p.config.Operation),
|
||||
"batchType": p.config.BatchType,
|
||||
"targetLabel": p.config.TargetLabel,
|
||||
"packageCode": p.options.PackageCode,
|
||||
"remark": p.options.Remark,
|
||||
"paymentMode": p.options.PaymentMode,
|
||||
"offlineContractCompleted": p.options.OfflineContractCompleted,
|
||||
"effectiveMode": p.options.EffectiveMode,
|
||||
"effectiveModeText": EffectiveModeText(p.options.EffectiveMode),
|
||||
"autoConfirm": p.options.AutoConfirm,
|
||||
"resultFile": g.Map{"status": "pending"},
|
||||
}
|
||||
if result != nil && result.ImportFileOssId > 0 {
|
||||
info["importFileOssId"] = result.ImportFileOssId
|
||||
} else if importOss != nil && importOss.OssId > 0 {
|
||||
info["importFileOssId"] = importOss.OssId
|
||||
}
|
||||
if resultOss != nil && resultOss.OssId > 0 {
|
||||
info["resultFileOssId"] = resultOss.OssId
|
||||
}
|
||||
if downloadId > 0 {
|
||||
info["resultDownloadId"] = downloadId
|
||||
}
|
||||
return gjson.New(info)
|
||||
}
|
||||
|
||||
func rowsValidationError(rows []batchimport.Row, prefix string) error {
|
||||
reasons := make([]string, 0)
|
||||
for _, item := range rows {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
rowReasons := item.GetErrorReasons()
|
||||
if len(rowReasons) == 0 {
|
||||
continue
|
||||
}
|
||||
reasons = append(reasons, fmt.Sprintf("第%d行:%s", item.GetRowNo(), strings.Join(rowReasons, ";")))
|
||||
if len(reasons) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(reasons) == 0 {
|
||||
return nil
|
||||
}
|
||||
return gerror.New(prefix + ":" + strings.Join(reasons, ";"))
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package addpackage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchmetadata"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/common"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/iot_excelize"
|
||||
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type Submitter struct {
|
||||
operation Operation
|
||||
}
|
||||
|
||||
func NewCardSubmitter() *Submitter {
|
||||
return &Submitter{operation: OperationCard}
|
||||
}
|
||||
|
||||
func NewPoolSubmitter() *Submitter {
|
||||
return &Submitter{operation: OperationPool}
|
||||
}
|
||||
|
||||
func (s *Submitter) DownloadTemplate(ctx context.Context) error {
|
||||
processor, err := NewProcessor(s.operation, SubmitOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f := excelize.NewFile()
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
headers := processor.TemplateHeaders()
|
||||
if err := f.SetSheetRow("Sheet1", "A1", &headers); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iot_excelize.ApplyHeaderColumnsTextStyle(f); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.SetColWidth("Sheet1", "A", "B", 24); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.SetColWidth("Sheet1", "C", "C", 32); err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := "sim_card_add_package_template"
|
||||
if s.operation == OperationPool {
|
||||
fileName = "sim_pool_add_package_template"
|
||||
}
|
||||
return iot_excelize.WriteHTTP(ctx, f, fileName)
|
||||
}
|
||||
|
||||
func (s *Submitter) SubmitExcel(ctx context.Context, file *ghttp.UploadFile, options SubmitOptions) (*SubmitResult, error) {
|
||||
options.AutoConfirm = !options.ManualConfirm
|
||||
processor, err := NewPrepareProcessor(s.operation, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
File: file,
|
||||
BizParams: &processor.inner.options,
|
||||
SourceType: consts.BatchSourceTypeFile,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingUpload(ctx, req, processor, batchimport.PendingOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, string(s.operation)+"_add_package_prepare")
|
||||
res := pendingResultFromBatch(result, consts.BatchTypePrepare)
|
||||
res.TargetBatchType = processor.targetBatchType
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Submitter) SubmitInput(ctx context.Context, input string, options SubmitOptions) (*SubmitResult, error) {
|
||||
// Keep the legacy input/submit endpoint from bypassing async validation.
|
||||
return s.PrepareInput(ctx, input, options)
|
||||
}
|
||||
|
||||
func (s *Submitter) PrepareInput(ctx context.Context, input string, options SubmitOptions) (*SubmitResult, error) {
|
||||
if strings.TrimSpace(options.PackageCode) != "" || strings.TrimSpace(options.EffectiveMode) != "" {
|
||||
options.DefaultEffectiveMode = true
|
||||
}
|
||||
processor, err := NewPrepareProcessor(s.operation, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := parseInputRows(ctx, input, processor.inner.options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
BizParams: &processor.inner.options,
|
||||
SourceType: consts.BatchSourceTypeBatchInput,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingRows(ctx, req, processor, rows, batchimport.PendingOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, string(s.operation)+"_add_package_prepare")
|
||||
res := pendingResultFromBatch(result, consts.BatchTypePrepare)
|
||||
res.TargetBatchType = processor.targetBatchType
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func pendingResultFromBatch(result *batchimport.Result, batchType int) *SubmitResult {
|
||||
res := &SubmitResult{
|
||||
BatchType: batchType,
|
||||
BatchAsyncStatus: consts.BatchAsyncStatusPending,
|
||||
}
|
||||
if result == nil {
|
||||
return res
|
||||
}
|
||||
valid := result.ValidRows
|
||||
if result.TotalRows > 0 && valid == 0 {
|
||||
valid = result.TotalRows - result.ErrorRows
|
||||
if valid < 0 {
|
||||
valid = 0
|
||||
}
|
||||
}
|
||||
res.BatchNo = result.BatchNo
|
||||
res.BatchType = result.BatchType
|
||||
if batchType != result.BatchType {
|
||||
res.TargetBatchType = batchType
|
||||
}
|
||||
res.Total = result.TotalRows
|
||||
res.ValidCount = valid
|
||||
res.FailCount = result.ErrorRows
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package addpackage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
const (
|
||||
OperationCard Operation = "card"
|
||||
OperationPool Operation = "pool"
|
||||
)
|
||||
|
||||
type Operation string
|
||||
|
||||
type Config struct {
|
||||
Operation Operation
|
||||
Code string
|
||||
BatchType int
|
||||
BatchPrefix string
|
||||
TaskName string
|
||||
ExportScene int
|
||||
ImportDir string
|
||||
ResultDir string
|
||||
TargetHeader string
|
||||
TargetLabel string
|
||||
}
|
||||
|
||||
type SubmitOptions struct {
|
||||
PackageCode string
|
||||
Remark string
|
||||
PaymentMode string
|
||||
OfflineContractCompleted bool
|
||||
EffectiveMode string
|
||||
DefaultEffectiveMode bool
|
||||
ManualConfirm bool
|
||||
AutoConfirm bool
|
||||
}
|
||||
|
||||
type SubmitResult struct {
|
||||
BatchNo string
|
||||
BatchType int
|
||||
TargetBatchType int
|
||||
BatchAsyncStatus int
|
||||
Total int
|
||||
ValidCount int
|
||||
FailCount int
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
RowNo int
|
||||
TargetCode string
|
||||
PackageCode string
|
||||
Remark string
|
||||
EffectiveMode string
|
||||
Authorized bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (op Operation) Config() (Config, error) {
|
||||
switch op {
|
||||
case OperationCard:
|
||||
return Config{
|
||||
Operation: op,
|
||||
Code: "sim_card_add_package",
|
||||
BatchType: consts.BatchTypeCardAddPackage,
|
||||
BatchPrefix: consts.BatchNoPrefixCardAddPackage,
|
||||
TaskName: consts.ResourceBatchDownloadTaskSimCardAddPackage,
|
||||
ExportScene: consts.ResourceExportSceneCardAddPackage,
|
||||
ImportDir: consts.SimCardAddPackageSubDirImportExcel,
|
||||
ResultDir: consts.SimCardAddPackageSubDirResultExcel,
|
||||
TargetHeader: "ICCID",
|
||||
TargetLabel: "ICCID",
|
||||
}, nil
|
||||
case OperationPool:
|
||||
return Config{
|
||||
Operation: op,
|
||||
Code: "sim_pool_add_package",
|
||||
BatchType: consts.BatchTypePoolAddPackage,
|
||||
BatchPrefix: consts.BatchNoPrefixPoolAddPackage,
|
||||
TaskName: consts.ResourceBatchDownloadTaskSimPoolAddPackage,
|
||||
ExportScene: consts.ResourceExportScenePoolAddPackage,
|
||||
ImportDir: consts.SimPoolAddPackageSubDirImportExcel,
|
||||
ResultDir: consts.SimPoolAddPackageSubDirResultExcel,
|
||||
TargetHeader: "流量池",
|
||||
TargetLabel: "流量池",
|
||||
}, nil
|
||||
default:
|
||||
return Config{}, gerror.New("加油包操作类型不支持")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Row) GetRowNo() int {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
return r.RowNo
|
||||
}
|
||||
|
||||
func (r *Row) GetItemID() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(r.TargetCode)
|
||||
}
|
||||
|
||||
func (r *Row) GetErrorReasons() []string {
|
||||
if r == nil || strings.TrimSpace(r.Reason) == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{strings.TrimSpace(r.Reason)}
|
||||
}
|
||||
|
||||
func (r *Row) AddErrorReason(reason string) {
|
||||
if r == nil || strings.TrimSpace(r.Reason) != "" || strings.TrimSpace(reason) == "" {
|
||||
return
|
||||
}
|
||||
r.Reason = strings.TrimSpace(reason)
|
||||
}
|
||||
|
||||
func (r *Row) Snapshot() *gjson.Json {
|
||||
if r == nil {
|
||||
return gjson.New(g.Map{})
|
||||
}
|
||||
data := g.Map{
|
||||
"targetCode": strings.TrimSpace(r.TargetCode),
|
||||
"packageCode": strings.TrimSpace(r.PackageCode),
|
||||
"remark": strings.TrimSpace(r.Remark),
|
||||
"authorized": r.Authorized,
|
||||
}
|
||||
if mode := strings.TrimSpace(r.EffectiveMode); mode != "" {
|
||||
data["effectiveMode"] = mode
|
||||
data["effectiveModeText"] = EffectiveModeText(mode)
|
||||
}
|
||||
return gjson.New(data)
|
||||
}
|
||||
|
||||
const (
|
||||
EffectiveModeCurrentMonth = "current_month"
|
||||
)
|
||||
|
||||
func NormalizeEffectiveMode(mode string) (string, error) {
|
||||
switch strings.TrimSpace(mode) {
|
||||
case "", "immediate", "current_month", "this_month", "本月", "本月生效", "当月", "当月生效":
|
||||
return EffectiveModeCurrentMonth, nil
|
||||
case "next_month", "nextMonth", "下月", "次月", "次月生效":
|
||||
return "", gerror.New("加油包只支持本月生效")
|
||||
default:
|
||||
return "", gerror.New("加油包生效方式不正确")
|
||||
}
|
||||
}
|
||||
|
||||
func EffectiveModeText(mode string) string {
|
||||
_, _ = NormalizeEffectiveMode(mode)
|
||||
return "本月生效"
|
||||
}
|
||||
Reference in New Issue
Block a user