Files
iotlcm/server/internal/logic/iot/t_sim_pool_bind_package.go
T
2026-10-29 14:36:49 +08:00

459 lines
16 KiB
Go

// Package gen
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2026 LiXiujie
// @Author Lxj <xxx@example.com>
// @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})
}