package service import ( "errors" "time" "gorm.io/gorm" "gorm.io/gorm/clause" "laic-backend/common" "laic-backend/logger" "laic-backend/model" "laic-backend/tool" "laic-backend/vo" ) type PlatformBillingService struct{} var DefaultPlatformBillingService = &PlatformBillingService{} func (s *PlatformBillingService) GetPool() (*model.PlatformResourcePool, *common.BusiError) { var pool model.PlatformResourcePool err := common.DB.Order("id ASC").First(&pool).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, common.ErrNotFound } if err != nil { logger.ERROR("查询平台资源池失败", err) return nil, common.ErrInternal } return &pool, nil } func (s *PlatformBillingService) CreatePurchase(adminID int64, req *vo.PlatformPurchaseCreateReq) (*model.PlatformResourcePurchase, *common.BusiError) { var purchase model.PlatformResourcePurchase now := time.Now() err := common.DB.Transaction(func(tx *gorm.DB) error { if err := tx.Where("batch_no = ?", req.BatchNo).First(&purchase).Error; err == nil { if purchase.Provider == req.Provider && purchase.TotalBytes == req.TotalBytes && purchase.TotalCost == req.TotalCost { return gorm.ErrDuplicatedKey } return common.NewBusiError(common.ParamError, "采购批次号已存在且内容不一致") } else if !errors.Is(err, gorm.ErrRecordNotFound) { return err } id, err := tool.NextID() if err != nil { return err } purchase = model.PlatformResourcePurchase{ ID: id, BatchNo: req.BatchNo, Provider: req.Provider, TotalBytes: req.TotalBytes, UnitCost: req.UnitCost, TotalCost: req.TotalCost, ExpiresAt: req.ExpiresAt, Status: "active", Remark: req.Remark, CreatedBy: adminID, CreatedAt: now, UpdatedAt: now, } if err := tx.Create(&purchase).Error; err != nil { return err } var pool model.PlatformResourcePool err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).Order("id ASC").First(&pool).Error if errors.Is(err, gorm.ErrRecordNotFound) { pool = model.PlatformResourcePool{ID: mustPlatformID(), Status: "active", CreatedAt: now, UpdatedAt: now} if pool.ID == 0 { return common.ErrInternal } if err := tx.Create(&pool).Error; err != nil { return err } err = nil } if err != nil { return err } before := pool.AvailableBytes if err := tx.Model(&pool).Updates(map[string]any{ "total_bytes": gorm.Expr("total_bytes + ?", req.TotalBytes), "available_bytes": gorm.Expr("available_bytes + ?", req.TotalBytes), "version": gorm.Expr("version + 1"), "updated_at": now, }).Error; err != nil { return err } return tx.Create(&model.TrafficLedger{ ID: mustPlatformID(), AccountType: "platform", AccountID: pool.ID, Direction: "credit", AmountBytes: req.TotalBytes, BalanceBefore: before, BalanceAfter: before + req.TotalBytes, SourceType: "purchase", SourceID: req.BatchNo, IdempotencyKey: "purchase:" + req.BatchNo, OperatorID: adminID, CreatedAt: now, }).Error }) if err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { return &purchase, nil } if busiErr, ok := err.(*common.BusiError); ok { return nil, busiErr } logger.ERROR("录入平台资源采购失败", err) return nil, common.ErrInternal } return &purchase, nil } func (s *PlatformBillingService) GetPurchasePage(req *vo.PlatformPurchasePageReq) (*common.PageResponse[model.PlatformResourcePurchase], *common.BusiError) { db := common.DB.Model(&model.PlatformResourcePurchase{}) if req.Status != "" { db = db.Where("status = ?", req.Status) } if req.Provider != "" { db = db.Where("provider = ?", req.Provider) } if req.Keyword != "" { db = db.Where("batch_no LIKE ?", "%"+req.Keyword+"%") } var total int64 if err := db.Count(&total).Error; err != nil { return nil, common.ErrInternal } var list []model.PlatformResourcePurchase if err := db.Scopes(req.Paginate).Order("created_at DESC, id DESC").Find(&list).Error; err != nil { return nil, common.ErrInternal } return common.Page(req.Pagination, total, list), nil } func (s *PlatformBillingService) CreatePackage(req *vo.TrafficPackageCreateReq) (*model.TrafficPackage, *common.BusiError) { id, err := tool.NextID() if err != nil { return nil, common.ErrInternal } now := time.Now() pkg := &model.TrafficPackage{ID: id, Code: req.Code, Name: req.Name, AmountBytes: req.AmountBytes, Price: req.Price, ValidityDays: req.ValidityDays, Status: "active", Sort: req.Sort, CreatedAt: now, UpdatedAt: now} if err := common.DB.Create(pkg).Error; err != nil { if code, _ := common.ParseError(err); code == 1062 { return nil, common.NewBusiError(common.ParamError, "套餐编码已存在") } return nil, common.ErrInternal } return pkg, nil } func (s *PlatformBillingService) GetPackagePage(req *vo.TrafficPackagePageReq) (*common.PageResponse[model.TrafficPackage], *common.BusiError) { db := common.DB.Model(&model.TrafficPackage{}) if req.Status != "" { db = db.Where("status = ?", req.Status) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, common.ErrInternal } var list []model.TrafficPackage if err := db.Scopes(req.Paginate).Order("sort ASC, id DESC").Find(&list).Error; err != nil { return nil, common.ErrInternal } return common.Page(req.Pagination, total, list), nil } func (s *PlatformBillingService) ListActivePackages() ([]vo.TrafficPackageVO, *common.BusiError) { var packages []vo.TrafficPackageVO if err := common.DB.Model(&model.TrafficPackage{}). Select("id, code, name, amount_bytes, price, validity_days, sort"). Where("status = ?", "active"). Order("sort ASC, id DESC"). Find(&packages).Error; err != nil { return nil, common.ErrInternal } return packages, nil } func (s *PlatformBillingService) UpdatePackage(id int64, req *vo.TrafficPackageUpdateReq) *common.BusiError { updates := map[string]any{"updated_at": time.Now(), "sort": req.Sort} if req.Name != "" { updates["name"] = req.Name } if req.AmountBytes > 0 { updates["amount_bytes"] = req.AmountBytes } if req.Price > 0 { updates["price"] = req.Price } if req.ValidityDays > 0 { updates["validity_days"] = req.ValidityDays } if req.Status != "" { updates["status"] = req.Status } res := common.DB.Model(&model.TrafficPackage{}).Where("id = ?", id).Updates(updates) if res.Error != nil { return common.ErrInternal } if res.RowsAffected == 0 { return common.ErrNotFound } return nil } func (s *PlatformBillingService) DisablePackage(id int64) *common.BusiError { res := common.DB.Model(&model.TrafficPackage{}).Where("id = ?", id).Updates(map[string]any{"status": "inactive", "updated_at": time.Now()}) if res.Error != nil { return common.ErrInternal } if res.RowsAffected == 0 { return common.ErrNotFound } return nil } func (s *PlatformBillingService) GetLedgerPage(req *vo.TrafficLedgerPageReq) (*common.PageResponse[model.TrafficLedger], *common.BusiError) { db := common.DB.Model(&model.TrafficLedger{}) if req.AccountType != "" { db = db.Where("account_type = ?", req.AccountType) } if req.AccountID > 0 { db = db.Where("account_id = ?", req.AccountID) } if req.SourceType != "" { db = db.Where("source_type = ?", req.SourceType) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, common.ErrInternal } var list []model.TrafficLedger if err := db.Scopes(req.Paginate).Order("created_at DESC, id DESC").Find(&list).Error; err != nil { return nil, common.ErrInternal } return common.Page(req.Pagination, total, list), nil } func mustPlatformID() int64 { id, err := tool.NextID() if err != nil { logger.ERROR("生成平台账务 ID 失败", err) return 0 } return id }