feat: add creator report overview export
This commit is contained in:
@@ -69,6 +69,50 @@ func (c *Creator) CreateMemberInvite(
|
||||
return services.Tenant.CreateInvite(ctx, tenantID, user.ID, form)
|
||||
}
|
||||
|
||||
// Get report overview
|
||||
//
|
||||
// @Router /t/:tenantCode/v1/creator/reports/overview [get]
|
||||
// @Summary Report overview
|
||||
// @Description Get creator report overview
|
||||
// @Tags CreatorCenter
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param start_at query string false "Start time (RFC3339)"
|
||||
// @Param end_at query string false "End time (RFC3339)"
|
||||
// @Param granularity query string false "Granularity (day)"
|
||||
// @Success 200 {object} dto.ReportOverviewResponse
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind filter query
|
||||
func (c *Creator) ReportOverview(
|
||||
ctx fiber.Ctx,
|
||||
user *models.User,
|
||||
filter *dto.ReportOverviewFilter,
|
||||
) (*dto.ReportOverviewResponse, error) {
|
||||
tenantID := getTenantID(ctx)
|
||||
return services.Creator.ReportOverview(ctx, tenantID, user.ID, filter)
|
||||
}
|
||||
|
||||
// Export report overview
|
||||
//
|
||||
// @Router /t/:tenantCode/v1/creator/reports/export [post]
|
||||
// @Summary Export report overview
|
||||
// @Description Export creator report overview
|
||||
// @Tags CreatorCenter
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param form body dto.ReportExportForm true "Export form"
|
||||
// @Success 200 {object} dto.ReportExportResponse
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *Creator) ExportReport(
|
||||
ctx fiber.Ctx,
|
||||
user *models.User,
|
||||
form *dto.ReportExportForm,
|
||||
) (*dto.ReportExportResponse, error) {
|
||||
tenantID := getTenantID(ctx)
|
||||
return services.Creator.ExportReport(ctx, tenantID, user.ID, form)
|
||||
}
|
||||
|
||||
// Get creator dashboard stats
|
||||
//
|
||||
// @Router /t/:tenantCode/v1/creator/dashboard [get]
|
||||
|
||||
65
backend/app/http/v1/dto/creator_report.go
Normal file
65
backend/app/http/v1/dto/creator_report.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package dto
|
||||
|
||||
type ReportOverviewFilter struct {
|
||||
// StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。
|
||||
StartAt *string `query:"start_at"`
|
||||
// EndAt 统计结束时间(RFC3339,可选;默认当前时间)。
|
||||
EndAt *string `query:"end_at"`
|
||||
// Granularity 统计粒度(day;目前仅支持 day)。
|
||||
Granularity *string `query:"granularity"`
|
||||
}
|
||||
|
||||
type ReportOverviewResponse struct {
|
||||
// Summary 汇总指标。
|
||||
Summary ReportSummary `json:"summary"`
|
||||
// Items 按日期拆分的趋势数据。
|
||||
Items []ReportOverviewItem `json:"items"`
|
||||
}
|
||||
|
||||
type ReportSummary struct {
|
||||
// TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
||||
TotalViews int64 `json:"total_views"`
|
||||
// PaidOrders 统计区间内已支付订单数。
|
||||
PaidOrders int64 `json:"paid_orders"`
|
||||
// PaidAmount 统计区间内已支付金额(单位元)。
|
||||
PaidAmount float64 `json:"paid_amount"`
|
||||
// RefundOrders 统计区间内退款订单数。
|
||||
RefundOrders int64 `json:"refund_orders"`
|
||||
// RefundAmount 统计区间内退款金额(单位元)。
|
||||
RefundAmount float64 `json:"refund_amount"`
|
||||
// ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
||||
ConversionRate float64 `json:"conversion_rate"`
|
||||
}
|
||||
|
||||
type ReportOverviewItem struct {
|
||||
// Date 日期(YYYY-MM-DD)。
|
||||
Date string `json:"date"`
|
||||
// PaidOrders 当日已支付订单数。
|
||||
PaidOrders int64 `json:"paid_orders"`
|
||||
// PaidAmount 当日已支付金额(单位元)。
|
||||
PaidAmount float64 `json:"paid_amount"`
|
||||
// RefundOrders 当日退款订单数。
|
||||
RefundOrders int64 `json:"refund_orders"`
|
||||
// RefundAmount 当日退款金额(单位元)。
|
||||
RefundAmount float64 `json:"refund_amount"`
|
||||
}
|
||||
|
||||
type ReportExportForm struct {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type ReportExportResponse struct {
|
||||
// Filename 导出文件名。
|
||||
Filename string `json:"filename"`
|
||||
// MimeType 导出内容类型。
|
||||
MimeType string `json:"mime_type"`
|
||||
// Content 导出内容(CSV 文本)。
|
||||
Content string `json:"content"`
|
||||
}
|
||||
@@ -191,6 +191,12 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.creator.ListPayoutAccounts,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/creator/reports/overview -> creator.ReportOverview")
|
||||
router.Get("/t/:tenantCode/v1/creator/reports/overview"[len(r.Path()):], DataFunc2(
|
||||
r.creator.ReportOverview,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
Query[dto.ReportOverviewFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/creator/settings -> creator.GetSettings")
|
||||
router.Get("/t/:tenantCode/v1/creator/settings"[len(r.Path()):], DataFunc1(
|
||||
r.creator.GetSettings,
|
||||
@@ -234,6 +240,12 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
Local[*models.User]("__ctx_user"),
|
||||
Body[dto.PayoutAccount]("form"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/reports/export -> creator.ExportReport")
|
||||
router.Post("/t/:tenantCode/v1/creator/reports/export"[len(r.Path()):], DataFunc2(
|
||||
r.creator.ExportReport,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
Body[dto.ReportExportForm]("form"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/withdraw -> creator.Withdraw")
|
||||
router.Post("/t/:tenantCode/v1/creator/withdraw"[len(r.Path()):], Func2(
|
||||
r.creator.Withdraw,
|
||||
|
||||
256
backend/app/services/creator_report.go
Normal file
256
backend/app/services/creator_report.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
creator_dto "quyun/v2/app/http/v1/dto"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
)
|
||||
|
||||
type reportRange struct {
|
||||
startDay time.Time
|
||||
endDay time.Time
|
||||
endNext time.Time
|
||||
}
|
||||
|
||||
func (s *creator) ReportOverview(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
userID int64,
|
||||
filter *creator_dto.ReportOverviewFilter,
|
||||
) (*creator_dto.ReportOverviewResponse, error) {
|
||||
// 校验租户归属(仅租户主可查看统计)。
|
||||
tid, err := s.getTenantID(ctx, tenantID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 统一统计时间范围与粒度。
|
||||
rg, err := s.normalizeReportRange(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 统计累计曝光(全量累计值,暂无按时间拆分的曝光记录)。
|
||||
var totalViews int64
|
||||
err = models.ContentQuery.WithContext(ctx).
|
||||
UnderlyingDB().
|
||||
Model(&models.Content{}).
|
||||
Select("coalesce(sum(views), 0)").
|
||||
Where("tenant_id = ?", tid).
|
||||
Scan(&totalViews).Error
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
||||
paidCount, paidAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refundCount, refundAmount, err := s.orderAggregate(ctx, tid, 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.orderSeries(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refundSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]creator_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, creator_dto.ReportOverviewItem{
|
||||
Date: key,
|
||||
PaidOrders: paidItem.Count,
|
||||
PaidAmount: float64(paidItem.Amount) / 100.0,
|
||||
RefundOrders: refundItem.Count,
|
||||
RefundAmount: float64(refundItem.Amount) / 100.0,
|
||||
})
|
||||
}
|
||||
|
||||
return &creator_dto.ReportOverviewResponse{
|
||||
Summary: creator_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 *creator) ExportReport(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
userID int64,
|
||||
form *creator_dto.ReportExportForm,
|
||||
) (*creator_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, tenantID, userID, &creator_dto.ReportOverviewFilter{
|
||||
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 := fmt.Sprintf("report_overview_%s.csv", time.Now().Format("20060102_150405"))
|
||||
return &creator_dto.ReportExportResponse{
|
||||
Filename: filename,
|
||||
MimeType: "text/csv",
|
||||
Content: builder.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type reportAggRow struct {
|
||||
Day time.Time `gorm:"column:day"`
|
||||
Count int64 `gorm:"column:count"`
|
||||
Amount int64 `gorm:"column:amount"`
|
||||
}
|
||||
|
||||
func (s *creator) orderAggregate(
|
||||
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"`
|
||||
}
|
||||
err := models.OrderQuery.WithContext(ctx).
|
||||
UnderlyingDB().
|
||||
Model(&models.Order{}).
|
||||
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||
tenantID, consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext).
|
||||
Scan(&total).Error
|
||||
if err != nil {
|
||||
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return total.Count, total.Amount, nil
|
||||
}
|
||||
|
||||
func (s *creator) orderSeries(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
status consts.OrderStatus,
|
||||
timeField string,
|
||||
rg reportRange,
|
||||
) (map[string]reportAggRow, error) {
|
||||
rows := make([]reportAggRow, 0)
|
||||
err := 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("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||
tenantID, consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext).
|
||||
Group("day").
|
||||
Scan(&rows).Error
|
||||
if 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 *creator) normalizeReportRange(filter *creator_dto.ReportOverviewFilter) (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 formatAmount(amount float64) string {
|
||||
return strconv.FormatFloat(amount, 'f', 2, 64)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/commands/testx"
|
||||
creator_dto "quyun/v2/app/http/v1/dto"
|
||||
@@ -384,3 +385,128 @@ func (s *CreatorTestSuite) Test_Refund() {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CreatorTestSuite) Test_ReportOverview() {
|
||||
Convey("ReportOverview", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(ctx, s.DB,
|
||||
models.TableNameTenant,
|
||||
models.TableNameUser,
|
||||
models.TableNameContent,
|
||||
models.TableNameOrder,
|
||||
)
|
||||
|
||||
owner := &models.User{Username: "owner_r", Phone: "13900001011"}
|
||||
models.UserQuery.WithContext(ctx).Create(owner)
|
||||
tenant := &models.Tenant{
|
||||
Name: "Tenant Report",
|
||||
UserID: owner.ID,
|
||||
Status: consts.TenantStatusVerified,
|
||||
}
|
||||
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||
|
||||
models.ContentQuery.WithContext(ctx).Create(&models.Content{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Title: "Content A",
|
||||
Status: consts.ContentStatusPublished,
|
||||
Views: 100,
|
||||
})
|
||||
|
||||
now := time.Now()
|
||||
inRangePaidAt := now.Add(-12 * time.Hour)
|
||||
outRangePaidAt := now.Add(-10 * 24 * time.Hour)
|
||||
|
||||
models.OrderQuery.WithContext(ctx).Create(
|
||||
&models.Order{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Type: consts.OrderTypeContentPurchase,
|
||||
Status: consts.OrderStatusPaid,
|
||||
AmountPaid: 1000,
|
||||
PaidAt: inRangePaidAt,
|
||||
},
|
||||
&models.Order{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Type: consts.OrderTypeContentPurchase,
|
||||
Status: consts.OrderStatusPaid,
|
||||
AmountPaid: 2000,
|
||||
PaidAt: outRangePaidAt,
|
||||
},
|
||||
&models.Order{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Type: consts.OrderTypeContentPurchase,
|
||||
Status: consts.OrderStatusRefunded,
|
||||
AmountPaid: 500,
|
||||
UpdatedAt: now.Add(-6 * time.Hour),
|
||||
},
|
||||
)
|
||||
|
||||
start := now.Add(-24 * time.Hour).Format(time.RFC3339)
|
||||
end := now.Format(time.RFC3339)
|
||||
report, err := Creator.ReportOverview(ctx, tenant.ID, owner.ID, &creator_dto.ReportOverviewFilter{
|
||||
StartAt: &start,
|
||||
EndAt: &end,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(report.Summary.TotalViews, ShouldEqual, 100)
|
||||
So(report.Summary.PaidOrders, ShouldEqual, 1)
|
||||
So(report.Summary.PaidAmount, ShouldEqual, 10.0)
|
||||
So(report.Summary.RefundOrders, ShouldEqual, 1)
|
||||
So(report.Summary.RefundAmount, ShouldEqual, 5.0)
|
||||
|
||||
var paidSum, refundSum int64
|
||||
for _, item := range report.Items {
|
||||
paidSum += item.PaidOrders
|
||||
refundSum += item.RefundOrders
|
||||
}
|
||||
So(paidSum, ShouldEqual, report.Summary.PaidOrders)
|
||||
So(refundSum, ShouldEqual, report.Summary.RefundOrders)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CreatorTestSuite) Test_ExportReport() {
|
||||
Convey("ExportReport", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(ctx, s.DB,
|
||||
models.TableNameTenant,
|
||||
models.TableNameUser,
|
||||
models.TableNameContent,
|
||||
models.TableNameOrder,
|
||||
)
|
||||
|
||||
owner := &models.User{Username: "owner_e", Phone: "13900001012"}
|
||||
models.UserQuery.WithContext(ctx).Create(owner)
|
||||
tenant := &models.Tenant{
|
||||
Name: "Tenant Export",
|
||||
UserID: owner.ID,
|
||||
Status: consts.TenantStatusVerified,
|
||||
}
|
||||
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||
|
||||
models.ContentQuery.WithContext(ctx).Create(&models.Content{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Title: "Content Export",
|
||||
Status: consts.ContentStatusPublished,
|
||||
Views: 10,
|
||||
})
|
||||
|
||||
models.OrderQuery.WithContext(ctx).Create(&models.Order{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Type: consts.OrderTypeContentPurchase,
|
||||
Status: consts.OrderStatusPaid,
|
||||
AmountPaid: 1200,
|
||||
PaidAt: time.Now().Add(-2 * time.Hour),
|
||||
})
|
||||
|
||||
form := &creator_dto.ReportExportForm{Format: "csv"}
|
||||
resp, err := Creator.ExportReport(ctx, tenant.ID, owner.ID, form)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.Filename, ShouldNotBeBlank)
|
||||
So(resp.Content, ShouldContainSubstring, "date,paid_orders,paid_amount,refund_orders,refund_amount")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1884,6 +1884,83 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/creator/reports/export": {
|
||||
"post": {
|
||||
"description": "Export creator report overview",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"CreatorCenter"
|
||||
],
|
||||
"summary": "Export report overview",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Export form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ReportExportForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ReportExportResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/creator/reports/overview": {
|
||||
"get": {
|
||||
"description": "Get creator report overview",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"CreatorCenter"
|
||||
],
|
||||
"summary": "Report overview",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Start time (RFC3339)",
|
||||
"name": "start_at",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "End time (RFC3339)",
|
||||
"name": "end_at",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Granularity (day)",
|
||||
"name": "granularity",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ReportOverviewResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/creator/settings": {
|
||||
"get": {
|
||||
"description": "Get channel settings",
|
||||
@@ -4442,6 +4519,118 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportExportForm": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportExportResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content 导出内容(CSV 文本)。",
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"description": "Filename 导出文件名。",
|
||||
"type": "string"
|
||||
},
|
||||
"mime_type": {
|
||||
"description": "MimeType 导出内容类型。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportOverviewItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"description": "Date 日期(YYYY-MM-DD)。",
|
||||
"type": "string"
|
||||
},
|
||||
"paid_amount": {
|
||||
"description": "PaidAmount 当日已支付金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"paid_orders": {
|
||||
"description": "PaidOrders 当日已支付订单数。",
|
||||
"type": "integer"
|
||||
},
|
||||
"refund_amount": {
|
||||
"description": "RefundAmount 当日退款金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"refund_orders": {
|
||||
"description": "RefundOrders 当日退款订单数。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportOverviewResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"description": "Items 按日期拆分的趋势数据。",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.ReportOverviewItem"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"description": "Summary 汇总指标。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/dto.ReportSummary"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"conversion_rate": {
|
||||
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
||||
"type": "number"
|
||||
},
|
||||
"paid_amount": {
|
||||
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"paid_orders": {
|
||||
"description": "PaidOrders 统计区间内已支付订单数。",
|
||||
"type": "integer"
|
||||
},
|
||||
"refund_amount": {
|
||||
"description": "RefundAmount 统计区间内退款金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"refund_orders": {
|
||||
"description": "RefundOrders 统计区间内退款订单数。",
|
||||
"type": "integer"
|
||||
},
|
||||
"total_views": {
|
||||
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1878,6 +1878,83 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/creator/reports/export": {
|
||||
"post": {
|
||||
"description": "Export creator report overview",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"CreatorCenter"
|
||||
],
|
||||
"summary": "Export report overview",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Export form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ReportExportForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ReportExportResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/creator/reports/overview": {
|
||||
"get": {
|
||||
"description": "Get creator report overview",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"CreatorCenter"
|
||||
],
|
||||
"summary": "Report overview",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Start time (RFC3339)",
|
||||
"name": "start_at",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "End time (RFC3339)",
|
||||
"name": "end_at",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Granularity (day)",
|
||||
"name": "granularity",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.ReportOverviewResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/creator/settings": {
|
||||
"get": {
|
||||
"description": "Get channel settings",
|
||||
@@ -4436,6 +4513,118 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportExportForm": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportExportResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content 导出内容(CSV 文本)。",
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"description": "Filename 导出文件名。",
|
||||
"type": "string"
|
||||
},
|
||||
"mime_type": {
|
||||
"description": "MimeType 导出内容类型。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportOverviewItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"description": "Date 日期(YYYY-MM-DD)。",
|
||||
"type": "string"
|
||||
},
|
||||
"paid_amount": {
|
||||
"description": "PaidAmount 当日已支付金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"paid_orders": {
|
||||
"description": "PaidOrders 当日已支付订单数。",
|
||||
"type": "integer"
|
||||
},
|
||||
"refund_amount": {
|
||||
"description": "RefundAmount 当日退款金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"refund_orders": {
|
||||
"description": "RefundOrders 当日退款订单数。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportOverviewResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"description": "Items 按日期拆分的趋势数据。",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.ReportOverviewItem"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"description": "Summary 汇总指标。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/dto.ReportSummary"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ReportSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"conversion_rate": {
|
||||
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
||||
"type": "number"
|
||||
},
|
||||
"paid_amount": {
|
||||
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"paid_orders": {
|
||||
"description": "PaidOrders 统计区间内已支付订单数。",
|
||||
"type": "integer"
|
||||
},
|
||||
"refund_amount": {
|
||||
"description": "RefundAmount 统计区间内退款金额(单位元)。",
|
||||
"type": "number"
|
||||
},
|
||||
"refund_orders": {
|
||||
"description": "RefundOrders 统计区间内退款订单数。",
|
||||
"type": "integer"
|
||||
},
|
||||
"total_views": {
|
||||
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -768,6 +768,84 @@ definitions:
|
||||
description: Reason 退款原因/备注。
|
||||
type: string
|
||||
type: object
|
||||
dto.ReportExportForm:
|
||||
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
|
||||
type: object
|
||||
dto.ReportExportResponse:
|
||||
properties:
|
||||
content:
|
||||
description: Content 导出内容(CSV 文本)。
|
||||
type: string
|
||||
filename:
|
||||
description: Filename 导出文件名。
|
||||
type: string
|
||||
mime_type:
|
||||
description: MimeType 导出内容类型。
|
||||
type: string
|
||||
type: object
|
||||
dto.ReportOverviewItem:
|
||||
properties:
|
||||
date:
|
||||
description: Date 日期(YYYY-MM-DD)。
|
||||
type: string
|
||||
paid_amount:
|
||||
description: PaidAmount 当日已支付金额(单位元)。
|
||||
type: number
|
||||
paid_orders:
|
||||
description: PaidOrders 当日已支付订单数。
|
||||
type: integer
|
||||
refund_amount:
|
||||
description: RefundAmount 当日退款金额(单位元)。
|
||||
type: number
|
||||
refund_orders:
|
||||
description: RefundOrders 当日退款订单数。
|
||||
type: integer
|
||||
type: object
|
||||
dto.ReportOverviewResponse:
|
||||
properties:
|
||||
items:
|
||||
description: Items 按日期拆分的趋势数据。
|
||||
items:
|
||||
$ref: '#/definitions/dto.ReportOverviewItem'
|
||||
type: array
|
||||
summary:
|
||||
allOf:
|
||||
- $ref: '#/definitions/dto.ReportSummary'
|
||||
description: Summary 汇总指标。
|
||||
type: object
|
||||
dto.ReportSummary:
|
||||
properties:
|
||||
conversion_rate:
|
||||
description: ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
||||
type: number
|
||||
paid_amount:
|
||||
description: PaidAmount 统计区间内已支付金额(单位元)。
|
||||
type: number
|
||||
paid_orders:
|
||||
description: PaidOrders 统计区间内已支付订单数。
|
||||
type: integer
|
||||
refund_amount:
|
||||
description: RefundAmount 统计区间内退款金额(单位元)。
|
||||
type: number
|
||||
refund_orders:
|
||||
description: RefundOrders 统计区间内退款订单数。
|
||||
type: integer
|
||||
total_views:
|
||||
description: TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
||||
type: integer
|
||||
type: object
|
||||
dto.Settings:
|
||||
properties:
|
||||
avatar:
|
||||
@@ -2885,6 +2963,56 @@ paths:
|
||||
summary: Add payout account
|
||||
tags:
|
||||
- CreatorCenter
|
||||
/t/{tenantCode}/v1/creator/reports/export:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Export creator report overview
|
||||
parameters:
|
||||
- description: Export form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.ReportExportForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.ReportExportResponse'
|
||||
summary: Export report overview
|
||||
tags:
|
||||
- CreatorCenter
|
||||
/t/{tenantCode}/v1/creator/reports/overview:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get creator report overview
|
||||
parameters:
|
||||
- description: Start time (RFC3339)
|
||||
in: query
|
||||
name: start_at
|
||||
type: string
|
||||
- description: End time (RFC3339)
|
||||
in: query
|
||||
name: end_at
|
||||
type: string
|
||||
- description: Granularity (day)
|
||||
in: query
|
||||
name: granularity
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.ReportOverviewResponse'
|
||||
summary: Report overview
|
||||
tags:
|
||||
- CreatorCenter
|
||||
/t/{tenantCode}/v1/creator/settings:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
Reference in New Issue
Block a user