diff --git a/internal/app/app.go b/internal/app/app.go index 2bf963d..9ad2d9a 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -269,6 +269,9 @@ func (a *Application) autoMigrate(db *gorm.DB) error { &subordinateEntities.SubordinateInvitation{}, &subordinateEntities.UserSubordinateLink{}, &subordinateEntities.SubordinateWalletAllocation{}, + &subordinateEntities.SubordinateQuotaPurchase{}, + &subordinateEntities.UserProductQuotaAccount{}, + &subordinateEntities.UserProductQuotaLedger{}, // 任务域 &taskEntities.AsyncTask{}, diff --git a/internal/application/subordinate/dto/commands/subordinate_commands.go b/internal/application/subordinate/dto/commands/subordinate_commands.go index 6e4bdf8..83888a0 100644 --- a/internal/application/subordinate/dto/commands/subordinate_commands.go +++ b/internal/application/subordinate/dto/commands/subordinate_commands.go @@ -53,3 +53,26 @@ type RemoveChildSubscriptionCommand struct { ChildUserID string `json:"child_user_id" binding:"required"` SubscriptionID string `json:"subscription_id" binding:"required"` } + +// PurchaseChildQuotaCommand 主账号为子账号购买调用额度 +type PurchaseChildQuotaCommand struct { + ParentUserID string + ChildUserID string `json:"child_user_id" binding:"required"` + ProductID string `json:"product_id" binding:"required"` + CallCount int64 `json:"call_count" binding:"required,min=1"` + VerifyCode string `json:"verify_code" binding:"required,len=6"` +} + +// ListChildQuotaPurchasesCommand 下属额度购买记录查询 +type ListChildQuotaPurchasesCommand struct { + ParentUserID string + ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"` + Page int `json:"page" form:"page"` + PageSize int `json:"page_size" form:"page_size"` +} + +// ListChildQuotaAccountsCommand 下属额度账户查询 +type ListChildQuotaAccountsCommand struct { + ParentUserID string + ChildUserID string `json:"child_user_id" form:"child_user_id" binding:"required"` +} diff --git a/internal/application/subordinate/dto/responses/subordinate_responses.go b/internal/application/subordinate/dto/responses/subordinate_responses.go index 1e15694..0aaefd7 100644 --- a/internal/application/subordinate/dto/responses/subordinate_responses.go +++ b/internal/application/subordinate/dto/responses/subordinate_responses.go @@ -55,3 +55,28 @@ type ChildSubscriptionItem struct { UIComponentPrice string `json:"ui_component_price"` CreatedAt time.Time `json:"created_at"` } + +// ChildQuotaPurchaseItem 下属额度购买记录 +type ChildQuotaPurchaseItem struct { + ID string `json:"id"` + ProductID string `json:"product_id"` + CallCount int64 `json:"call_count"` + UnitPrice string `json:"unit_price"` + TotalAmount string `json:"total_amount"` + BusinessRef string `json:"business_ref"` + CreatedAt time.Time `json:"created_at"` +} + +// ChildQuotaPurchaseListResponse 下属额度购买记录列表 +type ChildQuotaPurchaseListResponse struct { + Total int64 `json:"total"` + Items []ChildQuotaPurchaseItem `json:"items"` +} + +// ChildQuotaAccountItem 下属产品额度账户 +type ChildQuotaAccountItem struct { + ProductID string `json:"product_id"` + TotalQuota int64 `json:"total_quota"` + UsedQuota int64 `json:"used_quota"` + AvailableQuota int64 `json:"available_quota"` +} diff --git a/internal/application/subordinate/subordinate_application_service.go b/internal/application/subordinate/subordinate_application_service.go index 822bc34..39adb5b 100644 --- a/internal/application/subordinate/subordinate_application_service.go +++ b/internal/application/subordinate/subordinate_application_service.go @@ -17,4 +17,8 @@ type SubordinateApplicationService interface { AssignChildSubscription(ctx context.Context, cmd *commands.AssignChildSubscriptionCommand) error ListChildSubscriptions(ctx context.Context, cmd *commands.ListChildSubscriptionsCommand) ([]responses.ChildSubscriptionItem, error) RemoveChildSubscription(ctx context.Context, cmd *commands.RemoveChildSubscriptionCommand) error + PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error + ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error) + ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error) + ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error) } diff --git a/internal/application/subordinate/subordinate_application_service_impl.go b/internal/application/subordinate/subordinate_application_service_impl.go index 7685c52..dc5d07d 100644 --- a/internal/application/subordinate/subordinate_application_service_impl.go +++ b/internal/application/subordinate/subordinate_application_service_impl.go @@ -391,3 +391,218 @@ func (s *SubordinateApplicationServiceImpl) RemoveChildSubscription(ctx context. } return s.productSub.CancelSubscription(ctx, cmd.SubscriptionID) } + +// PurchaseChildQuota 主账号为子账号购买调用额度(按子账号订阅价结算) +func (s *SubordinateApplicationServiceImpl) PurchaseChildQuota(ctx context.Context, cmd *commands.PurchaseChildQuotaCommand) error { + if cmd.CallCount <= 0 { + return fmt.Errorf("购买次数必须大于0") + } + + parentUser, err := s.userRepo.GetByID(ctx, cmd.ParentUserID) + if err != nil { + return fmt.Errorf("主账号信息获取失败") + } + if err := s.smsService.VerifyCode(ctx, parentUser.Phone, strings.TrimSpace(cmd.VerifyCode), user_entities.SMSSceneLogin); err != nil { + return fmt.Errorf("验证码错误或已过期") + } + + lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID) + if err != nil { + return err + } + if lnk == nil || lnk.Status != subentities.LinkStatusActive { + return fmt.Errorf("该用户不是您的有效下属") + } + + parentSub, err := s.productSub.GetUserSubscribedProduct(ctx, cmd.ParentUserID, cmd.ProductID) + if err != nil { + return err + } + if parentSub == nil { + return fmt.Errorf("主账号未订阅该产品,无法购买额度") + } + if !parentSub.Price.GreaterThan(decimal.Zero) { + return fmt.Errorf("主账号订阅价格异常,无法购买额度") + } + + callCountDec := decimal.NewFromInt(cmd.CallCount) + totalAmount := parentSub.Price.Mul(callCountDec) + if !totalAmount.GreaterThan(decimal.Zero) { + return fmt.Errorf("购买金额必须大于0") + } + + bizRef := uuid.New().String() + return s.txm.ExecuteInTx(ctx, func(txCtx context.Context) error { + // 购买额度前自动确保子账号存在该产品订阅,并统一为主账号订阅价 + childSub, err := s.productSub.GetUserSubscribedProduct(txCtx, cmd.ChildUserID, cmd.ProductID) + if err != nil { + return err + } + if childSub == nil { + newSub := &productentities.Subscription{ + UserID: cmd.ChildUserID, + ProductID: cmd.ProductID, + Price: parentSub.Price, + UIComponentPrice: parentSub.UIComponentPrice, + } + if err := s.productSub.SaveSubscription(txCtx, newSub); err != nil { + return fmt.Errorf("为下属创建订阅失败: %w", err) + } + } else { + childSub.Price = parentSub.Price + childSub.UIComponentPrice = parentSub.UIComponentPrice + if err := s.productSub.SaveSubscription(txCtx, childSub); err != nil { + return fmt.Errorf("更新下属订阅失败: %w", err) + } + } + + ok, err := s.walletRepo.UpdateBalanceByUserID(txCtx, cmd.ParentUserID, totalAmount, "subtract") + if err != nil { + return err + } + if !ok { + return fmt.Errorf("主账号扣款失败,请重试") + } + + account, err := s.subRepo.FindQuotaAccount(txCtx, cmd.ChildUserID, cmd.ProductID) + if err != nil { + return err + } + + var beforeAvailable int64 + if account == nil { + account = &subentities.UserProductQuotaAccount{ + UserID: cmd.ChildUserID, + ProductID: cmd.ProductID, + TotalQuota: cmd.CallCount, + UsedQuota: 0, + AvailableQuota: cmd.CallCount, + } + beforeAvailable = 0 + if err := s.subRepo.CreateQuotaAccount(txCtx, account); err != nil { + return err + } + } else { + beforeAvailable = account.AvailableQuota + account.TotalQuota += cmd.CallCount + account.AvailableQuota += cmd.CallCount + if err := s.subRepo.UpdateQuotaAccount(txCtx, account); err != nil { + return err + } + } + + purchase := &subentities.SubordinateQuotaPurchase{ + ParentUserID: cmd.ParentUserID, + ChildUserID: cmd.ChildUserID, + ProductID: cmd.ProductID, + CallCount: cmd.CallCount, + UnitPrice: parentSub.Price, + TotalAmount: totalAmount, + BusinessRef: bizRef, + OperatorUserID: cmd.ParentUserID, + } + if err := s.subRepo.CreateQuotaPurchase(txCtx, purchase); err != nil { + return err + } + + ledger := &subentities.UserProductQuotaLedger{ + UserID: cmd.ChildUserID, + ProductID: cmd.ProductID, + ChangeType: subentities.QuotaLedgerChangeTypePurchaseForSub, + DeltaQuota: cmd.CallCount, + BeforeQuota: beforeAvailable, + AfterQuota: beforeAvailable + cmd.CallCount, + SourceID: purchase.ID, + OperatorID: cmd.ParentUserID, + Remark: "主账号为子账号购买额度", + } + return s.subRepo.CreateQuotaLedger(txCtx, ledger) + }) +} + +// ListChildQuotaPurchases 下属额度购买记录 +func (s *SubordinateApplicationServiceImpl) ListChildQuotaPurchases(ctx context.Context, cmd *commands.ListChildQuotaPurchasesCommand) (*responses.ChildQuotaPurchaseListResponse, error) { + lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID) + if err != nil { + return nil, err + } + if lnk == nil || lnk.Status != subentities.LinkStatusActive { + return nil, fmt.Errorf("该用户不是您的有效下属") + } + + page := cmd.Page + pageSize := cmd.PageSize + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 100 { + pageSize = 20 + } + offset := (page - 1) * pageSize + rows, total, err := s.subRepo.ListQuotaPurchasesByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID, pageSize, offset) + if err != nil { + return nil, err + } + + items := make([]responses.ChildQuotaPurchaseItem, 0, len(rows)) + for _, row := range rows { + items = append(items, responses.ChildQuotaPurchaseItem{ + ID: row.ID, + ProductID: row.ProductID, + CallCount: row.CallCount, + UnitPrice: row.UnitPrice.StringFixed(2), + TotalAmount: row.TotalAmount.StringFixed(2), + BusinessRef: row.BusinessRef, + CreatedAt: row.CreatedAt, + }) + } + + return &responses.ChildQuotaPurchaseListResponse{ + Total: total, + Items: items, + }, nil +} + +// ListChildQuotaAccounts 下属额度账户 +func (s *SubordinateApplicationServiceImpl) ListChildQuotaAccounts(ctx context.Context, cmd *commands.ListChildQuotaAccountsCommand) ([]responses.ChildQuotaAccountItem, error) { + lnk, err := s.subRepo.FindLinkByParentAndChild(ctx, cmd.ParentUserID, cmd.ChildUserID) + if err != nil { + return nil, err + } + if lnk == nil || lnk.Status != subentities.LinkStatusActive { + return nil, fmt.Errorf("该用户不是您的有效下属") + } + + accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, cmd.ChildUserID) + if err != nil { + return nil, err + } + items := make([]responses.ChildQuotaAccountItem, 0, len(accounts)) + for _, account := range accounts { + items = append(items, responses.ChildQuotaAccountItem{ + ProductID: account.ProductID, + TotalQuota: account.TotalQuota, + UsedQuota: account.UsedQuota, + AvailableQuota: account.AvailableQuota, + }) + } + return items, nil +} + +// ListMyQuotaAccounts 查询当前用户额度账户(通用能力,适配所有用户) +func (s *SubordinateApplicationServiceImpl) ListMyQuotaAccounts(ctx context.Context, userID string) ([]responses.ChildQuotaAccountItem, error) { + accounts, err := s.subRepo.ListQuotaAccountsByUser(ctx, userID) + if err != nil { + return nil, err + } + items := make([]responses.ChildQuotaAccountItem, 0, len(accounts)) + for _, account := range accounts { + items = append(items, responses.ChildQuotaAccountItem{ + ProductID: account.ProductID, + TotalQuota: account.TotalQuota, + UsedQuota: account.UsedQuota, + AvailableQuota: account.AvailableQuota, + }) + } + return items, nil +} diff --git a/internal/domains/subordinate/entities/quota.go b/internal/domains/subordinate/entities/quota.go new file mode 100644 index 0000000..fea8ab5 --- /dev/null +++ b/internal/domains/subordinate/entities/quota.go @@ -0,0 +1,96 @@ +package entities + +import ( + "time" + + "github.com/google/uuid" + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +const ( + // QuotaLedgerChangeTypePurchaseForSub 主账号为子账号购买额度 + QuotaLedgerChangeTypePurchaseForSub = "purchase_for_sub" +) + +// SubordinateQuotaPurchase 主账号为子账号购买额度记录 +type SubordinateQuotaPurchase struct { + ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` + ParentUserID string `gorm:"type:varchar(36);not null;index" json:"parent_user_id"` + ChildUserID string `gorm:"type:varchar(36);not null;index" json:"child_user_id"` + ProductID string `gorm:"type:varchar(36);not null;index" json:"product_id"` + CallCount int64 `gorm:"type:bigint;not null" json:"call_count"` + UnitPrice decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"unit_price"` + TotalAmount decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"total_amount"` + BusinessRef string `gorm:"type:varchar(64);not null;uniqueIndex" json:"business_ref"` + OperatorUserID string `gorm:"type:varchar(36);not null" json:"operator_user_id"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (SubordinateQuotaPurchase) TableName() string { + return "subordinate_quota_purchases" +} + +func (q *SubordinateQuotaPurchase) BeforeCreate(tx *gorm.DB) error { + if q.ID == "" { + q.ID = uuid.New().String() + } + return nil +} + +// UserProductQuotaAccount 用户产品额度账户(通用模型,适配所有用户) +type UserProductQuotaAccount struct { + ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` + UserID string `gorm:"type:varchar(36);not null;index:idx_user_product,unique" json:"user_id"` + ProductID string `gorm:"type:varchar(36);not null;index:idx_user_product,unique" json:"product_id"` + TotalQuota int64 `gorm:"type:bigint;not null;default:0" json:"total_quota"` + UsedQuota int64 `gorm:"type:bigint;not null;default:0" json:"used_quota"` + AvailableQuota int64 `gorm:"type:bigint;not null;default:0" json:"available_quota"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (UserProductQuotaAccount) TableName() string { + return "user_product_quota_accounts" +} + +func (a *UserProductQuotaAccount) BeforeCreate(tx *gorm.DB) error { + if a.ID == "" { + a.ID = uuid.New().String() + } + return nil +} + +// UserProductQuotaLedger 用户产品额度流水(通用模型,适配所有用户) +type UserProductQuotaLedger struct { + ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` + UserID string `gorm:"type:varchar(36);not null;index" json:"user_id"` + ProductID string `gorm:"type:varchar(36);not null;index" json:"product_id"` + ChangeType string `gorm:"type:varchar(50);not null;index" json:"change_type"` + DeltaQuota int64 `gorm:"type:bigint;not null" json:"delta_quota"` + BeforeQuota int64 `gorm:"type:bigint;not null" json:"before_quota"` + AfterQuota int64 `gorm:"type:bigint;not null" json:"after_quota"` + SourceID string `gorm:"type:varchar(36);index" json:"source_id"` + OperatorID string `gorm:"type:varchar(36);not null" json:"operator_id"` + Remark string `gorm:"type:varchar(255)" json:"remark"` + + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (UserProductQuotaLedger) TableName() string { + return "user_product_quota_ledgers" +} + +func (l *UserProductQuotaLedger) BeforeCreate(tx *gorm.DB) error { + if l.ID == "" { + l.ID = uuid.New().String() + } + return nil +} diff --git a/internal/domains/subordinate/repositories/subordinate_repository_interface.go b/internal/domains/subordinate/repositories/subordinate_repository_interface.go index be0ca3d..8af8bfc 100644 --- a/internal/domains/subordinate/repositories/subordinate_repository_interface.go +++ b/internal/domains/subordinate/repositories/subordinate_repository_interface.go @@ -28,4 +28,15 @@ type SubordinateRepository interface { // 划拨 CreateWalletAllocation(ctx context.Context, a *entities.SubordinateWalletAllocation) error ListWalletAllocationsByParentAndChild(ctx context.Context, parentUserID, childUserID string, limit, offset int) ([]*entities.SubordinateWalletAllocation, int64, error) + + // 额度购买 + CreateQuotaPurchase(ctx context.Context, p *entities.SubordinateQuotaPurchase) error + ListQuotaPurchasesByParentAndChild(ctx context.Context, parentUserID, childUserID string, limit, offset int) ([]*entities.SubordinateQuotaPurchase, int64, error) + + // 额度账户 + FindQuotaAccount(ctx context.Context, userID, productID string) (*entities.UserProductQuotaAccount, error) + CreateQuotaAccount(ctx context.Context, account *entities.UserProductQuotaAccount) error + UpdateQuotaAccount(ctx context.Context, account *entities.UserProductQuotaAccount) error + ListQuotaAccountsByUser(ctx context.Context, userID string) ([]*entities.UserProductQuotaAccount, error) + CreateQuotaLedger(ctx context.Context, ledger *entities.UserProductQuotaLedger) error } diff --git a/internal/infrastructure/database/repositories/subordinate/gorm_subordinate_repository.go b/internal/infrastructure/database/repositories/subordinate/gorm_subordinate_repository.go index e64d35c..07accf9 100644 --- a/internal/infrastructure/database/repositories/subordinate/gorm_subordinate_repository.go +++ b/internal/infrastructure/database/repositories/subordinate/gorm_subordinate_repository.go @@ -199,3 +199,67 @@ func (r *GormSubordinateRepository) ListWalletAllocationsByParentAndChild(ctx co } return out, total, nil } + +// CreateQuotaPurchase 创建额度购买记录 +func (r *GormSubordinateRepository) CreateQuotaPurchase(ctx context.Context, p *entities.SubordinateQuotaPurchase) error { + return r.withCtx(ctx).Create(p).Error +} + +// ListQuotaPurchasesByParentAndChild 查询主对子额度购买记录 +func (r *GormSubordinateRepository) ListQuotaPurchasesByParentAndChild(ctx context.Context, parentUserID, childUserID string, limit, offset int) ([]*entities.SubordinateQuotaPurchase, int64, error) { + var list []entities.SubordinateQuotaPurchase + var total int64 + q := r.withCtx(ctx).Model(&entities.SubordinateQuotaPurchase{}).Where("parent_user_id = ? AND child_user_id = ?", parentUserID, childUserID) + if err := q.Count(&total).Error; err != nil { + return nil, 0, err + } + if err := q.Order("created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil { + return nil, 0, err + } + out := make([]*entities.SubordinateQuotaPurchase, len(list)) + for i := range list { + out[i] = &list[i] + } + return out, total, nil +} + +// FindQuotaAccount 查询用户产品额度账户 +func (r *GormSubordinateRepository) FindQuotaAccount(ctx context.Context, userID, productID string) (*entities.UserProductQuotaAccount, error) { + var account entities.UserProductQuotaAccount + err := r.withCtx(ctx).Where("user_id = ? AND product_id = ?", userID, productID).First(&account).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + return &account, nil +} + +// CreateQuotaAccount 创建额度账户 +func (r *GormSubordinateRepository) CreateQuotaAccount(ctx context.Context, account *entities.UserProductQuotaAccount) error { + return r.withCtx(ctx).Create(account).Error +} + +// UpdateQuotaAccount 更新额度账户 +func (r *GormSubordinateRepository) UpdateQuotaAccount(ctx context.Context, account *entities.UserProductQuotaAccount) error { + return r.withCtx(ctx).Save(account).Error +} + +// ListQuotaAccountsByUser 查询用户全部额度账户 +func (r *GormSubordinateRepository) ListQuotaAccountsByUser(ctx context.Context, userID string) ([]*entities.UserProductQuotaAccount, error) { + var list []entities.UserProductQuotaAccount + if err := r.withCtx(ctx).Where("user_id = ?", userID).Order("updated_at DESC").Find(&list).Error; err != nil { + return nil, err + } + out := make([]*entities.UserProductQuotaAccount, len(list)) + for i := range list { + out[i] = &list[i] + } + return out, nil +} + +// CreateQuotaLedger 创建额度流水 +func (r *GormSubordinateRepository) CreateQuotaLedger(ctx context.Context, ledger *entities.UserProductQuotaLedger) error { + return r.withCtx(ctx).Create(ledger).Error +} diff --git a/internal/infrastructure/http/handlers/subordinate_handler.go b/internal/infrastructure/http/handlers/subordinate_handler.go index ae5bc12..db24da4 100644 --- a/internal/infrastructure/http/handlers/subordinate_handler.go +++ b/internal/infrastructure/http/handlers/subordinate_handler.go @@ -209,3 +209,91 @@ func (h *SubordinateHandler) RemoveChildSubscription(c *gin.Context) { } h.response.Success(c, nil, "删除成功") } + +// PurchaseQuota 为下属购买额度 +func (h *SubordinateHandler) PurchaseQuota(c *gin.Context) { + parentID := c.GetString("user_id") + if parentID == "" { + h.response.Unauthorized(c, "未登录") + return + } + var cmd commands.PurchaseChildQuotaCommand + if err := h.validator.BindAndValidate(c, &cmd); err != nil { + return + } + cmd.ParentUserID = parentID + if err := h.app.PurchaseChildQuota(c.Request.Context(), &cmd); err != nil { + h.logger.Error("为下属购买额度失败", zap.Error(err)) + h.response.BadRequest(c, err.Error()) + return + } + h.response.Success(c, nil, "购买额度成功") +} + +// ListQuotaPurchases 下属额度购买记录 +func (h *SubordinateHandler) ListQuotaPurchases(c *gin.Context) { + parentID := c.GetString("user_id") + if parentID == "" { + h.response.Unauthorized(c, "未登录") + return + } + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + cmd := &commands.ListChildQuotaPurchasesCommand{ + ParentUserID: parentID, + ChildUserID: c.Query("child_user_id"), + Page: page, + PageSize: size, + } + if cmd.ChildUserID == "" { + h.response.BadRequest(c, "child_user_id 不能为空") + return + } + res, err := h.app.ListChildQuotaPurchases(c.Request.Context(), cmd) + if err != nil { + h.logger.Error("获取额度购买记录失败", zap.Error(err)) + h.response.BadRequest(c, err.Error()) + return + } + h.response.Success(c, res, "获取成功") +} + +// ListChildQuotaAccounts 下属额度账户 +func (h *SubordinateHandler) ListChildQuotaAccounts(c *gin.Context) { + parentID := c.GetString("user_id") + if parentID == "" { + h.response.Unauthorized(c, "未登录") + return + } + childID := c.Query("child_user_id") + if childID == "" { + h.response.BadRequest(c, "child_user_id 不能为空") + return + } + res, err := h.app.ListChildQuotaAccounts(c.Request.Context(), &commands.ListChildQuotaAccountsCommand{ + ParentUserID: parentID, + ChildUserID: childID, + }) + if err != nil { + h.logger.Error("获取下属额度账户失败", zap.Error(err)) + h.response.BadRequest(c, err.Error()) + return + } + h.response.Success(c, res, "获取成功") +} + +// ListMyQuotaAccounts 当前登录用户额度账户 +func (h *SubordinateHandler) ListMyQuotaAccounts(c *gin.Context) { + userID := c.GetString("user_id") + if userID == "" { + h.response.Unauthorized(c, "未登录") + return + } + res, err := h.app.ListMyQuotaAccounts(c.Request.Context(), userID) + if err != nil { + h.logger.Error("获取我的额度账户失败", zap.Error(err)) + h.response.BadRequest(c, err.Error()) + return + } + h.response.Success(c, res, "获取成功") +} diff --git a/internal/infrastructure/http/routes/subordinate_routes.go b/internal/infrastructure/http/routes/subordinate_routes.go index f3a21c7..0bb7991 100644 --- a/internal/infrastructure/http/routes/subordinate_routes.go +++ b/internal/infrastructure/http/routes/subordinate_routes.go @@ -40,6 +40,10 @@ func (r *SubordinateRoutes) Register(router *sharedhttp.GinRouter) { sub.POST("/assign-subscription", r.handler.AssignSubscription) sub.GET("/child-subscriptions", r.handler.ListChildSubscriptions) sub.DELETE("/child-subscriptions/:subscription_id", r.handler.RemoveChildSubscription) + sub.POST("/purchase-quota", r.handler.PurchaseQuota) + sub.GET("/quota-purchases", r.handler.ListQuotaPurchases) + sub.GET("/child-quotas", r.handler.ListChildQuotaAccounts) + sub.GET("/my-quotas", r.handler.ListMyQuotaAccounts) } r.logger.Info("下属账号路由注册完成")