176 lines
6.8 KiB
Go
176 lines
6.8 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"time"
|
|
|
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
|
|
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
|
)
|
|
|
|
// MetricSlice 是仪表盘图表使用的名称/数值序列。
|
|
type MetricSlice struct {
|
|
Name string `json:"name"`
|
|
Value int64 `json:"value"`
|
|
}
|
|
|
|
// DailyMetric 是按自然日聚合的运营指标。
|
|
type DailyMetric struct {
|
|
Date string `json:"date"`
|
|
OrderCount int64 `json:"order_count"`
|
|
OrderAmount int64 `json:"order_amount"`
|
|
}
|
|
|
|
// DashboardStatistics 是平台运营总览。该接口专用结构只属于 dashboard 逻辑层。
|
|
type DashboardStatistics struct {
|
|
GasBasicCount int64 `json:"gas_basic_count"`
|
|
DeliveryBasicCount int64 `json:"delivery_basic_count"`
|
|
StaffCount int64 `json:"staff_count"`
|
|
UserCount int64 `json:"user_count"`
|
|
ProductCount int64 `json:"product_count"`
|
|
ActiveContractCount int64 `json:"active_contract_count"`
|
|
TodayOrderCount int64 `json:"today_order_count"`
|
|
TodayOrderAmount int64 `json:"today_order_amount"`
|
|
PendingTicketCount int64 `json:"pending_ticket_count"`
|
|
PaidAmount int64 `json:"paid_amount"`
|
|
OrderStatuses []MetricSlice `json:"order_statuses"`
|
|
ProductStatuses []MetricSlice `json:"product_statuses"`
|
|
PaymentChannels []MetricSlice `json:"payment_channels"`
|
|
RecentOrders []DailyMetric `json:"recent_orders"`
|
|
}
|
|
|
|
type statusAggregate struct {
|
|
Status int
|
|
Count int64
|
|
}
|
|
|
|
type channelAggregate struct {
|
|
Name string
|
|
Value int64
|
|
}
|
|
|
|
type dailyAggregate struct {
|
|
Date time.Time
|
|
OrderCount int64
|
|
OrderAmount int64
|
|
}
|
|
|
|
var orderStatusNames = map[int]string{
|
|
common.StatusCreated: "已创建", common.StatusOrdered: "已下单",
|
|
common.StatusAssigned: "已分配", common.StatusFilling: "充装中",
|
|
common.StatusReady: "已就绪", common.StatusDelivering: "配送中",
|
|
common.StatusAwaitingConfirmation: "待确认", common.StatusCompleted: "已完成",
|
|
common.StatusCancelled: "已取消", common.StatusException: "异常",
|
|
}
|
|
|
|
var productStatusNames = map[int]string{
|
|
common.StatusPending: "待处理", common.StatusInStock: "在库",
|
|
common.StatusInTransit: "运输中", common.StatusInUse: "使用中",
|
|
common.StatusRepairing: "维修中", common.StatusScrapped: "已报废",
|
|
}
|
|
|
|
// GetDashboardOverview 汇总组织、人员、资产、订单、支付及客服维度的数据。
|
|
func GetDashboardOverview() (DashboardStatistics, error) {
|
|
result := DashboardStatistics{
|
|
OrderStatuses: make([]MetricSlice, 0),
|
|
ProductStatuses: make([]MetricSlice, 0),
|
|
PaymentChannels: make([]MetricSlice, 0),
|
|
RecentOrders: make([]DailyMetric, 0),
|
|
}
|
|
counts := []struct {
|
|
model any
|
|
where string
|
|
args []any
|
|
out *int64
|
|
}{
|
|
{&models.GasBasic{}, "status = ?", []any{common.StatusEnable}, &result.GasBasicCount},
|
|
{&models.DeliveryBasic{}, "status = ?", []any{common.StatusEnable}, &result.DeliveryBasicCount},
|
|
{&models.StaffAccount{}, "status = ? AND work_status = ?", []any{common.StatusEnable, "on_duty"}, &result.StaffCount},
|
|
{&models.UserAccount{}, "status = ?", []any{common.StatusEnable}, &result.UserCount},
|
|
{&models.ProductInfo{}, "status = ?", []any{common.StatusEnable}, &result.ProductCount},
|
|
{&models.GasorderContract{}, "status = ? AND contract_status = ?", []any{common.StatusEnable, common.StatusActive}, &result.ActiveContractCount},
|
|
{&models.GasorderBasic{}, "status = ? AND created_at >= CURRENT_DATE", []any{common.StatusEnable}, &result.TodayOrderCount},
|
|
{&models.CsTicket{}, "status = ? AND ticket_status = ?", []any{common.StatusEnable, common.StatusOpen}, &result.PendingTicketCount},
|
|
}
|
|
for _, item := range counts {
|
|
if err := impl.DBService.Model(item.model).Where(item.where, item.args...).Count(item.out).Error; err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
}
|
|
if err := impl.DBService.Model(&models.GasorderBasic{}).
|
|
Select("COALESCE(SUM(payable_amount), 0)").
|
|
Where("status = ? AND created_at >= CURRENT_DATE", common.StatusEnable).
|
|
Scan(&result.TodayOrderAmount).Error; err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
if err := impl.DBService.Model(&models.PaymentOrder{}).
|
|
Select("COALESCE(SUM(amount), 0)").
|
|
Where("status = ? AND payment_status = ?", common.StatusEnable, 23).
|
|
Scan(&result.PaidAmount).Error; err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
|
|
var orderStatuses []statusAggregate
|
|
if err := impl.DBService.Model(&models.GasorderBasic{}).
|
|
Select("order_status AS status, COUNT(*) AS count").
|
|
Where("status = ?", common.StatusEnable).Group("order_status").Scan(&orderStatuses).Error; err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
result.OrderStatuses = namedStatuses(orderStatuses, orderStatusNames)
|
|
|
|
var productStatuses []statusAggregate
|
|
if err := impl.DBService.Model(&models.ProductInfo{}).
|
|
Select("product_status AS status, COUNT(*) AS count").
|
|
Where("status = ?", common.StatusEnable).Group("product_status").Scan(&productStatuses).Error; err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
result.ProductStatuses = namedStatuses(productStatuses, productStatusNames)
|
|
|
|
if err := loadPaymentChannels(&result.PaymentChannels); err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
|
|
var daily []dailyAggregate
|
|
if err := impl.DBService.Model(&models.GasorderBasic{}).
|
|
Select("DATE_TRUNC('day', created_at) AS date, COUNT(*) AS order_count, COALESCE(SUM(payable_amount), 0) AS order_amount").
|
|
Where("status = ? AND created_at >= CURRENT_DATE - INTERVAL '6 days'", common.StatusEnable).
|
|
Group("DATE_TRUNC('day', created_at)").Order("date").Scan(&daily).Error; err != nil {
|
|
return DashboardStatistics{}, err
|
|
}
|
|
result.RecentOrders = fillRecentDays(daily, time.Now())
|
|
return result, nil
|
|
}
|
|
|
|
func loadPaymentChannels(result *[]MetricSlice) error {
|
|
return impl.DBService.Model(&models.PaymentOrder{}).
|
|
Select("channel AS name, COALESCE(SUM(amount), 0) AS value").
|
|
Where("status = ? AND payment_status = ?", common.StatusEnable, 23).
|
|
Group("channel").Order("value DESC").Scan(result).Error
|
|
}
|
|
|
|
func namedStatuses(rows []statusAggregate, names map[int]string) []MetricSlice {
|
|
result := make([]MetricSlice, 0, len(rows))
|
|
for _, row := range rows {
|
|
name, ok := names[row.Status]
|
|
if !ok {
|
|
name = "其他"
|
|
}
|
|
result = append(result, MetricSlice{Name: name, Value: row.Count})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func fillRecentDays(rows []dailyAggregate, now time.Time) []DailyMetric {
|
|
values := make(map[string]dailyAggregate, len(rows))
|
|
for _, row := range rows {
|
|
values[row.Date.Format("2006-01-02")] = row
|
|
}
|
|
result := make([]DailyMetric, 0, 7)
|
|
for offset := -6; offset <= 0; offset++ {
|
|
date := now.AddDate(0, 0, offset).Format("2006-01-02")
|
|
row := values[date]
|
|
result = append(result, DailyMetric{Date: date, OrderCount: row.OrderCount, OrderAmount: row.OrderAmount})
|
|
}
|
|
return result
|
|
}
|