feat: add dashboard summary endpoint and related models, queries, and services

This commit is contained in:
Tran Anh Tuan
2026-05-13 17:53:32 +07:00
parent b815111b8f
commit 383bed757d
12 changed files with 928 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
package mapper
import (
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
)
func ToDomainTotalComponentStats(r db.GetTotalComponentStatsRow) models.TotalComponentStats {
return models.TotalComponentStats{
TotalTypes: r.TotalTypes,
TotalQuantity: r.TotalQuantity,
}
}
func ToDomainAbnormalAlert(r db.GetAbnormalItemCountsRow) models.AbnormalAlert {
return models.AbnormalAlert{
Status: string(r.Status),
Count: r.Count,
}
}
func ToDomainTodayInvoiceCount(r db.GetTodayInvoiceCountsRow) models.TodayInvoiceCount {
return models.TodayInvoiceCount{
Type: string(r.Type),
Count: r.Count,
}
}
func ToDomainContainerStats(r db.GetContainerStatsRow) models.ContainerStats {
return models.ContainerStats{
TotalContainers: r.TotalContainers,
EmptyContainers: int64(r.EmptyContainers),
}
}

View File

@@ -0,0 +1,30 @@
package models
type TotalComponentStats struct {
TotalTypes int64 `json:"totalTypes"`
TotalQuantity int64 `json:"totalQuantity"`
}
type AbnormalAlert struct {
Status string `json:"status"`
Count int64 `json:"count"`
}
type TodayInvoiceCount struct {
Type string `json:"type"`
Count int64 `json:"count"`
}
type ContainerStats struct {
TotalContainers int64 `json:"totalContainers"`
EmptyContainers int64 `json:"emptyContainers"`
}
type DashboardSummary struct {
TotalComponents TotalComponentStats `json:"totalComponents"`
PendingInvoices int64 `json:"pendingInvoices"`
LowStockComponents int64 `json:"lowStockComponents"`
AbnormalAlerts []AbnormalAlert `json:"abnormalAlerts"`
TodayInvoices []TodayInvoiceCount `json:"todayInvoices"`
EmptyContainers ContainerStats `json:"emptyContainers"`
}

View File

@@ -0,0 +1,61 @@
package repositories
import (
"context"
"wm-backend/internal/mapper"
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/jackc/pgx/v5/pgtype"
)
func GetDashboardSummary(ctx context.Context, queries *db.Queries, warehouseID pgtype.Int8) (models.DashboardSummary, error) {
totalStats, err := queries.GetTotalComponentStats(ctx, warehouseID)
if err != nil {
return models.DashboardSummary{}, err
}
pendingInvoices, err := queries.CountPendingInvoices(ctx)
if err != nil {
return models.DashboardSummary{}, err
}
lowStockCount, err := queries.CountLowStockComponents(ctx)
if err != nil {
return models.DashboardSummary{}, err
}
abnormalRows, err := queries.GetAbnormalItemCounts(ctx, warehouseID)
if err != nil {
return models.DashboardSummary{}, err
}
todayInvoiceRows, err := queries.GetTodayInvoiceCounts(ctx)
if err != nil {
return models.DashboardSummary{}, err
}
containerStats, err := queries.GetContainerStats(ctx, warehouseID)
if err != nil {
return models.DashboardSummary{}, err
}
abnormalAlerts := make([]models.AbnormalAlert, 0, len(abnormalRows))
for _, r := range abnormalRows {
abnormalAlerts = append(abnormalAlerts, mapper.ToDomainAbnormalAlert(r))
}
todayInvoices := make([]models.TodayInvoiceCount, 0, len(todayInvoiceRows))
for _, r := range todayInvoiceRows {
todayInvoices = append(todayInvoices, mapper.ToDomainTodayInvoiceCount(r))
}
return models.DashboardSummary{
TotalComponents: mapper.ToDomainTotalComponentStats(totalStats),
PendingInvoices: pendingInvoices,
LowStockComponents: lowStockCount,
AbnormalAlerts: abnormalAlerts,
TodayInvoices: todayInvoices,
EmptyContainers: mapper.ToDomainContainerStats(containerStats),
}, nil
}

View File

@@ -152,6 +152,11 @@ func NewRouter() *gin.Engine {
alternativeComponent.PUT("/:id", utils.AsyncHandler(services.AlternativeComponentUpdate))
alternativeComponent.DELETE("/:id", utils.AsyncHandler(services.AlternativeComponentDelete))
}
dashboard := protected.Group(constants.API_GROUP_DASHBOARD)
{
dashboard.GET("/summary", utils.AsyncHandler(services.DashboardSummary))
}
}
}

View File

@@ -0,0 +1,43 @@
package services
import (
"net/http"
"strconv"
"wm-backend/global"
"wm-backend/internal/repositories"
"wm-backend/response"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgtype"
"github.com/rs/zerolog/log"
)
// @Summary Get dashboard summary
// @Description Retrieve dashboard summary with key statistics
// @Tags dashboard
// @Accept json
// @Produce json
// @Param warehouse_id query int false "Filter by warehouse ID"
// @Success 200 {object} response.SuccessResponse{data=models.DashboardSummary}
// @Failure 500 {object} response.ErrorResponse
// @Router /v1/dashboard/summary [get]
func DashboardSummary(c *gin.Context) error {
var warehouseID pgtype.Int8
if raw := c.Query("warehouse_id"); raw != "" {
id, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
response.BadRequestError(c, http.StatusBadRequest, "Invalid warehouse_id")
return nil
}
warehouseID = pgtype.Int8{Int64: id, Valid: true}
}
summary, err := repositories.GetDashboardSummary(c.Request.Context(), global.Queries, warehouseID)
if err != nil {
log.Err(err).Msg("Error when Get Dashboard Summary")
response.InternalServerError(c, http.StatusInternalServerError, "Failed to get dashboard summary")
return nil
}
response.Ok(c, "Success", summary)
return nil
}