Add monthly revenue trend for analytics when year is specified.
Exposes payments.revenue_monthly with Jan–Dec SUCCESS totals (UTC) per currency for dashboard charts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a1696bf1e0
commit
2883561525
|
|
@ -206,6 +206,35 @@ LEFT JOIN payments p ON COALESCE(p.paid_at, p.created_at)::date = d.date
|
||||||
GROUP BY d.date
|
GROUP BY d.date
|
||||||
ORDER BY d.date;
|
ORDER BY d.date;
|
||||||
|
|
||||||
|
-- Monthly successful revenue for a calendar year (UTC buckets). Months with multiple currencies emit one row each; months with no revenue emit one row (currency ETB, revenue 0).
|
||||||
|
-- name: AnalyticsRevenueMonthlyByYear :many
|
||||||
|
WITH months AS (
|
||||||
|
SELECT bucket
|
||||||
|
FROM generate_series(
|
||||||
|
make_timestamptz(sqlc.arg('report_year')::int, 1, 1, 0, 0, 0, 'UTC'),
|
||||||
|
make_timestamptz(sqlc.arg('report_year')::int, 12, 1, 0, 0, 0, 'UTC'),
|
||||||
|
INTERVAL '1 month'
|
||||||
|
) AS gs(bucket)
|
||||||
|
),
|
||||||
|
by_month_currency AS (
|
||||||
|
SELECT
|
||||||
|
date_trunc('month', COALESCE(p.paid_at, p.created_at) AT TIME ZONE 'UTC') AS ym,
|
||||||
|
p.currency,
|
||||||
|
SUM(p.amount)::float8 AS total_revenue
|
||||||
|
FROM payments p
|
||||||
|
WHERE p.status = 'SUCCESS'
|
||||||
|
AND EXTRACT(YEAR FROM COALESCE(p.paid_at, p.created_at) AT TIME ZONE 'UTC')::int = sqlc.arg('report_year')::int
|
||||||
|
GROUP BY 1, 2
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
(EXTRACT(MONTH FROM m.bucket AT TIME ZONE 'UTC'))::int AS month,
|
||||||
|
date_trunc('month', m.bucket AT TIME ZONE 'UTC')::date AS month_start,
|
||||||
|
COALESCE(b.currency, 'ETB'::varchar) AS currency,
|
||||||
|
COALESCE(b.total_revenue, 0)::float8 AS total_revenue
|
||||||
|
FROM months m
|
||||||
|
LEFT JOIN by_month_currency b ON b.ym = date_trunc('month', m.bucket AT TIME ZONE 'UTC')
|
||||||
|
ORDER BY m.bucket, COALESCE(b.currency, ''::varchar);
|
||||||
|
|
||||||
-- =====================
|
-- =====================
|
||||||
-- Course Analytics
|
-- Course Analytics
|
||||||
-- =====================
|
-- =====================
|
||||||
|
|
|
||||||
37
docs/docs.go
37
docs/docs.go
|
|
@ -704,7 +704,7 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/api/v1/analytics/dashboard": {
|
"/api/v1/analytics/dashboard": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range.",
|
"description": "Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range. When year is set, payments.revenue_monthly returns Jan–Dec SUCCESS revenue totals (UTC) per currency for that calendar year — use for yearly revenue charts. Daily series remains in revenue_last_30_days (see date_filter.series_*). Courses section counts LMS + exam_prep inventory.",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -9835,6 +9835,30 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.AnalyticsMonthlyRevenuePoint": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"description": "Short English month label, e.g. Jan",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"description": "1–12",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"month_start": {
|
||||||
|
"description": "UTC date of month start (for sorting / tooltips)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"revenue": {
|
||||||
|
"description": "SUCCESS payments aggregate for that bucket",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.AnalyticsNotificationsSection": {
|
"domain.AnalyticsNotificationsSection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -9879,12 +9903,23 @@ const docTemplate = `{
|
||||||
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"monthly_revenue_year": {
|
||||||
|
"description": "MonthlyRevenueYear is set when RevenueMonthly is non-empty (the calendar year of those buckets).",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"revenue_last_30_days": {
|
"revenue_last_30_days": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/domain.AnalyticsRevenueTimePoint"
|
"$ref": "#/definitions/domain.AnalyticsRevenueTimePoint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"revenue_monthly": {
|
||||||
|
"description": "RevenueMonthly is populated only when the request includes year=..., with 12 months (possibly multiple currencies per month).",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.AnalyticsMonthlyRevenuePoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
"successful_payments": {
|
"successful_payments": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -696,7 +696,7 @@
|
||||||
},
|
},
|
||||||
"/api/v1/analytics/dashboard": {
|
"/api/v1/analytics/dashboard": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range.",
|
"description": "Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range. When year is set, payments.revenue_monthly returns Jan–Dec SUCCESS revenue totals (UTC) per currency for that calendar year — use for yearly revenue charts. Daily series remains in revenue_last_30_days (see date_filter.series_*). Courses section counts LMS + exam_prep inventory.",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -9827,6 +9827,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.AnalyticsMonthlyRevenuePoint": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"description": "Short English month label, e.g. Jan",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"description": "1–12",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"month_start": {
|
||||||
|
"description": "UTC date of month start (for sorting / tooltips)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"revenue": {
|
||||||
|
"description": "SUCCESS payments aggregate for that bucket",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.AnalyticsNotificationsSection": {
|
"domain.AnalyticsNotificationsSection": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -9871,12 +9895,23 @@
|
||||||
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"monthly_revenue_year": {
|
||||||
|
"description": "MonthlyRevenueYear is set when RevenueMonthly is non-empty (the calendar year of those buckets).",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"revenue_last_30_days": {
|
"revenue_last_30_days": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/domain.AnalyticsRevenueTimePoint"
|
"$ref": "#/definitions/domain.AnalyticsRevenueTimePoint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"revenue_monthly": {
|
||||||
|
"description": "RevenueMonthly is populated only when the request includes year=..., with 12 months (possibly multiple currencies per month).",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.AnalyticsMonthlyRevenuePoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
"successful_payments": {
|
"successful_payments": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,23 @@ definitions:
|
||||||
label:
|
label:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.AnalyticsMonthlyRevenuePoint:
|
||||||
|
properties:
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
label:
|
||||||
|
description: Short English month label, e.g. Jan
|
||||||
|
type: string
|
||||||
|
month:
|
||||||
|
description: 1–12
|
||||||
|
type: integer
|
||||||
|
month_start:
|
||||||
|
description: UTC date of month start (for sorting / tooltips)
|
||||||
|
type: string
|
||||||
|
revenue:
|
||||||
|
description: SUCCESS payments aggregate for that bucket
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
domain.AnalyticsNotificationsSection:
|
domain.AnalyticsNotificationsSection:
|
||||||
properties:
|
properties:
|
||||||
by_channel:
|
by_channel:
|
||||||
|
|
@ -193,10 +210,20 @@ definitions:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/domain.AnalyticsLabelAmount'
|
$ref: '#/definitions/domain.AnalyticsLabelAmount'
|
||||||
type: array
|
type: array
|
||||||
|
monthly_revenue_year:
|
||||||
|
description: MonthlyRevenueYear is set when RevenueMonthly is non-empty (the
|
||||||
|
calendar year of those buckets).
|
||||||
|
type: integer
|
||||||
revenue_last_30_days:
|
revenue_last_30_days:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/domain.AnalyticsRevenueTimePoint'
|
$ref: '#/definitions/domain.AnalyticsRevenueTimePoint'
|
||||||
type: array
|
type: array
|
||||||
|
revenue_monthly:
|
||||||
|
description: RevenueMonthly is populated only when the request includes year=...,
|
||||||
|
with 12 months (possibly multiple currencies per month).
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.AnalyticsMonthlyRevenuePoint'
|
||||||
|
type: array
|
||||||
successful_payments:
|
successful_payments:
|
||||||
type: integer
|
type: integer
|
||||||
total_payments:
|
total_payments:
|
||||||
|
|
@ -2907,7 +2934,10 @@ paths:
|
||||||
/api/v1/analytics/dashboard:
|
/api/v1/analytics/dashboard:
|
||||||
get:
|
get:
|
||||||
description: 'Platform analytics with optional date filters: all-time (default),
|
description: 'Platform analytics with optional date filters: all-time (default),
|
||||||
year, year+month, or custom from/to range.'
|
year, year+month, or custom from/to range. When year is set, payments.revenue_monthly
|
||||||
|
returns Jan–Dec SUCCESS revenue totals (UTC) per currency for that calendar
|
||||||
|
year — use for yearly revenue charts. Daily series remains in revenue_last_30_days
|
||||||
|
(see date_filter.series_*). Courses section counts LMS + exam_prep inventory.'
|
||||||
parameters:
|
parameters:
|
||||||
- description: Calendar year (e.g. 2025)
|
- description: Calendar year (e.g. 2025)
|
||||||
in: query
|
in: query
|
||||||
|
|
|
||||||
|
|
@ -728,6 +728,68 @@ func (q *Queries) AnalyticsRevenueLast30Days(ctx context.Context, arg AnalyticsR
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AnalyticsRevenueMonthlyByYear = `-- name: AnalyticsRevenueMonthlyByYear :many
|
||||||
|
WITH months AS (
|
||||||
|
SELECT bucket
|
||||||
|
FROM generate_series(
|
||||||
|
make_timestamptz($1::int, 1, 1, 0, 0, 0, 'UTC'),
|
||||||
|
make_timestamptz($1::int, 12, 1, 0, 0, 0, 'UTC'),
|
||||||
|
INTERVAL '1 month'
|
||||||
|
) AS gs(bucket)
|
||||||
|
),
|
||||||
|
by_month_currency AS (
|
||||||
|
SELECT
|
||||||
|
date_trunc('month', COALESCE(p.paid_at, p.created_at) AT TIME ZONE 'UTC') AS ym,
|
||||||
|
p.currency,
|
||||||
|
SUM(p.amount)::float8 AS total_revenue
|
||||||
|
FROM payments p
|
||||||
|
WHERE p.status = 'SUCCESS'
|
||||||
|
AND EXTRACT(YEAR FROM COALESCE(p.paid_at, p.created_at) AT TIME ZONE 'UTC')::int = $1::int
|
||||||
|
GROUP BY 1, 2
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
(EXTRACT(MONTH FROM m.bucket AT TIME ZONE 'UTC'))::int AS month,
|
||||||
|
date_trunc('month', m.bucket AT TIME ZONE 'UTC')::date AS month_start,
|
||||||
|
COALESCE(b.currency, 'ETB'::varchar) AS currency,
|
||||||
|
COALESCE(b.total_revenue, 0)::float8 AS total_revenue
|
||||||
|
FROM months m
|
||||||
|
LEFT JOIN by_month_currency b ON b.ym = date_trunc('month', m.bucket AT TIME ZONE 'UTC')
|
||||||
|
ORDER BY m.bucket, COALESCE(b.currency, ''::varchar)
|
||||||
|
`
|
||||||
|
|
||||||
|
type AnalyticsRevenueMonthlyByYearRow struct {
|
||||||
|
Month int32 `json:"month"`
|
||||||
|
MonthStart pgtype.Date `json:"month_start"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
TotalRevenue float64 `json:"total_revenue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monthly successful revenue for a calendar year (UTC buckets). Months with multiple currencies emit one row each; months with no revenue emit one row (currency ETB, revenue 0).
|
||||||
|
func (q *Queries) AnalyticsRevenueMonthlyByYear(ctx context.Context, reportYear int32) ([]AnalyticsRevenueMonthlyByYearRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, AnalyticsRevenueMonthlyByYear, reportYear)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []AnalyticsRevenueMonthlyByYearRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i AnalyticsRevenueMonthlyByYearRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.Month,
|
||||||
|
&i.MonthStart,
|
||||||
|
&i.Currency,
|
||||||
|
&i.TotalRevenue,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const AnalyticsSubscriptionsByStatus = `-- name: AnalyticsSubscriptionsByStatus :many
|
const AnalyticsSubscriptionsByStatus = `-- name: AnalyticsSubscriptionsByStatus :many
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(us.status, 'unknown') AS status,
|
COALESCE(us.status, 'unknown') AS status,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,15 @@ type AnalyticsRevenueTimePoint struct {
|
||||||
Revenue float64 `json:"revenue"`
|
Revenue float64 `json:"revenue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnalyticsMonthlyRevenuePoint is one calendar month bucket (UTC month start) within a dashboard year query.
|
||||||
|
type AnalyticsMonthlyRevenuePoint struct {
|
||||||
|
Month int `json:"month"` // 1–12
|
||||||
|
MonthStart time.Time `json:"month_start"` // UTC date of month start (for sorting / tooltips)
|
||||||
|
Label string `json:"label"` // Short English month label, e.g. Jan
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Revenue float64 `json:"revenue"` // SUCCESS payments aggregate for that bucket
|
||||||
|
}
|
||||||
|
|
||||||
type AnalyticsUsersSection struct {
|
type AnalyticsUsersSection struct {
|
||||||
TotalUsers int64 `json:"total_users"`
|
TotalUsers int64 `json:"total_users"`
|
||||||
NewToday int64 `json:"new_today"`
|
NewToday int64 `json:"new_today"`
|
||||||
|
|
@ -67,6 +76,11 @@ type AnalyticsPaymentsSection struct {
|
||||||
ByMethod []AnalyticsLabelAmount `json:"by_method"`
|
ByMethod []AnalyticsLabelAmount `json:"by_method"`
|
||||||
|
|
||||||
RevenueLast30Days []AnalyticsRevenueTimePoint `json:"revenue_last_30_days"`
|
RevenueLast30Days []AnalyticsRevenueTimePoint `json:"revenue_last_30_days"`
|
||||||
|
|
||||||
|
// RevenueMonthly is populated only when the request includes year=..., with 12 months (possibly multiple currencies per month).
|
||||||
|
RevenueMonthly []AnalyticsMonthlyRevenuePoint `json:"revenue_monthly,omitempty"`
|
||||||
|
// MonthlyRevenueYear is set when RevenueMonthly is non-empty (the calendar year of those buckets).
|
||||||
|
MonthlyRevenueYear *int `json:"monthly_revenue_year,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnalyticsLMSContentCounts reflects the LMS hierarchy (Learn English): programs → courses → modules → lessons.
|
// AnalyticsLMSContentCounts reflects the LMS hierarchy (Learn English): programs → courses → modules → lessons.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Short month labels for analytics monthly charts (aligned with UTC calendar months).
|
||||||
|
var analyticsShortMonthLabels = []string{
|
||||||
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||||
|
}
|
||||||
|
|
||||||
func toTime(v interface{}) time.Time {
|
func toTime(v interface{}) time.Time {
|
||||||
if t, ok := v.(time.Time); ok {
|
if t, ok := v.(time.Time); ok {
|
||||||
return t
|
return t
|
||||||
|
|
@ -17,7 +22,7 @@ func toTime(v interface{}) time.Time {
|
||||||
|
|
||||||
// GetAnalyticsDashboard godoc
|
// GetAnalyticsDashboard godoc
|
||||||
// @Summary Analytics dashboard
|
// @Summary Analytics dashboard
|
||||||
// @Description Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range. The courses section includes LMS (programs→courses→modules→lessons, lms_practices) and exam_prep (catalog_courses→units→unit_modules→lessons, lesson_practices) inventory counts.
|
// @Description Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range. When year is set, payments.revenue_monthly returns Jan–Dec SUCCESS revenue totals (UTC) per currency for that calendar year — use for yearly revenue charts. Daily series remains in revenue_last_30_days (see date_filter.series_*). Courses section counts LMS + exam_prep inventory.
|
||||||
// @Tags analytics
|
// @Tags analytics
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param year query int false "Calendar year (e.g. 2025)"
|
// @Param year query int false "Calendar year (e.g. 2025)"
|
||||||
|
|
@ -103,6 +108,17 @@ func (h *Handler) GetAnalyticsDashboard(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch revenue time series")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch revenue time series")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var revenueMonthlyRows []dbgen.AnalyticsRevenueMonthlyByYearRow
|
||||||
|
var monthlyRevenueYear *int
|
||||||
|
if filter.Year != nil {
|
||||||
|
rowsMonthly, err := h.analyticsDB.AnalyticsRevenueMonthlyByYear(ctx, int32(*filter.Year))
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch monthly revenue series")
|
||||||
|
}
|
||||||
|
revenueMonthlyRows = rowsMonthly
|
||||||
|
monthlyRevenueYear = filter.Year
|
||||||
|
}
|
||||||
|
|
||||||
courseCounts, err := h.analyticsDB.AnalyticsCourseCounts(ctx)
|
courseCounts, err := h.analyticsDB.AnalyticsCourseCounts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch course analytics")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch course analytics")
|
||||||
|
|
@ -165,7 +181,7 @@ func (h *Handler) GetAnalyticsDashboard(c *fiber.Ctx) error {
|
||||||
DateFilter: filter,
|
DateFilter: filter,
|
||||||
Users: mapUsersSection(usersSummary, usersByRole, usersByStatus, usersByAge, usersByKnowledge, usersByRegion, userRegs),
|
Users: mapUsersSection(usersSummary, usersByRole, usersByStatus, usersByAge, usersByKnowledge, usersByRegion, userRegs),
|
||||||
Subscriptions: mapSubscriptionsSection(subsSummary, subsByStatus, revenueByPlan, newSubs30),
|
Subscriptions: mapSubscriptionsSection(subsSummary, subsByStatus, revenueByPlan, newSubs30),
|
||||||
Payments: mapPaymentsSection(paymentsSummary, paymentsByStatus, paymentsByMethod, revenue30),
|
Payments: mapPaymentsSection(paymentsSummary, paymentsByStatus, paymentsByMethod, revenue30, revenueMonthlyRows, monthlyRevenueYear),
|
||||||
Courses: mapCoursesSection(courseCounts),
|
Courses: mapCoursesSection(courseCounts),
|
||||||
Content: mapContentSection(questionsCounts, questionsByType, questionSetsByType),
|
Content: mapContentSection(questionsCounts, questionsByType, questionSetsByType),
|
||||||
Notifications: mapNotificationsSection(notifSummary, notifByChannel, notifByType),
|
Notifications: mapNotificationsSection(notifSummary, notifByChannel, notifByType),
|
||||||
|
|
@ -286,6 +302,8 @@ func mapPaymentsSection(
|
||||||
byStatus []dbgen.AnalyticsPaymentsByStatusRow,
|
byStatus []dbgen.AnalyticsPaymentsByStatusRow,
|
||||||
byMethod []dbgen.AnalyticsPaymentsByMethodRow,
|
byMethod []dbgen.AnalyticsPaymentsByMethodRow,
|
||||||
revenue []dbgen.AnalyticsRevenueLast30DaysRow,
|
revenue []dbgen.AnalyticsRevenueLast30DaysRow,
|
||||||
|
revenueMonthly []dbgen.AnalyticsRevenueMonthlyByYearRow,
|
||||||
|
monthlyYear *int,
|
||||||
) domain.AnalyticsPaymentsSection {
|
) domain.AnalyticsPaymentsSection {
|
||||||
statuses := make([]domain.AnalyticsLabelAmount, len(byStatus))
|
statuses := make([]domain.AnalyticsLabelAmount, len(byStatus))
|
||||||
for i, r := range byStatus {
|
for i, r := range byStatus {
|
||||||
|
|
@ -299,6 +317,27 @@ func mapPaymentsSection(
|
||||||
for i, r := range revenue {
|
for i, r := range revenue {
|
||||||
timePoints[i] = domain.AnalyticsRevenueTimePoint{Date: toTime(r.Date), Revenue: r.TotalRevenue}
|
timePoints[i] = domain.AnalyticsRevenueTimePoint{Date: toTime(r.Date), Revenue: r.TotalRevenue}
|
||||||
}
|
}
|
||||||
|
monthlyPoints := make([]domain.AnalyticsMonthlyRevenuePoint, 0, len(revenueMonthly))
|
||||||
|
for _, r := range revenueMonthly {
|
||||||
|
m := int(r.Month)
|
||||||
|
label := ""
|
||||||
|
if m >= 1 && m <= 12 {
|
||||||
|
label = analyticsShortMonthLabels[m-1]
|
||||||
|
}
|
||||||
|
ms := time.Time{}
|
||||||
|
if r.MonthStart.Valid {
|
||||||
|
t := r.MonthStart.Time.UTC()
|
||||||
|
ms = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
monthlyPoints = append(monthlyPoints, domain.AnalyticsMonthlyRevenuePoint{
|
||||||
|
Month: m,
|
||||||
|
MonthStart: ms,
|
||||||
|
Label: label,
|
||||||
|
Currency: r.Currency,
|
||||||
|
Revenue: r.TotalRevenue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return domain.AnalyticsPaymentsSection{
|
return domain.AnalyticsPaymentsSection{
|
||||||
TotalRevenue: summary.TotalRevenue,
|
TotalRevenue: summary.TotalRevenue,
|
||||||
AvgTransactionValue: summary.AvgValue,
|
AvgTransactionValue: summary.AvgValue,
|
||||||
|
|
@ -307,6 +346,8 @@ func mapPaymentsSection(
|
||||||
ByStatus: statuses,
|
ByStatus: statuses,
|
||||||
ByMethod: methods,
|
ByMethod: methods,
|
||||||
RevenueLast30Days: timePoints,
|
RevenueLast30Days: timePoints,
|
||||||
|
RevenueMonthly: monthlyPoints,
|
||||||
|
MonthlyRevenueYear: monthlyYear,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user