// Package gen // @Link https://github.com/xiujiecn/xiu-admin // @Copyright Copyright (c) 2026 LiXiujie // @Author Lxj // @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE // @AutoGenerate Version package iot import ( "context" "fmt" "math" "sort" "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/contexts" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/addpackage" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" ) type sIotTSimPoolBindPackage struct{} func NewIotTSimPoolBindPackage() *sIotTSimPoolBindPackage { return &sIotTSimPoolBindPackage{} } func (s *sIotTSimPoolBindPackage) DownloadExcelTemplate(ctx context.Context) error { return addpackage.NewPoolSubmitter().DownloadTemplate(ctx) } func (s *sIotTSimPoolBindPackage) SubmitExcel(ctx context.Context, in *model.TSimPoolBindPackageAddPackageExcelSubmitParam) (*model.TSimPoolBindPackageAddPackageExcelSubmitRes, error) { if in == nil { in = &model.TSimPoolBindPackageAddPackageExcelSubmitParam{} } file, err := poolAddPackageUploadFile(ctx) if err != nil { return nil, err } paymentMode, offlineContractCompleted, err := normalizeAddPackagePaymentMode(ctx, in.PaymentMode, in.OfflineContractCompleted) if err != nil { return nil, err } result, err := addpackage.NewPoolSubmitter().SubmitExcel(ctx, file, addpackage.SubmitOptions{ PaymentMode: paymentMode, OfflineContractCompleted: offlineContractCompleted, ManualConfirm: in.ManualConfirm, }) if err != nil { return nil, err } return poolAddPackageSubmitRes(result), nil } func (s *sIotTSimPoolBindPackage) SubmitInput(ctx context.Context, in *model.TSimPoolBindPackageAddPackageInputSubmitParam) (*model.TSimPoolBindPackageAddPackageInputSubmitRes, error) { if in == nil { in = &model.TSimPoolBindPackageAddPackageInputSubmitParam{} } paymentMode, offlineContractCompleted, err := normalizeAddPackagePaymentMode(ctx, in.PaymentMode, in.OfflineContractCompleted) if err != nil { return nil, err } result, err := addpackage.NewPoolSubmitter().SubmitInput(ctx, in.Input, addpackage.SubmitOptions{ PackageCode: in.PackageCode, Remark: in.Remark, PaymentMode: paymentMode, OfflineContractCompleted: offlineContractCompleted, EffectiveMode: in.EffectiveMode, }) if err != nil { return nil, err } return (*model.TSimPoolBindPackageAddPackageInputSubmitRes)(poolAddPackageSubmitRes(result)), nil } func poolAddPackageUploadFile(ctx context.Context) (*ghttp.UploadFile, error) { r := g.RequestFromCtx(ctx) if r == nil { return nil, gerror.New("当前上下文不是HTTP请求") } file := r.GetUploadFile("file") if file == nil { return nil, gerror.New("请选择上传文件") } return file, nil } func poolAddPackageSubmitRes(result *addpackage.SubmitResult) *model.TSimPoolBindPackageAddPackageBatchSubmitRes { res := &model.TSimPoolBindPackageAddPackageBatchSubmitRes{} if result == nil { return res } res.BatchNo = result.BatchNo res.BatchType = result.BatchType res.TargetBatchType = result.TargetBatchType res.BatchAsyncStatus = result.BatchAsyncStatus res.Total = result.Total res.ValidCount = result.ValidCount res.FailCount = result.FailCount return res } func init() { service.RegisterIotTSimPoolBindPackage(NewIotTSimPoolBindPackage()) } // Model 卡池加油包订购表ORM模型 func (s *sIotTSimPoolBindPackage) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { if len(option) == 0 { option = append(option, &handler.Option{ FilterTenant: true, FilterAuth: true, }) } return handler.Model(dao.TSimPoolBindPackage.Ctx(ctx), option...) } // List 获取卡池加油包订购表列表 func (s *sIotTSimPoolBindPackage) List(ctx context.Context, in *model.TSimPoolBindPackageListParam) (list []*model.TSimPoolBindPackageListModel, totalCount int, err error) { mod := s.Model(ctx) // 字段过滤 mod = mod.Fields(model.TSimPoolBindPackageListModel{}) // 查询主键ID if in.Id > 0 { mod = mod.Where(dao.TSimPoolBindPackage.Columns().Id, in.Id) } // 查询订购状态 枚举 if in.EffectiveStatus > 0 { mod = mod.Where(dao.TSimPoolBindPackage.Columns().EffectiveStatus, in.EffectiveStatus) } // 查询创建时间 if len(in.CreatedAt) == 2 { in.CreatedAt[1] = in.CreatedAt[1].EndOfDay() } if len(in.CreatedAt) == 2 { mod = mod.WhereBetween(dao.TSimPoolBindPackage.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1]) } // 分页 mod = mod.Page(in.Page, in.PageSize) // 排序 mod = mod.OrderDesc(dao.TSimPoolBindPackage.Columns().Id) // 操作人摘要信息 mod = mod.Hook(hook.MemberSummary) // 查询数据 if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { err = gerror.Wrap(err, "获取卡池加油包订购表列表失败,请稍后重试!") return } return } // Export 导出卡池加油包订购表 func (s *sIotTSimPoolBindPackage) Export(ctx context.Context, in *model.TSimPoolBindPackageListParam) (err error) { list, totalCount, err := s.List(ctx, in) if err != nil { return } // 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...} tags, err := convert.GetEntityDescTags(model.TSimPoolBindPackageExportModel{}) if err != nil { return } var ( fileName = "导出卡池加油包订购表-" + gctx.CtxId(ctx) sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list)) exports []model.TSimPoolBindPackageExportModel ) if err = gconv.Scan(list, &exports); err != nil { return } err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName) return } // Edit 修改/新增卡池加油包订购表 func (s *sIotTSimPoolBindPackage) Edit(ctx context.Context, in *model.TSimPoolBindPackageEditParam) (err error) { // 验证'SubscriptionNo'唯一 if err = xgorm.IsUnique(ctx, &dao.TSimPoolBindPackage, g.Map{dao.TSimPoolBindPackage.Columns().SubscriptionNo: in.SubscriptionNo}, "订购记录编号已存在", in.Id); err != nil { return } return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { // 修改 if in.Id > 0 { updatedBy := contexts.GetUserId(ctx) in.UpdatedBy = &updatedBy in.UpdatedAt = gtime.Now() if _, err = s.Model(ctx). Fields(model.TSimPoolBindPackageUpdateFields{}). WherePri(in.Id).Data(in).Update(); err != nil { err = gerror.Wrap(err, "修改卡池加油包订购表失败,请稍后重试!") } return } // 新增 tenantId := contexts.GetTenantId(ctx) in.TenantId = &tenantId createdDept := contexts.GetDeptId(ctx) in.CreatedDept = &createdDept createdBy := contexts.GetUserId(ctx) in.CreatedBy = &createdBy in.CreatedAt = gtime.Now() if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}). Fields(model.TSimPoolBindPackageInsertFields{}). Data(in).Insert(); err != nil { err = gerror.Wrap(err, "新增卡池加油包订购表失败,请稍后重试!") } return }) } // Delete 删除卡池加油包订购表 func (s *sIotTSimPoolBindPackage) Delete(ctx context.Context, in *model.TSimPoolBindPackageDeleteParam) (err error) { if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil { err = gerror.Wrap(err, "删除卡池加油包订购表失败,请稍后重试!") return } return } // View 获取卡池加油包订购表指定信息 func (s *sIotTSimPoolBindPackage) View(ctx context.Context, in *model.TSimPoolBindPackageViewParam) (res *model.TSimPoolBindPackageViewModel, err error) { if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil { err = gerror.Wrap(err, "获取卡池加油包订购表信息,请稍后重试!") return } return } // AddPackagePaymentContext 获取当前组织的加油包支付上下文。 func (s *sIotTSimPoolBindPackage) AddPackagePaymentContext(ctx context.Context) (*model.TSimAddPackageOptionsRes, error) { return queryAddPackagePaymentContext(ctx) } // AddPackageOptions 获取当前流量池共同可订购的加油包套餐。 func (s *sIotTSimPoolBindPackage) AddPackageOptions(ctx context.Context, in *model.TSimPoolAddPackageOptionsParam) (*model.TSimAddPackageOptionsRes, error) { if in == nil || len(in.FlowPoolCodes) == 0 { return nil, gerror.New("流量池编码不能为空") } poolCodes := uniqueNonEmptyStrings(in.FlowPoolCodes) if len(poolCodes) == 0 { return nil, gerror.New("流量池编码不能为空") } poolColumns := dao.TSimFlowPool.Columns() var pools []struct { FlowPoolCode string `orm:"flow_pool_code"` PoolPackageCode int64 `orm:"pool_package_code"` PoolStatus int `orm:"pool_status"` DeptId int64 `orm:"dept_id"` } if err := sPoolModel(ctx). Fields(poolColumns.FlowPoolCode, poolColumns.PoolPackageCode, poolColumns.PoolStatus, poolColumns.TenantId, poolColumns.DeptId, poolColumns.CreatedDept, poolColumns.CreatedBy). WhereIn(poolColumns.FlowPoolCode, poolCodes). Scan(&pools); err != nil { return nil, gerror.Wrap(err, "查询流量池信息失败") } poolMap := make(map[string]struct { poolPackageCode int64 poolStatus int deptID int64 }, len(pools)) for _, pool := range pools { code := strings.TrimSpace(pool.FlowPoolCode) if code != "" { poolMap[code] = struct { poolPackageCode int64 poolStatus int deptID int64 }{poolPackageCode: pool.PoolPackageCode, poolStatus: pool.PoolStatus, deptID: pool.DeptId} } } poolPackageCodes := make([]int64, 0, len(poolCodes)) seenPoolPackages := make(map[int64]struct{}, len(poolCodes)) for _, code := range poolCodes { pool, ok := poolMap[code] if !ok { return nil, gerror.Newf("流量池 %s 不存在或无权限", code) } if pool.poolStatus != 0 { return nil, gerror.Newf("流量池 %s 当前状态不可订购加油包", code) } if pool.poolPackageCode <= 0 { return nil, gerror.Newf("流量池 %s 未配置基础套餐", code) } if pool.deptID <= 0 { return nil, gerror.Newf("流量池 %s 未绑定客户组织", code) } if _, ok := seenPoolPackages[pool.poolPackageCode]; !ok { seenPoolPackages[pool.poolPackageCode] = struct{}{} poolPackageCodes = append(poolPackageCodes, pool.poolPackageCode) } } if err := ensureNoActivePoolTopupPackages(ctx, poolCodes); err != nil { return nil, err } packageColumns := dao.TSimFlowPackageInfo.Columns() var packages []*entity.TSimFlowPackageInfo if err := handler.Model(dao.TSimFlowPackageInfo.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: false}). Fields(packageColumns.PackageCode, packageColumns.PackageName, packageColumns.FlowMb, packageColumns.PoolPackageCode). WhereIn(packageColumns.PoolPackageCode, poolPackageCodes). Where(packageColumns.PackageType, consts.PackageTypePoolAddPackage). Where(packageColumns.Enabled, 1). OrderAsc(packageColumns.SortNo). Scan(&packages); err != nil { return nil, gerror.Wrap(err, "查询流量池加油包套餐失败") } availableByPool := make(map[int64]map[string]*entity.TSimFlowPackageInfo, len(poolPackageCodes)) for _, pkg := range packages { if pkg == nil { continue } code := strings.TrimSpace(pkg.PackageCode) if code == "" { continue } if availableByPool[pkg.PoolPackageCode] == nil { availableByPool[pkg.PoolPackageCode] = make(map[string]*entity.TSimFlowPackageInfo) } if _, exists := availableByPool[pkg.PoolPackageCode][code]; !exists { availableByPool[pkg.PoolPackageCode][code] = pkg } } commonCodes := make(map[string]*entity.TSimFlowPackageInfo) for index, poolPackageCode := range poolPackageCodes { available := availableByPool[poolPackageCode] if index == 0 { for code, pkg := range available { commonCodes[code] = pkg } continue } for code := range commonCodes { if _, ok := available[code]; !ok { delete(commonCodes, code) } } } if len(commonCodes) == 0 { return nil, gerror.New("所选流量池没有共同可购的加油包套餐") } commonCodeList := make([]string, 0, len(commonCodes)) for code := range commonCodes { commonCodeList = append(commonCodeList, code) } sort.Strings(commonCodeList) isTopDept, _, err := service.AgentDept().IsTopDept(ctx) if err != nil { return nil, gerror.Wrap(err, "判断当前组织类型失败") } availableBalance, err := queryAddPackageAvailableBalance(ctx, contexts.GetDeptId(ctx)) if err != nil { return nil, err } items := make([]*model.TSimAddPackageOptionModel, 0, len(commonCodeList)) for _, code := range commonCodeList { pkg := commonCodes[code] var totalAmount float64 var unitPrice float64 priceAvailable := true uniformPrice := true paymentAccounts := make(map[int64]*model.TSimAddPackagePaymentAccountModel) for index, targetCode := range poolCodes { customerDeptID := poolMap[targetCode].deptID priceOwnerDeptID, err := loadPackagePriceParentDeptID(ctx, nil, contexts.GetTenantId(ctx), customerDeptID) if err != nil { return nil, err } salePrice, err := resolvePackageEffectiveSalePrice(ctx, nil, contexts.GetTenantId(ctx), priceOwnerDeptID, customerDeptID, packagePriceSourceFromEntity(pkg)) if err != nil { priceAvailable = false break } if index == 0 { unitPrice = salePrice.AgentShipmentPrice } else if math.Abs(unitPrice-salePrice.AgentShipmentPrice) > 0.0001 { uniformPrice = false } totalAmount = roundAmount(totalAmount + salePrice.AgentShipmentPrice) account := paymentAccounts[customerDeptID] if account == nil { balance, err := queryAddPackageAvailableBalance(ctx, customerDeptID) if err != nil { return nil, err } account = &model.TSimAddPackagePaymentAccountModel{ PayerDeptId: customerDeptID, AvailableBalance: balance, } paymentAccounts[customerDeptID] = account } account.TargetCount++ account.RequiredAmount = roundAmount(account.RequiredAmount + salePrice.AgentShipmentPrice) } paymentAccountItems := make([]*model.TSimAddPackagePaymentAccountModel, 0, len(paymentAccounts)) for _, account := range paymentAccounts { account.BalanceEnough = account.AvailableBalance+0.0001 >= account.RequiredAmount paymentAccountItems = append(paymentAccountItems, account) } if !uniformPrice { unitPrice = 0 } items = append(items, &model.TSimAddPackageOptionModel{ PackageCode: code, PackageName: strings.TrimSpace(pkg.PackageName), FlowMb: pkg.FlowMb, UnitPrice: unitPrice, TotalAmount: totalAmount, PriceAvailable: priceAvailable, PaymentAccounts: paymentAccountItems, }) } return &model.TSimAddPackageOptionsRes{ Items: items, IsTopDept: isTopDept, DefaultPaymentMode: addPackageDefaultPaymentMode(isTopDept), AvailableBalance: availableBalance, }, nil } func sPoolModel(ctx context.Context) *gdb.Model { return handler.Model(dao.TSimFlowPool.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}) }