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 }