feat: wire superadmin p1 data

This commit is contained in:
2026-01-15 09:35:16 +08:00
parent bb4c5b39d2
commit 235a216b0c
21 changed files with 3188 additions and 28 deletions

View File

@@ -0,0 +1,28 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"github.com/gofiber/fiber/v3"
)
// @provider
type coupons struct{}
// List coupons
//
// @Router /super/v1/coupons [get]
// @Summary List coupons
// @Description List coupon templates 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.SuperCouponItem}
// @Bind filter query
func (c *coupons) List(ctx fiber.Ctx, filter *dto.SuperCouponListFilter) (*requests.Pager, error) {
return services.Super.ListCoupons(ctx, filter)
}

View File

@@ -0,0 +1,28 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"github.com/gofiber/fiber/v3"
)
// @provider
type creators struct{}
// List creators
//
// @Router /super/v1/creators [get]
// @Summary List creators
// @Description List creator tenants (channels) across the platform
// @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 *creators) List(ctx fiber.Ctx, filter *dto.TenantListFilter) (*requests.Pager, error) {
return services.Super.ListTenants(ctx, filter)
}

View File

@@ -0,0 +1,75 @@
package dto
import (
"quyun/v2/app/requests"
"quyun/v2/pkg/consts"
)
// SuperCouponListFilter 超管优惠券列表过滤条件。
type SuperCouponListFilter struct {
requests.Pagination
// ID 优惠券ID精确匹配。
ID *int64 `query:"id"`
// TenantID 租户ID精确匹配。
TenantID *int64 `query:"tenant_id"`
// TenantCode 租户编码,模糊匹配。
TenantCode *string `query:"tenant_code"`
// TenantName 租户名称,模糊匹配。
TenantName *string `query:"tenant_name"`
// Keyword 标题或描述关键词,模糊匹配。
Keyword *string `query:"keyword"`
// Type 优惠券类型过滤fix_amount/discount
Type *string `query:"type"`
// Status 状态过滤active/expired/upcoming
Status *string `query:"status"`
// CreatedAtFrom 创建时间起始RFC3339
CreatedAtFrom *string `query:"created_at_from"`
// CreatedAtTo 创建时间结束RFC3339
CreatedAtTo *string `query:"created_at_to"`
// Asc 升序字段id/created_at/start_at/end_at
Asc *string `query:"asc"`
// Desc 降序字段id/created_at/start_at/end_at
Desc *string `query:"desc"`
}
// SuperCouponItem 超管优惠券列表项。
type SuperCouponItem 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"`
// Title 优惠券标题。
Title string `json:"title"`
// Description 优惠券描述。
Description string `json:"description"`
// Type 优惠券类型。
Type consts.CouponType `json:"type"`
// TypeDescription 类型描述(用于展示)。
TypeDescription string `json:"type_description"`
// Value 优惠券面额/折扣值。
Value int64 `json:"value"`
// MinOrderAmount 最低订单金额门槛。
MinOrderAmount int64 `json:"min_order_amount"`
// MaxDiscount 最大折扣金额(折扣券)。
MaxDiscount int64 `json:"max_discount"`
// TotalQuantity 总发行数量0 表示不限量)。
TotalQuantity int32 `json:"total_quantity"`
// UsedQuantity 已使用数量。
UsedQuantity int32 `json:"used_quantity"`
// Status 状态active/expired/upcoming
Status string `json:"status"`
// StatusDescription 状态描述(用于展示)。
StatusDescription string `json:"status_description"`
// StartAt 生效时间RFC3339
StartAt string `json:"start_at"`
// EndAt 结束时间RFC3339
EndAt string `json:"end_at"`
// CreatedAt 创建时间RFC3339
CreatedAt string `json:"created_at"`
// UpdatedAt 更新时间RFC3339
UpdatedAt string `json:"updated_at"`
}

View File

@@ -0,0 +1,27 @@
package dto
// SuperReportOverviewFilter 超管报表查询条件。
type SuperReportOverviewFilter struct {
// TenantID 租户ID不传代表全平台
TenantID *int64 `query:"tenant_id"`
// StartAt 统计开始时间RFC3339可选默认当前时间往前 7 天)。
StartAt *string `query:"start_at"`
// EndAt 统计结束时间RFC3339可选默认当前时间
EndAt *string `query:"end_at"`
// Granularity 统计粒度day目前仅支持 day
Granularity *string `query:"granularity"`
}
// SuperReportExportForm 超管报表导出参数。
type SuperReportExportForm struct {
// TenantID 租户ID不传代表全平台
TenantID *int64 `json:"tenant_id"`
// StartAt 统计开始时间RFC3339可选默认当前时间往前 7 天)。
StartAt *string `json:"start_at"`
// EndAt 统计结束时间RFC3339可选默认当前时间
EndAt *string `json:"end_at"`
// Granularity 统计粒度day目前仅支持 day
Granularity *string `json:"granularity"`
// Format 导出格式(仅支持 csv
Format string `json:"format"`
}

View File

@@ -0,0 +1,7 @@
package dto
// SuperWithdrawalRejectForm 超管驳回提现表单。
type SuperWithdrawalRejectForm struct {
// Reason 驳回原因。
Reason string `json:"reason" validate:"required"`
}

View File

@@ -17,6 +17,20 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*coupons, error) {
obj := &coupons{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*creators, error) {
obj := &creators{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*orders, error) {
obj := &orders{}
@@ -24,19 +38,34 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*reports, error) {
obj := &reports{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func(
contents *contents,
coupons *coupons,
creators *creators,
middlewares *middlewares.Middlewares,
orders *orders,
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,
}
if err := obj.Prepare(); err != nil {
return nil, err
@@ -60,5 +89,12 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*withdrawals, error) {
obj := &withdrawals{}
return obj, nil
}); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,41 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
v1_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/app/services"
"github.com/gofiber/fiber/v3"
)
// @provider
type reports struct{}
// Report overview
//
// @Router /super/v1/reports/overview [get]
// @Summary Report overview
// @Description Get platform report overview
// @Tags Report
// @Accept json
// @Produce json
// @Success 200 {object} v1_dto.ReportOverviewResponse
// @Bind filter query
func (c *reports) Overview(ctx fiber.Ctx, filter *dto.SuperReportOverviewFilter) (*v1_dto.ReportOverviewResponse, error) {
return services.Super.ReportOverview(ctx, filter)
}
// Export report
//
// @Router /super/v1/reports/export [post]
// @Summary Export report
// @Description Export platform report data
// @Tags Report
// @Accept json
// @Produce json
// @Param form body dto.SuperReportExportForm true "Export form"
// @Success 200 {object} v1_dto.ReportExportResponse
// @Bind form body
func (c *reports) Export(ctx fiber.Ctx, form *dto.SuperReportExportForm) (*v1_dto.ReportExportResponse, error) {
return services.Super.ExportReport(ctx, form)
}

View File

@@ -24,10 +24,14 @@ type Routes struct {
log *log.Entry `inject:"false"`
middlewares *middlewares.Middlewares
// Controller instances
contents *contents
orders *orders
tenants *tenants
users *users
contents *contents
coupons *coupons
creators *creators
orders *orders
reports *reports
tenants *tenants
users *users
withdrawals *withdrawals
}
// Prepare initializes the routes provider with logging configuration.
@@ -71,6 +75,18 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("id"),
Body[dto.SuperContentReviewForm]("form"),
))
// Register routes for controller: coupons
r.log.Debugf("Registering route: Get /super/v1/coupons -> coupons.List")
router.Get("/super/v1/coupons"[len(r.Path()):], DataFunc1(
r.coupons.List,
Query[dto.SuperCouponListFilter]("filter"),
))
// 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(
r.creators.List,
Query[dto.TenantListFilter]("filter"),
))
// Register routes for controller: orders
r.log.Debugf("Registering route: Get /super/v1/orders -> orders.List")
router.Get("/super/v1/orders"[len(r.Path()):], DataFunc1(
@@ -92,6 +108,17 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("id"),
Body[dto.SuperOrderRefundForm]("form"),
))
// 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(
r.reports.Overview,
Query[dto.SuperReportOverviewFilter]("filter"),
))
r.log.Debugf("Registering route: Post /super/v1/reports/export -> reports.Export")
router.Post("/super/v1/reports/export"[len(r.Path()):], DataFunc1(
r.reports.Export,
Body[dto.SuperReportExportForm]("form"),
))
// Register routes for controller: tenants
r.log.Debugf("Registering route: Get /super/v1/tenants -> tenants.List")
router.Get("/super/v1/tenants"[len(r.Path()):], DataFunc1(
@@ -172,6 +199,25 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("id"),
Body[dto.UserStatusUpdateForm]("form"),
))
// Register routes for controller: withdrawals
r.log.Debugf("Registering route: Get /super/v1/withdrawals -> withdrawals.List")
router.Get("/super/v1/withdrawals"[len(r.Path()):], DataFunc1(
r.withdrawals.List,
Query[dto.SuperOrderListFilter]("filter"),
))
r.log.Debugf("Registering route: Post /super/v1/withdrawals/:id<int>/approve -> withdrawals.Approve")
router.Post("/super/v1/withdrawals/:id<int>/approve"[len(r.Path()):], Func2(
r.withdrawals.Approve,
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Post /super/v1/withdrawals/:id<int>/reject -> withdrawals.Reject")
router.Post("/super/v1/withdrawals/:id<int>/reject"[len(r.Path()):], Func3(
r.withdrawals.Reject,
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
Body[dto.SuperWithdrawalRejectForm]("form"),
))
r.log.Info("Successfully registered all routes")
}

View File

@@ -0,0 +1,63 @@
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 withdrawals struct{}
// List withdrawals
//
// @Router /super/v1/withdrawals [get]
// @Summary List withdrawals
// @Description List withdrawal orders 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.SuperOrderItem}
// @Bind filter query
func (c *withdrawals) List(ctx fiber.Ctx, filter *dto.SuperOrderListFilter) (*requests.Pager, error) {
return services.Super.ListWithdrawals(ctx, filter)
}
// Approve withdrawal
//
// @Router /super/v1/withdrawals/:id<int>/approve [post]
// @Summary Approve withdrawal
// @Description Approve a withdrawal request
// @Tags Finance
// @Accept json
// @Produce json
// @Param id path int64 true "Withdrawal order ID"
// @Success 200 {string} string "Approved"
// @Bind user local key(__ctx_user)
// @Bind id path
func (c *withdrawals) Approve(ctx fiber.Ctx, user *models.User, id int64) error {
return services.Super.ApproveWithdrawal(ctx, user.ID, id)
}
// Reject withdrawal
//
// @Router /super/v1/withdrawals/:id<int>/reject [post]
// @Summary Reject withdrawal
// @Description Reject a withdrawal request
// @Tags Finance
// @Accept json
// @Produce json
// @Param id path int64 true "Withdrawal order ID"
// @Param form body dto.SuperWithdrawalRejectForm true "Reject form"
// @Success 200 {string} string "Rejected"
// @Bind user local key(__ctx_user)
// @Bind id path
// @Bind form body
func (c *withdrawals) Reject(ctx fiber.Ctx, user *models.User, id int64, form *dto.SuperWithdrawalRejectForm) error {
return services.Super.RejectWithdrawal(ctx, user.ID, id, form.Reason)
}

View File

@@ -3,6 +3,7 @@ package services
import (
"context"
"errors"
"strconv"
"strings"
"time"
@@ -2201,16 +2202,134 @@ func (s *super) evaluateTenantHealth(tenant *models.Tenant, metrics tenantHealth
}
func (s *super) ListWithdrawals(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperOrderListFilter{}
}
tbl, q := models.OrderQuery.QueryContext(ctx)
q = q.Where(tbl.Type.Eq(consts.OrderTypeWithdrawal))
if filter.ID != nil && *filter.ID > 0 {
q = q.Where(tbl.ID.Eq(*filter.ID))
}
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.Status != nil && *filter.Status != "" {
q = q.Where(tbl.Status.Eq(*filter.Status))
}
if filter.AmountPaidMin != nil {
q = q.Where(tbl.AmountPaid.Gte(*filter.AmountPaidMin))
}
if filter.AmountPaidMax != nil {
q = q.Where(tbl.AmountPaid.Lte(*filter.AmountPaidMax))
}
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))
}
}
if filter.PaidAtFrom != nil {
from, err := s.parseFilterTime(filter.PaidAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.PaidAt.Gte(*from))
}
}
if filter.PaidAtTo != nil {
to, err := s.parseFilterTime(filter.PaidAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.PaidAt.Lte(*to))
}
}
orderApplied := false
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
switch strings.TrimSpace(*filter.Desc) {
case "id":
q = q.Order(tbl.ID.Desc())
case "created_at":
q = q.Order(tbl.CreatedAt.Desc())
case "paid_at":
q = q.Order(tbl.PaidAt.Desc())
case "amount_paid":
q = q.Order(tbl.AmountPaid.Desc())
case "status":
q = q.Order(tbl.Status.Desc())
}
orderApplied = true
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
switch strings.TrimSpace(*filter.Asc) {
case "id":
q = q.Order(tbl.ID)
case "created_at":
q = q.Order(tbl.CreatedAt)
case "paid_at":
q = q.Order(tbl.PaidAt)
case "amount_paid":
q = q.Order(tbl.AmountPaid)
case "status":
q = q.Order(tbl.Status)
}
orderApplied = true
}
if !orderApplied {
q = q.Order(tbl.ID.Desc())
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -2226,6 +2345,436 @@ func (s *super) ListWithdrawals(ctx context.Context, filter *super_dto.SuperOrde
}, nil
}
func (s *super) ListCoupons(ctx context.Context, filter *super_dto.SuperCouponListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperCouponListFilter{}
}
tbl, q := models.CouponQuery.QueryContext(ctx)
if filter.ID != nil && *filter.ID > 0 {
q = q.Where(tbl.ID.Eq(*filter.ID))
}
if filter.TenantID != nil && *filter.TenantID > 0 {
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
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...))
}
}
if filter.Type != nil && strings.TrimSpace(*filter.Type) != "" {
parsed, err := consts.ParseCouponType(strings.TrimSpace(*filter.Type))
if err != nil {
return nil, errorx.ErrInvalidParameter.WithCause(err).WithMsg("优惠券类型无效")
}
q = q.Where(tbl.Type.Eq(parsed))
}
if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
keyword := strings.TrimSpace(*filter.Keyword)
q = q.Where(field.Or(
tbl.Title.Like("%"+keyword+"%"),
tbl.Description.Like("%"+keyword+"%"),
))
}
if filter.Status != nil && strings.TrimSpace(*filter.Status) != "" {
status := strings.ToLower(strings.TrimSpace(*filter.Status))
now := time.Now()
switch status {
case "active":
q = q.Where(field.Or(tbl.StartAt.Lte(now), tbl.StartAt.IsNull()))
q = q.Where(field.Or(tbl.EndAt.Gte(now), tbl.EndAt.IsNull()))
case "expired":
q = q.Where(tbl.EndAt.IsNotNull(), tbl.EndAt.Lt(now))
case "upcoming":
q = q.Where(tbl.StartAt.IsNotNull(), tbl.StartAt.Gt(now))
default:
return nil, errorx.ErrInvalidParameter.WithMsg("状态参数无效")
}
}
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))
}
}
orderApplied := false
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
switch strings.TrimSpace(*filter.Desc) {
case "id":
q = q.Order(tbl.ID.Desc())
case "created_at":
q = q.Order(tbl.CreatedAt.Desc())
case "start_at":
q = q.Order(tbl.StartAt.Desc())
case "end_at":
q = q.Order(tbl.EndAt.Desc())
}
orderApplied = true
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
switch strings.TrimSpace(*filter.Asc) {
case "id":
q = q.Order(tbl.ID)
case "created_at":
q = q.Order(tbl.CreatedAt)
case "start_at":
q = q.Order(tbl.StartAt)
case "end_at":
q = q.Order(tbl.EndAt)
}
orderApplied = true
}
if !orderApplied {
q = q.Order(tbl.CreatedAt.Desc())
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
items, err := s.buildSuperCouponItems(ctx, list)
if err != nil {
return nil, err
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperReportOverviewFilter) (*v1_dto.ReportOverviewResponse, error) {
// 统一统计时间范围与粒度。
rg, err := s.normalizeReportRange(filter)
if err != nil {
return nil, err
}
tenantID := int64(0)
if filter != nil && filter.TenantID != nil {
tenantID = *filter.TenantID
}
// 统计累计曝光(全量累计值,暂无按时间拆分的曝光记录)。
var totalViews int64
contentQuery := models.ContentQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.Content{}).
Select("coalesce(sum(views), 0)")
if tenantID > 0 {
contentQuery = contentQuery.Where("tenant_id = ?", tenantID)
}
if err := contentQuery.Scan(&totalViews).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
paidCount, paidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
if err != nil {
return nil, err
}
refundCount, refundAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
if err != nil {
return nil, err
}
conversionRate := 0.0
if totalViews > 0 {
conversionRate = float64(paidCount) / float64(totalViews)
}
paidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
if err != nil {
return nil, err
}
refundSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
if err != nil {
return nil, err
}
items := make([]v1_dto.ReportOverviewItem, 0)
for day := rg.startDay; !day.After(rg.endDay); day = day.AddDate(0, 0, 1) {
key := day.Format("2006-01-02")
paidItem := paidSeries[key]
refundItem := refundSeries[key]
items = append(items, v1_dto.ReportOverviewItem{
Date: key,
PaidOrders: paidItem.Count,
PaidAmount: float64(paidItem.Amount) / 100.0,
RefundOrders: refundItem.Count,
RefundAmount: float64(refundItem.Amount) / 100.0,
})
}
return &v1_dto.ReportOverviewResponse{
Summary: v1_dto.ReportSummary{
TotalViews: totalViews,
PaidOrders: paidCount,
PaidAmount: float64(paidAmount) / 100.0,
RefundOrders: refundCount,
RefundAmount: float64(refundAmount) / 100.0,
ConversionRate: conversionRate,
},
Items: items,
}, nil
}
func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExportForm) (*v1_dto.ReportExportResponse, error) {
if form == nil {
return nil, errorx.ErrBadRequest.WithMsg("导出参数不能为空")
}
format := strings.ToLower(strings.TrimSpace(form.Format))
if format == "" {
format = "csv"
}
if format != "csv" {
return nil, errorx.ErrBadRequest.WithMsg("仅支持 CSV 导出")
}
overview, err := s.ReportOverview(ctx, &super_dto.SuperReportOverviewFilter{
TenantID: form.TenantID,
StartAt: form.StartAt,
EndAt: form.EndAt,
Granularity: form.Granularity,
})
if err != nil {
return nil, err
}
builder := &strings.Builder{}
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount\n")
for _, item := range overview.Items {
builder.WriteString(item.Date)
builder.WriteString(",")
builder.WriteString(strconv.FormatInt(item.PaidOrders, 10))
builder.WriteString(",")
builder.WriteString(formatAmount(item.PaidAmount))
builder.WriteString(",")
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
builder.WriteString(",")
builder.WriteString(formatAmount(item.RefundAmount))
builder.WriteString("\n")
}
filename := "report_overview_" + time.Now().Format("20060102_150405") + ".csv"
return &v1_dto.ReportExportResponse{
Filename: filename,
MimeType: "text/csv",
Content: builder.String(),
}, nil
}
func (s *super) reportOrderAggregate(
ctx context.Context,
tenantID int64,
status consts.OrderStatus,
timeField string,
rg reportRange,
) (int64, int64, error) {
var total struct {
Count int64 `gorm:"column:count"`
Amount int64 `gorm:"column:amount"`
}
query := models.OrderQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.Order{}).
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Where("tenant_id = ?", tenantID)
}
if err := query.Scan(&total).Error; err != nil {
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
}
return total.Count, total.Amount, nil
}
func (s *super) reportOrderSeries(
ctx context.Context,
tenantID int64,
status consts.OrderStatus,
timeField string,
rg reportRange,
) (map[string]reportAggRow, error) {
rows := make([]reportAggRow, 0)
query := models.OrderQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.Order{}).
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Where("tenant_id = ?", tenantID)
}
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
result := make(map[string]reportAggRow, len(rows))
for _, row := range rows {
key := row.Day.Format("2006-01-02")
result[key] = row
}
return result, nil
}
func (s *super) normalizeReportRange(filter *super_dto.SuperReportOverviewFilter) (reportRange, error) {
granularity := "day"
if filter != nil && filter.Granularity != nil && strings.TrimSpace(*filter.Granularity) != "" {
granularity = strings.ToLower(strings.TrimSpace(*filter.Granularity))
}
if granularity != "day" {
return reportRange{}, errorx.ErrBadRequest.WithMsg("仅支持按天统计")
}
now := time.Now()
endAt := now
if filter != nil && filter.EndAt != nil && strings.TrimSpace(*filter.EndAt) != "" {
parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(*filter.EndAt))
if err != nil {
return reportRange{}, errorx.ErrBadRequest.WithMsg("结束时间格式错误")
}
endAt = parsed
}
startAt := endAt.AddDate(0, 0, -6)
if filter != nil && filter.StartAt != nil && strings.TrimSpace(*filter.StartAt) != "" {
parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(*filter.StartAt))
if err != nil {
return reportRange{}, errorx.ErrBadRequest.WithMsg("开始时间格式错误")
}
startAt = parsed
}
startDay := time.Date(startAt.Year(), startAt.Month(), startAt.Day(), 0, 0, 0, 0, startAt.Location())
endDay := time.Date(endAt.Year(), endAt.Month(), endAt.Day(), 0, 0, 0, 0, endAt.Location())
if endDay.Before(startDay) {
return reportRange{}, errorx.ErrBadRequest.WithMsg("结束时间不能早于开始时间")
}
endNext := endDay.AddDate(0, 0, 1)
return reportRange{
startDay: startDay,
endDay: endDay,
endNext: endNext,
}, nil
}
func (s *super) buildSuperCouponItems(ctx context.Context, list []*models.Coupon) ([]super_dto.SuperCouponItem, error) {
if len(list) == 0 {
return []super_dto.SuperCouponItem{}, nil
}
tenantIDs := make([]int64, 0, len(list))
seen := make(map[int64]struct{}, len(list))
for _, c := range list {
if c == nil {
continue
}
if _, ok := seen[c.TenantID]; ok {
continue
}
seen[c.TenantID] = struct{}{}
tenantIDs = append(tenantIDs, c.TenantID)
}
tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
if len(tenantIDs) > 0 {
tbl, q := models.TenantQuery.QueryContext(ctx)
tenants, err := q.Where(tbl.ID.In(tenantIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, t := range tenants {
tenantMap[t.ID] = t
}
}
items := make([]super_dto.SuperCouponItem, 0, len(list))
for _, c := range list {
if c == nil {
continue
}
items = append(items, s.toSuperCouponItem(c, tenantMap[c.TenantID]))
}
return items, nil
}
func (s *super) toSuperCouponItem(c *models.Coupon, tenant *models.Tenant) super_dto.SuperCouponItem {
status, statusDescription := s.resolveCouponStatus(c)
item := super_dto.SuperCouponItem{
ID: c.ID,
TenantID: c.TenantID,
Title: c.Title,
Description: c.Description,
Type: c.Type,
TypeDescription: c.Type.Description(),
Value: c.Value,
MinOrderAmount: c.MinOrderAmount,
MaxDiscount: c.MaxDiscount,
TotalQuantity: c.TotalQuantity,
UsedQuantity: c.UsedQuantity,
Status: status,
StatusDescription: statusDescription,
CreatedAt: s.formatTime(c.CreatedAt),
UpdatedAt: s.formatTime(c.UpdatedAt),
}
if tenant != nil {
item.TenantCode = tenant.Code
item.TenantName = tenant.Name
}
if !c.StartAt.IsZero() {
item.StartAt = s.formatTime(c.StartAt)
}
if !c.EndAt.IsZero() {
item.EndAt = s.formatTime(c.EndAt)
}
return item
}
func (s *super) resolveCouponStatus(c *models.Coupon) (string, string) {
now := time.Now()
if !c.EndAt.IsZero() && c.EndAt.Before(now) {
return "expired", "已过期"
}
if !c.StartAt.IsZero() && c.StartAt.After(now) {
return "upcoming", "未开始"
}
return "active", "生效中"
}
func (s *super) ApproveWithdrawal(ctx context.Context, operatorID, id int64) error {
if operatorID == 0 {
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")

View File

@@ -175,6 +175,110 @@ const docTemplate = `{
}
}
},
"/super/v1/coupons": {
"get": {
"description": "List coupon templates across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Coupon"
],
"summary": "List coupons",
"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.SuperCouponItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/creators": {
"get": {
"description": "List creator tenants (channels) across the platform",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Creator"
],
"summary": "List creators",
"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/orders": {
"get": {
"description": "List orders",
@@ -325,6 +429,63 @@ const docTemplate = `{
}
}
},
"/super/v1/reports/export": {
"post": {
"description": "Export platform report data",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Report"
],
"summary": "Export report",
"parameters": [
{
"description": "Export form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperReportExportForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.ReportExportResponse"
}
}
}
}
},
"/super/v1/reports/overview": {
"get": {
"description": "Get platform report overview",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Report"
],
"summary": "Report overview",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.ReportOverviewResponse"
}
}
}
}
},
"/super/v1/tenants": {
"get": {
"description": "List tenants",
@@ -1065,6 +1226,133 @@ const docTemplate = `{
}
}
},
"/super/v1/withdrawals": {
"get": {
"description": "List withdrawal orders across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "List withdrawals",
"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.SuperOrderItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/withdrawals/{id}/approve": {
"post": {
"description": "Approve a withdrawal request",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "Approve withdrawal",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Withdrawal order ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Approved",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/withdrawals/{id}/reject": {
"post": {
"description": "Reject a withdrawal request",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "Reject withdrawal",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Withdrawal order ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Reject form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperWithdrawalRejectForm"
}
}
],
"responses": {
"200": {
"description": "Rejected",
"schema": {
"type": "string"
}
}
}
}
},
"/t/{tenantCode}/v1/auth/login": {
"post": {
"description": "Login or register user using phone number and OTP",
@@ -3822,6 +4110,17 @@ const docTemplate = `{
"ContentStatusBlocked"
]
},
"consts.CouponType": {
"type": "string",
"enum": [
"fix_amount",
"discount"
],
"x-enum-varnames": [
"CouponTypeFixAmount",
"CouponTypeDiscount"
]
},
"consts.Currency": {
"type": "string",
"enum": [
@@ -5240,6 +5539,91 @@ const docTemplate = `{
}
}
},
"dto.SuperCouponItem": {
"type": "object",
"properties": {
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"description": {
"description": "Description 优惠券描述。",
"type": "string"
},
"end_at": {
"description": "EndAt 结束时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 优惠券ID。",
"type": "integer"
},
"max_discount": {
"description": "MaxDiscount 最大折扣金额(折扣券)。",
"type": "integer"
},
"min_order_amount": {
"description": "MinOrderAmount 最低订单金额门槛。",
"type": "integer"
},
"start_at": {
"description": "StartAt 生效时间RFC3339。",
"type": "string"
},
"status": {
"description": "Status 状态active/expired/upcoming。",
"type": "string"
},
"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"
},
"title": {
"description": "Title 优惠券标题。",
"type": "string"
},
"total_quantity": {
"description": "TotalQuantity 总发行数量0 表示不限量)。",
"type": "integer"
},
"type": {
"description": "Type 优惠券类型。",
"allOf": [
{
"$ref": "#/definitions/consts.CouponType"
}
]
},
"type_description": {
"description": "TypeDescription 类型描述(用于展示)。",
"type": "string"
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
},
"used_quantity": {
"description": "UsedQuantity 已使用数量。",
"type": "integer"
},
"value": {
"description": "Value 优惠券面额/折扣值。",
"type": "integer"
}
}
},
"dto.SuperOrderDetail": {
"type": "object",
"properties": {
@@ -5397,6 +5781,31 @@ const docTemplate = `{
}
}
},
"dto.SuperReportExportForm": {
"type": "object",
"properties": {
"end_at": {
"description": "EndAt 统计结束时间RFC3339可选默认当前时间。",
"type": "string"
},
"format": {
"description": "Format 导出格式(仅支持 csv。",
"type": "string"
},
"granularity": {
"description": "Granularity 统计粒度day目前仅支持 day。",
"type": "string"
},
"start_at": {
"description": "StartAt 统计开始时间RFC3339可选默认当前时间往前 7 天)。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID不传代表全平台。",
"type": "integer"
}
}
},
"dto.SuperTenantContentStatusUpdateForm": {
"type": "object",
"required": [
@@ -5482,6 +5891,18 @@ const docTemplate = `{
}
}
},
"dto.SuperWithdrawalRejectForm": {
"type": "object",
"required": [
"reason"
],
"properties": {
"reason": {
"description": "Reason 驳回原因。",
"type": "string"
}
}
},
"dto.TenantAdminUserLite": {
"type": "object",
"properties": {

View File

@@ -169,6 +169,110 @@
}
}
},
"/super/v1/coupons": {
"get": {
"description": "List coupon templates across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Coupon"
],
"summary": "List coupons",
"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.SuperCouponItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/creators": {
"get": {
"description": "List creator tenants (channels) across the platform",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Creator"
],
"summary": "List creators",
"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/orders": {
"get": {
"description": "List orders",
@@ -319,6 +423,63 @@
}
}
},
"/super/v1/reports/export": {
"post": {
"description": "Export platform report data",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Report"
],
"summary": "Export report",
"parameters": [
{
"description": "Export form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperReportExportForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.ReportExportResponse"
}
}
}
}
},
"/super/v1/reports/overview": {
"get": {
"description": "Get platform report overview",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Report"
],
"summary": "Report overview",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.ReportOverviewResponse"
}
}
}
}
},
"/super/v1/tenants": {
"get": {
"description": "List tenants",
@@ -1059,6 +1220,133 @@
}
}
},
"/super/v1/withdrawals": {
"get": {
"description": "List withdrawal orders across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "List withdrawals",
"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.SuperOrderItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/withdrawals/{id}/approve": {
"post": {
"description": "Approve a withdrawal request",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "Approve withdrawal",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Withdrawal order ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Approved",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/withdrawals/{id}/reject": {
"post": {
"description": "Reject a withdrawal request",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Finance"
],
"summary": "Reject withdrawal",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Withdrawal order ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Reject form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperWithdrawalRejectForm"
}
}
],
"responses": {
"200": {
"description": "Rejected",
"schema": {
"type": "string"
}
}
}
}
},
"/t/{tenantCode}/v1/auth/login": {
"post": {
"description": "Login or register user using phone number and OTP",
@@ -3816,6 +4104,17 @@
"ContentStatusBlocked"
]
},
"consts.CouponType": {
"type": "string",
"enum": [
"fix_amount",
"discount"
],
"x-enum-varnames": [
"CouponTypeFixAmount",
"CouponTypeDiscount"
]
},
"consts.Currency": {
"type": "string",
"enum": [
@@ -5234,6 +5533,91 @@
}
}
},
"dto.SuperCouponItem": {
"type": "object",
"properties": {
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"description": {
"description": "Description 优惠券描述。",
"type": "string"
},
"end_at": {
"description": "EndAt 结束时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 优惠券ID。",
"type": "integer"
},
"max_discount": {
"description": "MaxDiscount 最大折扣金额(折扣券)。",
"type": "integer"
},
"min_order_amount": {
"description": "MinOrderAmount 最低订单金额门槛。",
"type": "integer"
},
"start_at": {
"description": "StartAt 生效时间RFC3339。",
"type": "string"
},
"status": {
"description": "Status 状态active/expired/upcoming。",
"type": "string"
},
"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"
},
"title": {
"description": "Title 优惠券标题。",
"type": "string"
},
"total_quantity": {
"description": "TotalQuantity 总发行数量0 表示不限量)。",
"type": "integer"
},
"type": {
"description": "Type 优惠券类型。",
"allOf": [
{
"$ref": "#/definitions/consts.CouponType"
}
]
},
"type_description": {
"description": "TypeDescription 类型描述(用于展示)。",
"type": "string"
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
},
"used_quantity": {
"description": "UsedQuantity 已使用数量。",
"type": "integer"
},
"value": {
"description": "Value 优惠券面额/折扣值。",
"type": "integer"
}
}
},
"dto.SuperOrderDetail": {
"type": "object",
"properties": {
@@ -5391,6 +5775,31 @@
}
}
},
"dto.SuperReportExportForm": {
"type": "object",
"properties": {
"end_at": {
"description": "EndAt 统计结束时间RFC3339可选默认当前时间。",
"type": "string"
},
"format": {
"description": "Format 导出格式(仅支持 csv。",
"type": "string"
},
"granularity": {
"description": "Granularity 统计粒度day目前仅支持 day。",
"type": "string"
},
"start_at": {
"description": "StartAt 统计开始时间RFC3339可选默认当前时间往前 7 天)。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID不传代表全平台。",
"type": "integer"
}
}
},
"dto.SuperTenantContentStatusUpdateForm": {
"type": "object",
"required": [
@@ -5476,6 +5885,18 @@
}
}
},
"dto.SuperWithdrawalRejectForm": {
"type": "object",
"required": [
"reason"
],
"properties": {
"reason": {
"description": "Reason 驳回原因。",
"type": "string"
}
}
},
"dto.TenantAdminUserLite": {
"type": "object",
"properties": {

View File

@@ -14,6 +14,14 @@ definitions:
- ContentStatusPublished
- ContentStatusUnpublished
- ContentStatusBlocked
consts.CouponType:
enum:
- fix_amount
- discount
type: string
x-enum-varnames:
- CouponTypeFixAmount
- CouponTypeDiscount
consts.Currency:
enum:
- CNY
@@ -1021,6 +1029,67 @@ definitions:
description: Name 租户名称。
type: string
type: object
dto.SuperCouponItem:
properties:
created_at:
description: CreatedAt 创建时间RFC3339
type: string
description:
description: Description 优惠券描述。
type: string
end_at:
description: EndAt 结束时间RFC3339
type: string
id:
description: ID 优惠券ID。
type: integer
max_discount:
description: MaxDiscount 最大折扣金额(折扣券)。
type: integer
min_order_amount:
description: MinOrderAmount 最低订单金额门槛。
type: integer
start_at:
description: StartAt 生效时间RFC3339
type: string
status:
description: Status 状态active/expired/upcoming
type: string
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
title:
description: Title 优惠券标题。
type: string
total_quantity:
description: TotalQuantity 总发行数量0 表示不限量)。
type: integer
type:
allOf:
- $ref: '#/definitions/consts.CouponType'
description: Type 优惠券类型。
type_description:
description: TypeDescription 类型描述(用于展示)。
type: string
updated_at:
description: UpdatedAt 更新时间RFC3339
type: string
used_quantity:
description: UsedQuantity 已使用数量。
type: integer
value:
description: Value 优惠券面额/折扣值。
type: integer
type: object
dto.SuperOrderDetail:
properties:
buyer:
@@ -1119,6 +1188,24 @@ definitions:
description: Reason 退款原因说明。
type: string
type: object
dto.SuperReportExportForm:
properties:
end_at:
description: EndAt 统计结束时间RFC3339可选默认当前时间
type: string
format:
description: Format 导出格式(仅支持 csv
type: string
granularity:
description: Granularity 统计粒度day目前仅支持 day
type: string
start_at:
description: StartAt 统计开始时间RFC3339可选默认当前时间往前 7 天)。
type: string
tenant_id:
description: TenantID 租户ID不传代表全平台
type: integer
type: object
dto.SuperTenantContentStatusUpdateForm:
properties:
status:
@@ -1172,6 +1259,14 @@ definitions:
description: VerifiedAt 实名认证时间RFC3339
type: string
type: object
dto.SuperWithdrawalRejectForm:
properties:
reason:
description: Reason 驳回原因。
type: string
required:
- reason
type: object
dto.TenantAdminUserLite:
properties:
id:
@@ -2043,6 +2138,68 @@ paths:
summary: Review content
tags:
- Content
/super/v1/coupons:
get:
consumes:
- application/json
description: List coupon templates 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.SuperCouponItem'
type: array
type: object
summary: List coupons
tags:
- Coupon
/super/v1/creators:
get:
consumes:
- application/json
description: List creator tenants (channels) across the platform
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 creators
tags:
- Creator
/super/v1/orders:
get:
consumes:
@@ -2139,6 +2296,43 @@ paths:
summary: Order statistics
tags:
- Order
/super/v1/reports/export:
post:
consumes:
- application/json
description: Export platform report data
parameters:
- description: Export form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperReportExportForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.ReportExportResponse'
summary: Export report
tags:
- Report
/super/v1/reports/overview:
get:
consumes:
- application/json
description: Get platform report overview
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.ReportOverviewResponse'
summary: Report overview
tags:
- Report
/super/v1/tenants:
get:
consumes:
@@ -2612,6 +2806,87 @@ paths:
summary: User statuses
tags:
- User
/super/v1/withdrawals:
get:
consumes:
- application/json
description: List withdrawal orders 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.SuperOrderItem'
type: array
type: object
summary: List withdrawals
tags:
- Finance
/super/v1/withdrawals/{id}/approve:
post:
consumes:
- application/json
description: Approve a withdrawal request
parameters:
- description: Withdrawal order ID
format: int64
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Approved
schema:
type: string
summary: Approve withdrawal
tags:
- Finance
/super/v1/withdrawals/{id}/reject:
post:
consumes:
- application/json
description: Reject a withdrawal request
parameters:
- description: Withdrawal order ID
format: int64
in: path
name: id
required: true
type: integer
- description: Reject form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperWithdrawalRejectForm'
produces:
- application/json
responses:
"200":
description: Rejected
schema:
type: string
summary: Reject withdrawal
tags:
- Finance
/t/{tenantCode}/v1/auth/login:
post:
consumes: