feat: add superadmin creator review and coupon governance

This commit is contained in:
2026-01-15 13:14:25 +08:00
parent bec984b959
commit 539cdf3c1c
17 changed files with 2909 additions and 146 deletions

View File

@@ -29,6 +29,40 @@ func (c *coupons) List(ctx fiber.Ctx, filter *dto.SuperCouponListFilter) (*reque
return services.Super.ListCoupons(ctx, filter)
}
// List coupon grants
//
// @Router /super/v1/coupon-grants [get]
// @Summary List coupon grants
// @Description List coupon grant records across tenants
// @Tags Coupon
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param limit query int false "Page size"
// @Success 200 {object} requests.Pager{items=[]dto.SuperCouponGrantItem}
// @Bind filter query
func (c *coupons) ListGrants(ctx fiber.Ctx, filter *dto.SuperCouponGrantListFilter) (*requests.Pager, error) {
return services.Super.ListCouponGrants(ctx, filter)
}
// Update coupon status
//
// @Router /super/v1/coupons/:id<int>/status [patch]
// @Summary Update coupon status
// @Description Update coupon status across tenants
// @Tags Coupon
// @Accept json
// @Produce json
// @Param id path int64 true "Coupon ID"
// @Param form body dto.SuperCouponStatusUpdateForm true "Update form"
// @Success 200 {string} string "Updated"
// @Bind user local key(__ctx_user)
// @Bind id path
// @Bind form body
func (c *coupons) UpdateStatus(ctx fiber.Ctx, user *models.User, id int64, form *dto.SuperCouponStatusUpdateForm) error {
return services.Super.UpdateCouponStatus(ctx, user.ID, id, form)
}
// Create coupon
//
// @Router /super/v1/tenants/:tenantID<int>/coupons [post]

View File

@@ -0,0 +1,47 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"quyun/v2/database/models"
"github.com/gofiber/fiber/v3"
)
// @provider
type creatorApplications struct{}
// List creator applications
//
// @Router /super/v1/creator-applications [get]
// @Summary List creator applications
// @Description List creator applications across tenants
// @Tags Creator
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param limit query int false "Page size"
// @Success 200 {object} requests.Pager{items=[]dto.TenantItem}
// @Bind filter query
func (c *creatorApplications) List(ctx fiber.Ctx, filter *dto.TenantListFilter) (*requests.Pager, error) {
return services.Super.ListCreatorApplications(ctx, filter)
}
// Review creator application
//
// @Router /super/v1/creator-applications/:id<int>/review [post]
// @Summary Review creator application
// @Description Approve or reject creator application
// @Tags Creator
// @Accept json
// @Produce json
// @Param id path int64 true "Tenant ID"
// @Param form body dto.SuperCreatorApplicationReviewForm true "Review form"
// @Success 200 {string} string "Reviewed"
// @Bind user local key(__ctx_user)
// @Bind id path
// @Bind form body
func (c *creatorApplications) Review(ctx fiber.Ctx, user *models.User, id int64, form *dto.SuperCreatorApplicationReviewForm) error {
return services.Super.ReviewCreatorApplication(ctx, user.ID, id, form)
}

View File

@@ -79,3 +79,66 @@ type SuperCouponGrantResponse struct {
// Granted 实际发放数量。
Granted int `json:"granted"`
}
// SuperCouponStatusUpdateForm 超管优惠券状态更新表单。
type SuperCouponStatusUpdateForm struct {
// Status 目标状态frozen
Status string `json:"status" validate:"required,oneof=frozen"`
}
// SuperCouponGrantListFilter 超管优惠券发放记录过滤条件。
type SuperCouponGrantListFilter struct {
requests.Pagination
// CouponID 优惠券ID过滤精确匹配
CouponID *int64 `query:"coupon_id"`
// TenantID 租户ID过滤精确匹配
TenantID *int64 `query:"tenant_id"`
// TenantCode 租户编码过滤(模糊匹配)。
TenantCode *string `query:"tenant_code"`
// TenantName 租户名称过滤(模糊匹配)。
TenantName *string `query:"tenant_name"`
// UserID 用户ID过滤精确匹配
UserID *int64 `query:"user_id"`
// Username 用户名过滤(模糊匹配)。
Username *string `query:"username"`
// Status 用户券状态过滤unused/used/expired
Status *consts.UserCouponStatus `query:"status"`
// CreatedAtFrom 领取时间起始RFC3339
CreatedAtFrom *string `query:"created_at_from"`
// CreatedAtTo 领取时间结束RFC3339
CreatedAtTo *string `query:"created_at_to"`
// UsedAtFrom 使用时间起始RFC3339
UsedAtFrom *string `query:"used_at_from"`
// UsedAtTo 使用时间结束RFC3339
UsedAtTo *string `query:"used_at_to"`
}
// SuperCouponGrantItem 超管优惠券发放记录项。
type SuperCouponGrantItem struct {
// ID 用户券ID。
ID int64 `json:"id"`
// CouponID 优惠券ID。
CouponID int64 `json:"coupon_id"`
// CouponTitle 优惠券标题。
CouponTitle string `json:"coupon_title"`
// TenantID 租户ID。
TenantID int64 `json:"tenant_id"`
// TenantCode 租户编码。
TenantCode string `json:"tenant_code"`
// TenantName 租户名称。
TenantName string `json:"tenant_name"`
// UserID 用户ID。
UserID int64 `json:"user_id"`
// Username 用户名。
Username string `json:"username"`
// Status 用户券状态。
Status consts.UserCouponStatus `json:"status"`
// StatusDescription 状态描述(用于展示)。
StatusDescription string `json:"status_description"`
// OrderID 使用订单ID。
OrderID int64 `json:"order_id"`
// UsedAt 使用时间RFC3339
UsedAt string `json:"used_at"`
// CreatedAt 领取时间RFC3339
CreatedAt string `json:"created_at"`
}

View File

@@ -0,0 +1,9 @@
package dto
// SuperCreatorApplicationReviewForm 超管创作者申请审核表单。
type SuperCreatorApplicationReviewForm struct {
// Action 审核动作approve/reject
Action string `json:"action" validate:"required,oneof=approve reject"`
// Reason 审核说明(可选,驳回时填写)。
Reason string `json:"reason"`
}

View File

@@ -0,0 +1,52 @@
package dto
import "quyun/v2/app/requests"
// SuperPayoutAccountListFilter 超管结算账户列表过滤条件。
type SuperPayoutAccountListFilter struct {
requests.Pagination
// TenantID 租户ID过滤精确匹配
TenantID *int64 `query:"tenant_id"`
// TenantCode 租户编码过滤(模糊匹配)。
TenantCode *string `query:"tenant_code"`
// TenantName 租户名称过滤(模糊匹配)。
TenantName *string `query:"tenant_name"`
// UserID 用户ID过滤精确匹配
UserID *int64 `query:"user_id"`
// Username 用户名过滤(模糊匹配)。
Username *string `query:"username"`
// Type 账户类型过滤bank/alipay
Type *string `query:"type"`
// CreatedAtFrom 创建时间起始RFC3339
CreatedAtFrom *string `query:"created_at_from"`
// CreatedAtTo 创建时间结束RFC3339
CreatedAtTo *string `query:"created_at_to"`
}
// SuperPayoutAccountItem 超管结算账户列表项。
type SuperPayoutAccountItem struct {
// ID 结算账户ID。
ID int64 `json:"id"`
// TenantID 租户ID。
TenantID int64 `json:"tenant_id"`
// TenantCode 租户编码。
TenantCode string `json:"tenant_code"`
// TenantName 租户名称。
TenantName string `json:"tenant_name"`
// UserID 用户ID。
UserID int64 `json:"user_id"`
// Username 用户名。
Username string `json:"username"`
// Type 账户类型。
Type string `json:"type"`
// Name 账户名称/开户行。
Name string `json:"name"`
// Account 收款账号。
Account string `json:"account"`
// Realname 收款人姓名。
Realname string `json:"realname"`
// CreatedAt 创建时间RFC3339
CreatedAt string `json:"created_at"`
// UpdatedAt 更新时间RFC3339
UpdatedAt string `json:"updated_at"`
}

View File

@@ -0,0 +1,45 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"quyun/v2/database/models"
"github.com/gofiber/fiber/v3"
)
// @provider
type payoutAccounts struct{}
// List payout accounts
//
// @Router /super/v1/payout-accounts [get]
// @Summary List payout accounts
// @Description List payout accounts across tenants
// @Tags Finance
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param limit query int false "Page size"
// @Success 200 {object} requests.Pager{items=[]dto.SuperPayoutAccountItem}
// @Bind filter query
func (c *payoutAccounts) List(ctx fiber.Ctx, filter *dto.SuperPayoutAccountListFilter) (*requests.Pager, error) {
return services.Super.ListPayoutAccounts(ctx, filter)
}
// Remove payout account
//
// @Router /super/v1/payout-accounts/:id<int> [delete]
// @Summary Remove payout account
// @Description Remove payout account across tenants
// @Tags Finance
// @Accept json
// @Produce json
// @Param id path int64 true "Payout account ID"
// @Success 200 {string} string "Removed"
// @Bind user local key(__ctx_user)
// @Bind id path
func (c *payoutAccounts) Remove(ctx fiber.Ctx, user *models.User, id int64) error {
return services.Super.RemovePayoutAccount(ctx, user.ID, id)
}

View File

@@ -24,6 +24,13 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*creatorApplications, error) {
obj := &creatorApplications{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*creators, error) {
obj := &creators{}
@@ -38,6 +45,13 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*payoutAccounts, error) {
obj := &payoutAccounts{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*reports, error) {
obj := &reports{}
@@ -48,24 +62,28 @@ func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func(
contents *contents,
coupons *coupons,
creatorApplications *creatorApplications,
creators *creators,
middlewares *middlewares.Middlewares,
orders *orders,
payoutAccounts *payoutAccounts,
reports *reports,
tenants *tenants,
users *users,
withdrawals *withdrawals,
) (contracts.HttpRoute, error) {
obj := &Routes{
contents: contents,
coupons: coupons,
creators: creators,
middlewares: middlewares,
orders: orders,
reports: reports,
tenants: tenants,
users: users,
withdrawals: withdrawals,
contents: contents,
coupons: coupons,
creatorApplications: creatorApplications,
creators: creators,
middlewares: middlewares,
orders: orders,
payoutAccounts: payoutAccounts,
reports: reports,
tenants: tenants,
users: users,
withdrawals: withdrawals,
}
if err := obj.Prepare(); err != nil {
return nil, err

View File

@@ -25,14 +25,16 @@ type Routes struct {
log *log.Entry `inject:"false"`
middlewares *middlewares.Middlewares
// Controller instances
contents *contents
coupons *coupons
creators *creators
orders *orders
reports *reports
tenants *tenants
users *users
withdrawals *withdrawals
contents *contents
coupons *coupons
creatorApplications *creatorApplications
creators *creators
orders *orders
payoutAccounts *payoutAccounts
reports *reports
tenants *tenants
users *users
withdrawals *withdrawals
}
// Prepare initializes the routes provider with logging configuration.
@@ -83,6 +85,11 @@ func (r *Routes) Register(router fiber.Router) {
Body[dto.SuperContentBatchReviewForm]("form"),
))
// Register routes for controller: coupons
r.log.Debugf("Registering route: Get /super/v1/coupon-grants -> coupons.ListGrants")
router.Get("/super/v1/coupon-grants"[len(r.Path()):], DataFunc1(
r.coupons.ListGrants,
Query[dto.SuperCouponGrantListFilter]("filter"),
))
r.log.Debugf("Registering route: Get /super/v1/coupons -> coupons.List")
router.Get("/super/v1/coupons"[len(r.Path()):], DataFunc1(
r.coupons.List,
@@ -94,6 +101,13 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("tenantID"),
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Patch /super/v1/coupons/:id<int>/status -> coupons.UpdateStatus")
router.Patch("/super/v1/coupons/:id<int>/status"[len(r.Path()):], Func3(
r.coupons.UpdateStatus,
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
Body[dto.SuperCouponStatusUpdateForm]("form"),
))
r.log.Debugf("Registering route: Post /super/v1/tenants/:tenantID<int>/coupons -> coupons.Create")
router.Post("/super/v1/tenants/:tenantID<int>/coupons"[len(r.Path()):], DataFunc3(
r.coupons.Create,
@@ -116,6 +130,19 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("id"),
Body[v1_dto.CouponUpdateForm]("form"),
))
// Register routes for controller: creatorApplications
r.log.Debugf("Registering route: Get /super/v1/creator-applications -> creatorApplications.List")
router.Get("/super/v1/creator-applications"[len(r.Path()):], DataFunc1(
r.creatorApplications.List,
Query[dto.TenantListFilter]("filter"),
))
r.log.Debugf("Registering route: Post /super/v1/creator-applications/:id<int>/review -> creatorApplications.Review")
router.Post("/super/v1/creator-applications/:id<int>/review"[len(r.Path()):], Func3(
r.creatorApplications.Review,
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
Body[dto.SuperCreatorApplicationReviewForm]("form"),
))
// Register routes for controller: creators
r.log.Debugf("Registering route: Get /super/v1/creators -> creators.List")
router.Get("/super/v1/creators"[len(r.Path()):], DataFunc1(
@@ -143,6 +170,18 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("id"),
Body[dto.SuperOrderRefundForm]("form"),
))
// Register routes for controller: payoutAccounts
r.log.Debugf("Registering route: Delete /super/v1/payout-accounts/:id<int> -> payoutAccounts.Remove")
router.Delete("/super/v1/payout-accounts/:id<int>"[len(r.Path()):], Func2(
r.payoutAccounts.Remove,
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Get /super/v1/payout-accounts -> payoutAccounts.List")
router.Get("/super/v1/payout-accounts"[len(r.Path()):], DataFunc1(
r.payoutAccounts.List,
Query[dto.SuperPayoutAccountListFilter]("filter"),
))
// Register routes for controller: reports
r.log.Debugf("Registering route: Get /super/v1/reports/overview -> reports.Overview")
router.Get("/super/v1/reports/overview"[len(r.Path()):], DataFunc1(

View File

@@ -953,6 +953,76 @@ func (s *super) TenantHealth(ctx context.Context, filter *super_dto.TenantListFi
}, nil
}
func (s *super) ListCreatorApplications(ctx context.Context, filter *super_dto.TenantListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.TenantListFilter{}
}
if filter.Status == nil || *filter.Status == "" {
status := consts.TenantStatusPendingVerify
filter.Status = &status
}
return s.ListTenants(ctx, filter)
}
func (s *super) ReviewCreatorApplication(ctx context.Context, operatorID, tenantID int64, form *super_dto.SuperCreatorApplicationReviewForm) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
}
if tenantID == 0 || form == nil {
return errorx.ErrBadRequest.WithMsg("审核参数无效")
}
action := strings.ToLower(strings.TrimSpace(form.Action))
if action != "approve" && action != "reject" {
return errorx.ErrBadRequest.WithMsg("审核动作无效")
}
tbl, q := models.TenantQuery.QueryContext(ctx)
tenant, err := q.Where(tbl.ID.Eq(tenantID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("创作者申请不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if tenant.Status != consts.TenantStatusPendingVerify {
return errorx.ErrBadRequest.WithMsg("创作者申请已处理")
}
nextStatus := consts.TenantStatusVerified
if action == "reject" {
nextStatus = consts.TenantStatusBanned
}
_, err = q.Where(tbl.ID.Eq(tenant.ID), tbl.Status.Eq(consts.TenantStatusPendingVerify)).Update(tbl.Status, nextStatus)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if Notification != nil {
title := "创作者申请审核结果"
detail := "您的创作者申请已通过"
if nextStatus == consts.TenantStatusBanned {
detail = "您的创作者申请已驳回"
if strings.TrimSpace(form.Reason) != "" {
detail += ",原因:" + strings.TrimSpace(form.Reason)
}
}
_ = Notification.Send(ctx, tenant.ID, tenant.UserID, string(consts.NotificationTypeAudit), title, detail)
}
if Audit != nil {
detail := "approve"
if nextStatus == consts.TenantStatusBanned {
detail = "reject"
}
if strings.TrimSpace(form.Reason) != "" {
detail += ",原因:" + strings.TrimSpace(form.Reason)
}
Audit.Log(ctx, operatorID, "review_creator_application", cast.ToString(tenant.ID), detail)
}
return nil
}
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
uid := form.AdminUserID
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First(); err != nil {
@@ -1065,6 +1135,191 @@ func (s *super) ListTenantUsers(ctx context.Context, tenantID int64, filter *sup
}, nil
}
func (s *super) ListPayoutAccounts(ctx context.Context, filter *super_dto.SuperPayoutAccountListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperPayoutAccountListFilter{}
}
tbl, q := models.PayoutAccountQuery.QueryContext(ctx)
if filter.TenantID != nil && *filter.TenantID > 0 {
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
if filter.UserID != nil && *filter.UserID > 0 {
q = q.Where(tbl.UserID.Eq(*filter.UserID))
}
if filter.Type != nil && strings.TrimSpace(*filter.Type) != "" {
q = q.Where(tbl.Type.Eq(strings.TrimSpace(*filter.Type)))
}
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
if err != nil {
return nil, err
}
if tenantFilter {
if len(tenantIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.TenantID.In(tenantIDs...))
}
}
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
if err != nil {
return nil, err
}
if userFilter {
if len(userIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.UserID.In(userIDs...))
}
}
if filter.CreatedAtFrom != nil {
from, err := s.parseFilterTime(filter.CreatedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.CreatedAt.Gte(*from))
}
}
if filter.CreatedAtTo != nil {
to, err := s.parseFilterTime(filter.CreatedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.CreatedAt.Lte(*to))
}
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Order(tbl.CreatedAt.Desc()).
Offset(int(filter.Pagination.Offset())).
Limit(int(filter.Pagination.Limit)).
Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
tenantMap := make(map[int64]*models.Tenant)
userMap := make(map[int64]*models.User)
if len(list) > 0 {
tenantIDSet := make(map[int64]struct{})
userIDSet := make(map[int64]struct{})
for _, pa := range list {
if pa.TenantID > 0 {
tenantIDSet[pa.TenantID] = struct{}{}
}
if pa.UserID > 0 {
userIDSet[pa.UserID] = struct{}{}
}
}
tenantIDs = tenantIDs[:0]
for id := range tenantIDSet {
tenantIDs = append(tenantIDs, id)
}
if len(tenantIDs) > 0 {
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(tenantIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
userIDs = userIDs[:0]
for id := range userIDSet {
userIDs = append(userIDs, id)
}
if len(userIDs) > 0 {
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
users, err := userQuery.Where(userTbl.ID.In(userIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, user := range users {
userMap[user.ID] = user
}
}
}
items := make([]super_dto.SuperPayoutAccountItem, 0, len(list))
for _, pa := range list {
tenant := tenantMap[pa.TenantID]
user := userMap[pa.UserID]
tenantCode := ""
tenantName := ""
if tenant != nil {
tenantCode = tenant.Code
tenantName = tenant.Name
}
username := ""
if user != nil {
username = user.Username
}
if username == "" && pa.UserID > 0 {
username = "ID:" + strconv.FormatInt(pa.UserID, 10)
}
items = append(items, super_dto.SuperPayoutAccountItem{
ID: pa.ID,
TenantID: pa.TenantID,
TenantCode: tenantCode,
TenantName: tenantName,
UserID: pa.UserID,
Username: username,
Type: pa.Type,
Name: pa.Name,
Account: pa.Account,
Realname: pa.Realname,
CreatedAt: s.formatTime(pa.CreatedAt),
UpdatedAt: s.formatTime(pa.UpdatedAt),
})
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) RemovePayoutAccount(ctx context.Context, operatorID, id int64) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
}
if id == 0 {
return errorx.ErrBadRequest.WithMsg("结算账户ID不能为空")
}
tbl, q := models.PayoutAccountQuery.QueryContext(ctx)
account, err := q.Where(tbl.ID.Eq(id)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("结算账户不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
if _, err := q.Where(tbl.ID.Eq(account.ID)).Delete(); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if Audit != nil {
Audit.Log(ctx, operatorID, "remove_payout_account", cast.ToString(account.ID), "Removed payout account")
}
return nil
}
func (s *super) ListTenantJoinRequests(ctx context.Context, filter *super_dto.SuperTenantJoinRequestListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperTenantJoinRequestListFilter{}
@@ -3214,6 +3469,241 @@ func (s *super) ListCoupons(ctx context.Context, filter *super_dto.SuperCouponLi
}, nil
}
func (s *super) ListCouponGrants(ctx context.Context, filter *super_dto.SuperCouponGrantListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperCouponGrantListFilter{}
}
tbl, q := models.UserCouponQuery.QueryContext(ctx)
if filter.UserID != nil && *filter.UserID > 0 {
q = q.Where(tbl.UserID.Eq(*filter.UserID))
}
if filter.Status != nil && *filter.Status != "" {
q = q.Where(tbl.Status.Eq(*filter.Status))
}
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
if err != nil {
return nil, err
}
if userFilter {
if len(userIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.UserID.In(userIDs...))
}
}
couponIDs, couponFilter, err := s.filterCouponGrantCouponIDs(ctx, filter)
if err != nil {
return nil, err
}
if couponFilter {
if len(couponIDs) == 0 {
filter.Pagination.Format()
return &requests.Pager{
Pagination: filter.Pagination,
Total: 0,
Items: []super_dto.SuperCouponGrantItem{},
}, nil
}
q = q.Where(tbl.CouponID.In(couponIDs...))
}
if filter.CreatedAtFrom != nil {
from, err := s.parseFilterTime(filter.CreatedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.CreatedAt.Gte(*from))
}
}
if filter.CreatedAtTo != nil {
to, err := s.parseFilterTime(filter.CreatedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.CreatedAt.Lte(*to))
}
}
if filter.UsedAtFrom != nil {
from, err := s.parseFilterTime(filter.UsedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.UsedAt.Gte(*from))
}
}
if filter.UsedAtTo != nil {
to, err := s.parseFilterTime(filter.UsedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.UsedAt.Lte(*to))
}
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Order(tbl.CreatedAt.Desc()).
Offset(int(filter.Pagination.Offset())).
Limit(int(filter.Pagination.Limit)).
Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if len(list) == 0 {
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: []super_dto.SuperCouponGrantItem{},
}, nil
}
couponIDSet := make(map[int64]struct{})
userIDSet := make(map[int64]struct{})
for _, uc := range list {
if uc.CouponID > 0 {
couponIDSet[uc.CouponID] = struct{}{}
}
if uc.UserID > 0 {
userIDSet[uc.UserID] = struct{}{}
}
}
couponIDs = couponIDs[:0]
for id := range couponIDSet {
couponIDs = append(couponIDs, id)
}
userIDs = userIDs[:0]
for id := range userIDSet {
userIDs = append(userIDs, id)
}
couponMap := make(map[int64]*models.Coupon, len(couponIDs))
tenantMap := make(map[int64]*models.Tenant)
if len(couponIDs) > 0 {
couponTbl, couponQuery := models.CouponQuery.QueryContext(ctx)
coupons, err := couponQuery.Where(couponTbl.ID.In(couponIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
tenantSet := make(map[int64]struct{})
for _, coupon := range coupons {
couponMap[coupon.ID] = coupon
if coupon.TenantID > 0 {
tenantSet[coupon.TenantID] = struct{}{}
}
}
tenantIDs := make([]int64, 0, len(tenantSet))
for id := range tenantSet {
tenantIDs = append(tenantIDs, id)
}
if len(tenantIDs) > 0 {
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(tenantIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
}
userMap := make(map[int64]*models.User, len(userIDs))
if len(userIDs) > 0 {
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
users, err := userQuery.Where(userTbl.ID.In(userIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, user := range users {
userMap[user.ID] = user
}
}
items := make([]super_dto.SuperCouponGrantItem, 0, len(list))
for _, uc := range list {
item := super_dto.SuperCouponGrantItem{
ID: uc.ID,
CouponID: uc.CouponID,
UserID: uc.UserID,
Status: uc.Status,
StatusDescription: uc.Status.Description(),
OrderID: uc.OrderID,
UsedAt: s.formatTime(uc.UsedAt),
CreatedAt: s.formatTime(uc.CreatedAt),
}
if user := userMap[uc.UserID]; user != nil {
item.Username = user.Username
} else if uc.UserID > 0 {
item.Username = "ID:" + strconv.FormatInt(uc.UserID, 10)
}
if coupon := couponMap[uc.CouponID]; coupon != nil {
item.CouponTitle = coupon.Title
item.TenantID = coupon.TenantID
if tenant := tenantMap[coupon.TenantID]; tenant != nil {
item.TenantCode = tenant.Code
item.TenantName = tenant.Name
}
}
items = append(items, item)
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) UpdateCouponStatus(ctx context.Context, operatorID, couponID int64, form *super_dto.SuperCouponStatusUpdateForm) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
}
if couponID == 0 || form == nil {
return errorx.ErrBadRequest.WithMsg("参数无效")
}
status := strings.ToLower(strings.TrimSpace(form.Status))
if status != "frozen" {
return errorx.ErrBadRequest.WithMsg("仅支持冻结操作")
}
tbl, q := models.CouponQuery.QueryContext(ctx)
coupon, err := q.Where(tbl.ID.Eq(couponID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("优惠券不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
now := time.Now()
if !coupon.EndAt.IsZero() && now.After(coupon.EndAt) {
return nil
}
_, err = q.Where(tbl.ID.Eq(coupon.ID)).UpdateSimple(
tbl.EndAt.Value(now),
tbl.UpdatedAt.Value(now),
)
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if Audit != nil {
Audit.Log(ctx, operatorID, "freeze_coupon", cast.ToString(coupon.ID), "Freeze coupon")
}
return nil
}
func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperReportOverviewFilter) (*v1_dto.ReportOverviewResponse, error) {
// 统一统计时间范围与粒度。
rg, err := s.normalizeReportRange(filter)
@@ -3766,6 +4256,50 @@ func (s *super) filterCouponIDs(ctx context.Context, filter *super_dto.SuperUser
return ids, true, nil
}
func (s *super) filterCouponGrantCouponIDs(ctx context.Context, filter *super_dto.SuperCouponGrantListFilter) ([]int64, bool, error) {
if filter == nil {
return nil, false, nil
}
if filter.CouponID != nil && *filter.CouponID > 0 {
return []int64{*filter.CouponID}, true, nil
}
tenantIDs := make([]int64, 0)
applied := false
if filter.TenantID != nil && *filter.TenantID > 0 {
applied = true
tenantIDs = append(tenantIDs, *filter.TenantID)
}
lookupIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
if err != nil {
return nil, true, err
}
if tenantFilter {
applied = true
tenantIDs = append(tenantIDs, lookupIDs...)
}
if !applied {
return nil, false, nil
}
if len(tenantIDs) == 0 {
return []int64{}, true, nil
}
couponTbl, couponQuery := models.CouponQuery.QueryContext(ctx)
coupons, err := couponQuery.Where(couponTbl.TenantID.In(tenantIDs...)).Select(couponTbl.ID).Find()
if err != nil {
return nil, true, errorx.ErrDatabaseError.WithCause(err)
}
ids := make([]int64, 0, len(coupons))
for _, coupon := range coupons {
ids = append(ids, coupon.ID)
}
return ids, true, nil
}
func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reason string) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")

View File

@@ -209,6 +209,58 @@ const docTemplate = `{
}
}
},
"/super/v1/coupon-grants": {
"get": {
"description": "List coupon grant records across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Coupon"
],
"summary": "List coupon grants",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperCouponGrantItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/coupons": {
"get": {
"description": "List coupon templates across tenants",
@@ -261,6 +313,142 @@ const docTemplate = `{
}
}
},
"/super/v1/coupons/{id}/status": {
"patch": {
"description": "Update coupon status across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Coupon"
],
"summary": "Update coupon status",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Coupon ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Update form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperCouponStatusUpdateForm"
}
}
],
"responses": {
"200": {
"description": "Updated",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/creator-applications": {
"get": {
"description": "List creator applications across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Creator"
],
"summary": "List creator applications",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.TenantItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/creator-applications/{id}/review": {
"post": {
"description": "Approve or reject creator application",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Creator"
],
"summary": "Review creator application",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Tenant ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Review form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperCreatorApplicationReviewForm"
}
}
],
"responses": {
"200": {
"description": "Reviewed",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/creators": {
"get": {
"description": "List creator tenants (channels) across the platform",
@@ -463,6 +651,91 @@ const docTemplate = `{
}
}
},
"/super/v1/payout-accounts": {
"get": {
"description": "List payout accounts across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "List payout accounts",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperPayoutAccountItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/payout-accounts/{id}": {
"delete": {
"description": "Remove payout account across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "Remove payout account",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Payout account ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Removed",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/reports/export": {
"post": {
"description": "Export platform report data",
@@ -6118,6 +6391,67 @@ const docTemplate = `{
}
}
},
"dto.SuperCouponGrantItem": {
"type": "object",
"properties": {
"coupon_id": {
"description": "CouponID 优惠券ID。",
"type": "integer"
},
"coupon_title": {
"description": "CouponTitle 优惠券标题。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 领取时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 用户券ID。",
"type": "integer"
},
"order_id": {
"description": "OrderID 使用订单ID。",
"type": "integer"
},
"status": {
"description": "Status 用户券状态。",
"allOf": [
{
"$ref": "#/definitions/consts.UserCouponStatus"
}
]
},
"status_description": {
"description": "StatusDescription 状态描述(用于展示)。",
"type": "string"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"used_at": {
"description": "UsedAt 使用时间RFC3339。",
"type": "string"
},
"user_id": {
"description": "UserID 用户ID。",
"type": "integer"
},
"username": {
"description": "Username 用户名。",
"type": "string"
}
}
},
"dto.SuperCouponGrantResponse": {
"type": "object",
"properties": {
@@ -6212,6 +6546,41 @@ const docTemplate = `{
}
}
},
"dto.SuperCouponStatusUpdateForm": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"description": "Status 目标状态frozen。",
"type": "string",
"enum": [
"frozen"
]
}
}
},
"dto.SuperCreatorApplicationReviewForm": {
"type": "object",
"required": [
"action"
],
"properties": {
"action": {
"description": "Action 审核动作approve/reject。",
"type": "string",
"enum": [
"approve",
"reject"
]
},
"reason": {
"description": "Reason 审核说明(可选,驳回时填写)。",
"type": "string"
}
}
},
"dto.SuperOrderDetail": {
"type": "object",
"properties": {
@@ -6369,6 +6738,59 @@ const docTemplate = `{
}
}
},
"dto.SuperPayoutAccountItem": {
"type": "object",
"properties": {
"account": {
"description": "Account 收款账号。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 结算账户ID。",
"type": "integer"
},
"name": {
"description": "Name 账户名称/开户行。",
"type": "string"
},
"realname": {
"description": "Realname 收款人姓名。",
"type": "string"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"type": {
"description": "Type 账户类型。",
"type": "string"
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
},
"user_id": {
"description": "UserID 用户ID。",
"type": "integer"
},
"username": {
"description": "Username 用户名。",
"type": "string"
}
}
},
"dto.SuperReportExportForm": {
"type": "object",
"properties": {

View File

@@ -203,6 +203,58 @@
}
}
},
"/super/v1/coupon-grants": {
"get": {
"description": "List coupon grant records across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Coupon"
],
"summary": "List coupon grants",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperCouponGrantItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/coupons": {
"get": {
"description": "List coupon templates across tenants",
@@ -255,6 +307,142 @@
}
}
},
"/super/v1/coupons/{id}/status": {
"patch": {
"description": "Update coupon status across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Coupon"
],
"summary": "Update coupon status",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Coupon ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Update form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperCouponStatusUpdateForm"
}
}
],
"responses": {
"200": {
"description": "Updated",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/creator-applications": {
"get": {
"description": "List creator applications across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Creator"
],
"summary": "List creator applications",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.TenantItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/creator-applications/{id}/review": {
"post": {
"description": "Approve or reject creator application",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Creator"
],
"summary": "Review creator application",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Tenant ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Review form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperCreatorApplicationReviewForm"
}
}
],
"responses": {
"200": {
"description": "Reviewed",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/creators": {
"get": {
"description": "List creator tenants (channels) across the platform",
@@ -457,6 +645,91 @@
}
}
},
"/super/v1/payout-accounts": {
"get": {
"description": "List payout accounts across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "List payout accounts",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperPayoutAccountItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/payout-accounts/{id}": {
"delete": {
"description": "Remove payout account across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "Remove payout account",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Payout account ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Removed",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/reports/export": {
"post": {
"description": "Export platform report data",
@@ -6112,6 +6385,67 @@
}
}
},
"dto.SuperCouponGrantItem": {
"type": "object",
"properties": {
"coupon_id": {
"description": "CouponID 优惠券ID。",
"type": "integer"
},
"coupon_title": {
"description": "CouponTitle 优惠券标题。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 领取时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 用户券ID。",
"type": "integer"
},
"order_id": {
"description": "OrderID 使用订单ID。",
"type": "integer"
},
"status": {
"description": "Status 用户券状态。",
"allOf": [
{
"$ref": "#/definitions/consts.UserCouponStatus"
}
]
},
"status_description": {
"description": "StatusDescription 状态描述(用于展示)。",
"type": "string"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"used_at": {
"description": "UsedAt 使用时间RFC3339。",
"type": "string"
},
"user_id": {
"description": "UserID 用户ID。",
"type": "integer"
},
"username": {
"description": "Username 用户名。",
"type": "string"
}
}
},
"dto.SuperCouponGrantResponse": {
"type": "object",
"properties": {
@@ -6206,6 +6540,41 @@
}
}
},
"dto.SuperCouponStatusUpdateForm": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"description": "Status 目标状态frozen。",
"type": "string",
"enum": [
"frozen"
]
}
}
},
"dto.SuperCreatorApplicationReviewForm": {
"type": "object",
"required": [
"action"
],
"properties": {
"action": {
"description": "Action 审核动作approve/reject。",
"type": "string",
"enum": [
"approve",
"reject"
]
},
"reason": {
"description": "Reason 审核说明(可选,驳回时填写)。",
"type": "string"
}
}
},
"dto.SuperOrderDetail": {
"type": "object",
"properties": {
@@ -6363,6 +6732,59 @@
}
}
},
"dto.SuperPayoutAccountItem": {
"type": "object",
"properties": {
"account": {
"description": "Account 收款账号。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 结算账户ID。",
"type": "integer"
},
"name": {
"description": "Name 账户名称/开户行。",
"type": "string"
},
"realname": {
"description": "Realname 收款人姓名。",
"type": "string"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"type": {
"description": "Type 账户类型。",
"type": "string"
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
},
"user_id": {
"description": "UserID 用户ID。",
"type": "integer"
},
"username": {
"description": "Username 用户名。",
"type": "string"
}
}
},
"dto.SuperReportExportForm": {
"type": "object",
"properties": {

View File

@@ -1060,6 +1060,49 @@ definitions:
description: Name 租户名称。
type: string
type: object
dto.SuperCouponGrantItem:
properties:
coupon_id:
description: CouponID 优惠券ID。
type: integer
coupon_title:
description: CouponTitle 优惠券标题。
type: string
created_at:
description: CreatedAt 领取时间RFC3339
type: string
id:
description: ID 用户券ID。
type: integer
order_id:
description: OrderID 使用订单ID。
type: integer
status:
allOf:
- $ref: '#/definitions/consts.UserCouponStatus'
description: Status 用户券状态。
status_description:
description: StatusDescription 状态描述(用于展示)。
type: string
tenant_code:
description: TenantCode 租户编码。
type: string
tenant_id:
description: TenantID 租户ID。
type: integer
tenant_name:
description: TenantName 租户名称。
type: string
used_at:
description: UsedAt 使用时间RFC3339
type: string
user_id:
description: UserID 用户ID。
type: integer
username:
description: Username 用户名。
type: string
type: object
dto.SuperCouponGrantResponse:
properties:
granted:
@@ -1127,6 +1170,30 @@ definitions:
description: Value 优惠券面额/折扣值。
type: integer
type: object
dto.SuperCouponStatusUpdateForm:
properties:
status:
description: Status 目标状态frozen
enum:
- frozen
type: string
required:
- status
type: object
dto.SuperCreatorApplicationReviewForm:
properties:
action:
description: Action 审核动作approve/reject
enum:
- approve
- reject
type: string
reason:
description: Reason 审核说明(可选,驳回时填写)。
type: string
required:
- action
type: object
dto.SuperOrderDetail:
properties:
buyer:
@@ -1225,6 +1292,45 @@ definitions:
description: Reason 退款原因说明。
type: string
type: object
dto.SuperPayoutAccountItem:
properties:
account:
description: Account 收款账号。
type: string
created_at:
description: CreatedAt 创建时间RFC3339
type: string
id:
description: ID 结算账户ID。
type: integer
name:
description: Name 账户名称/开户行。
type: string
realname:
description: Realname 收款人姓名。
type: string
tenant_code:
description: TenantCode 租户编码。
type: string
tenant_id:
description: TenantID 租户ID。
type: integer
tenant_name:
description: TenantName 租户名称。
type: string
type:
description: Type 账户类型。
type: string
updated_at:
description: UpdatedAt 更新时间RFC3339
type: string
user_id:
description: UserID 用户ID。
type: integer
username:
description: Username 用户名。
type: string
type: object
dto.SuperReportExportForm:
properties:
end_at:
@@ -2394,6 +2500,37 @@ paths:
summary: Batch review contents
tags:
- Content
/super/v1/coupon-grants:
get:
consumes:
- application/json
description: List coupon grant records across tenants
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
items:
$ref: '#/definitions/dto.SuperCouponGrantItem'
type: array
type: object
summary: List coupon grants
tags:
- Coupon
/super/v1/coupons:
get:
consumes:
@@ -2425,6 +2562,93 @@ paths:
summary: List coupons
tags:
- Coupon
/super/v1/coupons/{id}/status:
patch:
consumes:
- application/json
description: Update coupon status across tenants
parameters:
- description: Coupon ID
format: int64
in: path
name: id
required: true
type: integer
- description: Update form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperCouponStatusUpdateForm'
produces:
- application/json
responses:
"200":
description: Updated
schema:
type: string
summary: Update coupon status
tags:
- Coupon
/super/v1/creator-applications:
get:
consumes:
- application/json
description: List creator applications across tenants
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
items:
$ref: '#/definitions/dto.TenantItem'
type: array
type: object
summary: List creator applications
tags:
- Creator
/super/v1/creator-applications/{id}/review:
post:
consumes:
- application/json
description: Approve or reject creator application
parameters:
- description: Tenant ID
format: int64
in: path
name: id
required: true
type: integer
- description: Review form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperCreatorApplicationReviewForm'
produces:
- application/json
responses:
"200":
description: Reviewed
schema:
type: string
summary: Review creator application
tags:
- Creator
/super/v1/creators:
get:
consumes:
@@ -2552,6 +2776,59 @@ paths:
summary: Order statistics
tags:
- Order
/super/v1/payout-accounts:
get:
consumes:
- application/json
description: List payout accounts across tenants
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
items:
$ref: '#/definitions/dto.SuperPayoutAccountItem'
type: array
type: object
summary: List payout accounts
tags:
- Finance
/super/v1/payout-accounts/{id}:
delete:
consumes:
- application/json
description: Remove payout account across tenants
parameters:
- description: Payout account ID
format: int64
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Removed
schema:
type: string
summary: Remove payout account
tags:
- Finance
/super/v1/reports/export:
post:
consumes: