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

@@ -24,6 +24,7 @@ const (
API_GROUP_INVOICE_CONFIG_ITEM = "/invoice-config-items" API_GROUP_INVOICE_CONFIG_ITEM = "/invoice-config-items"
API_GROUP_INVOICE = "/invoices" API_GROUP_INVOICE = "/invoices"
API_GROUP_ALTERNATIVE_COMPONENT = "/alternative-components" API_GROUP_ALTERNATIVE_COMPONENT = "/alternative-components"
API_GROUP_DASHBOARD = "/dashboard"
) )
const ( const (

44
db/queries/dashboard.sql Normal file
View File

@@ -0,0 +1,44 @@
-- name: GetTotalComponentStats :one
SELECT COUNT(DISTINCT ci.component_id) AS total_types, COALESCE(SUM(ci.quantity), 0)::bigint AS total_quantity
FROM component_items ci
JOIN containers con ON ci.container_id = con.id
JOIN shelves s ON con.shelf_id = s.id
JOIN cabinets cab ON s.cabinet_id = cab.id
JOIN rooms r ON cab.room_id = r.id
WHERE sqlc.narg('warehouse_id')::bigint IS NULL OR r.warehouse_id = sqlc.narg('warehouse_id')::bigint;
-- name: CountPendingInvoices :one
SELECT COUNT(*) FROM invoices
WHERE status IN ('draft', 'pending');
-- name: CountLowStockComponents :one
SELECT COUNT(*) FROM components
WHERE total_quantity <= min_quantity;
-- name: GetAbnormalItemCounts :many
SELECT ci.status, COUNT(*) AS count
FROM component_items ci
JOIN containers con ON ci.container_id = con.id
JOIN shelves s ON con.shelf_id = s.id
JOIN cabinets cab ON s.cabinet_id = cab.id
JOIN rooms r ON cab.room_id = r.id
WHERE ci.status != 'normal'
AND (sqlc.narg('warehouse_id')::bigint IS NULL OR r.warehouse_id = sqlc.narg('warehouse_id')::bigint)
GROUP BY ci.status;
-- name: GetTodayInvoiceCounts :many
SELECT type, COUNT(*) AS count
FROM invoices
WHERE created_at::date = CURRENT_DATE
GROUP BY type;
-- name: GetContainerStats :one
SELECT
COUNT(*) AS total_containers,
COUNT(*) - COUNT(DISTINCT ci.container_id) AS empty_containers
FROM containers c
JOIN shelves s ON c.shelf_id = s.id
JOIN cabinets cab ON s.cabinet_id = cab.id
JOIN rooms r ON cab.room_id = r.id
LEFT JOIN component_items ci ON c.id = ci.container_id
WHERE sqlc.narg('warehouse_id')::bigint IS NULL OR r.warehouse_id = sqlc.narg('warehouse_id')::bigint;

View File

@@ -1327,6 +1327,49 @@ const docTemplate = `{
} }
} }
}, },
"/profile": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Returns user info with roles and permissions (requires auth)",
"produces": [
"application/json"
],
"tags": [
"auth"
],
"summary": "Get current user profile",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/responses.BodyProfileResponse"
}
}
}
]
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/alternative-components": { "/v1/alternative-components": {
"get": { "get": {
"description": "Retrieve a list of all alternative components", "description": "Retrieve a list of all alternative components",
@@ -2146,6 +2189,47 @@ const docTemplate = `{
} }
} }
}, },
"/v1/dashboard/summary": {
"get": {
"description": "Retrieve dashboard summary with key statistics",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"dashboard"
],
"summary": "Get dashboard summary",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.DashboardSummary"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/invoice-config-items": { "/v1/invoice-config-items": {
"get": { "get": {
"description": "Retrieve a list of all invoice config items", "description": "Retrieve a list of all invoice config items",
@@ -3780,6 +3864,17 @@ const docTemplate = `{
} }
}, },
"definitions": { "definitions": {
"models.AbnormalAlert": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"status": {
"type": "string"
}
}
},
"models.AlternativeComponent": { "models.AlternativeComponent": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -3992,6 +4087,46 @@ const docTemplate = `{
} }
} }
}, },
"models.ContainerStats": {
"type": "object",
"properties": {
"emptyContainers": {
"type": "integer"
},
"totalContainers": {
"type": "integer"
}
}
},
"models.DashboardSummary": {
"type": "object",
"properties": {
"abnormalAlerts": {
"type": "array",
"items": {
"$ref": "#/definitions/models.AbnormalAlert"
}
},
"emptyContainers": {
"$ref": "#/definitions/models.ContainerStats"
},
"lowStockComponents": {
"type": "integer"
},
"pendingInvoices": {
"type": "integer"
},
"todayInvoices": {
"type": "array",
"items": {
"$ref": "#/definitions/models.TodayInvoiceCount"
}
},
"totalComponents": {
"$ref": "#/definitions/models.TotalComponentStats"
}
}
},
"models.FindComponentItemResult": { "models.FindComponentItemResult": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -4187,6 +4322,28 @@ const docTemplate = `{
} }
} }
}, },
"models.TodayInvoiceCount": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"type": {
"type": "string"
}
}
},
"models.TotalComponentStats": {
"type": "object",
"properties": {
"totalQuantity": {
"type": "integer"
},
"totalTypes": {
"type": "integer"
}
}
},
"models.Warehouse": { "models.Warehouse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -4841,6 +4998,26 @@ const docTemplate = `{
} }
} }
}, },
"responses.BodyProfileResponse": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/responses.UserInfoResponse"
},
"permissions": {
"type": "array",
"items": {
"type": "string"
}
},
"roles": {
"type": "array",
"items": {
"$ref": "#/definitions/responses.RoleItem"
}
}
}
},
"responses.BodyRegisterResponse": { "responses.BodyRegisterResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -4956,6 +5133,20 @@ const docTemplate = `{
} }
} }
}, },
"responses.RoleItem": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"responses.UpdateAlternativeComponentResponse": { "responses.UpdateAlternativeComponentResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -5259,6 +5450,26 @@ const docTemplate = `{
"type": "string" "type": "string"
} }
} }
},
"responses.UserInfoResponse": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"fullName": {
"type": "string"
},
"id": {
"type": "string"
},
"isActive": {
"type": "boolean"
},
"username": {
"type": "string"
}
}
} }
} }
}` }`

View File

@@ -1321,6 +1321,49 @@
} }
} }
}, },
"/profile": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Returns user info with roles and permissions (requires auth)",
"produces": [
"application/json"
],
"tags": [
"auth"
],
"summary": "Get current user profile",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/responses.BodyProfileResponse"
}
}
}
]
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/alternative-components": { "/v1/alternative-components": {
"get": { "get": {
"description": "Retrieve a list of all alternative components", "description": "Retrieve a list of all alternative components",
@@ -2140,6 +2183,47 @@
} }
} }
}, },
"/v1/dashboard/summary": {
"get": {
"description": "Retrieve dashboard summary with key statistics",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"dashboard"
],
"summary": "Get dashboard summary",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.DashboardSummary"
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/invoice-config-items": { "/v1/invoice-config-items": {
"get": { "get": {
"description": "Retrieve a list of all invoice config items", "description": "Retrieve a list of all invoice config items",
@@ -3774,6 +3858,17 @@
} }
}, },
"definitions": { "definitions": {
"models.AbnormalAlert": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"status": {
"type": "string"
}
}
},
"models.AlternativeComponent": { "models.AlternativeComponent": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -3986,6 +4081,46 @@
} }
} }
}, },
"models.ContainerStats": {
"type": "object",
"properties": {
"emptyContainers": {
"type": "integer"
},
"totalContainers": {
"type": "integer"
}
}
},
"models.DashboardSummary": {
"type": "object",
"properties": {
"abnormalAlerts": {
"type": "array",
"items": {
"$ref": "#/definitions/models.AbnormalAlert"
}
},
"emptyContainers": {
"$ref": "#/definitions/models.ContainerStats"
},
"lowStockComponents": {
"type": "integer"
},
"pendingInvoices": {
"type": "integer"
},
"todayInvoices": {
"type": "array",
"items": {
"$ref": "#/definitions/models.TodayInvoiceCount"
}
},
"totalComponents": {
"$ref": "#/definitions/models.TotalComponentStats"
}
}
},
"models.FindComponentItemResult": { "models.FindComponentItemResult": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -4181,6 +4316,28 @@
} }
} }
}, },
"models.TodayInvoiceCount": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"type": {
"type": "string"
}
}
},
"models.TotalComponentStats": {
"type": "object",
"properties": {
"totalQuantity": {
"type": "integer"
},
"totalTypes": {
"type": "integer"
}
}
},
"models.Warehouse": { "models.Warehouse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -4835,6 +4992,26 @@
} }
} }
}, },
"responses.BodyProfileResponse": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/responses.UserInfoResponse"
},
"permissions": {
"type": "array",
"items": {
"type": "string"
}
},
"roles": {
"type": "array",
"items": {
"$ref": "#/definitions/responses.RoleItem"
}
}
}
},
"responses.BodyRegisterResponse": { "responses.BodyRegisterResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -4950,6 +5127,20 @@
} }
} }
}, },
"responses.RoleItem": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"responses.UpdateAlternativeComponentResponse": { "responses.UpdateAlternativeComponentResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -5253,6 +5444,26 @@
"type": "string" "type": "string"
} }
} }
},
"responses.UserInfoResponse": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"fullName": {
"type": "string"
},
"id": {
"type": "string"
},
"isActive": {
"type": "boolean"
},
"username": {
"type": "string"
}
}
} }
} }
} }

View File

@@ -1,5 +1,12 @@
basePath: /api/v1 basePath: /api/v1
definitions: definitions:
models.AbnormalAlert:
properties:
count:
type: integer
status:
type: string
type: object
models.AlternativeComponent: models.AlternativeComponent:
properties: properties:
alternativeComponentId: alternativeComponentId:
@@ -139,6 +146,32 @@ definitions:
updatedAt: updatedAt:
type: string type: string
type: object type: object
models.ContainerStats:
properties:
emptyContainers:
type: integer
totalContainers:
type: integer
type: object
models.DashboardSummary:
properties:
abnormalAlerts:
items:
$ref: '#/definitions/models.AbnormalAlert'
type: array
emptyContainers:
$ref: '#/definitions/models.ContainerStats'
lowStockComponents:
type: integer
pendingInvoices:
type: integer
todayInvoices:
items:
$ref: '#/definitions/models.TodayInvoiceCount'
type: array
totalComponents:
$ref: '#/definitions/models.TotalComponentStats'
type: object
models.FindComponentItemResult: models.FindComponentItemResult:
properties: properties:
cabinetName: cabinetName:
@@ -267,6 +300,20 @@ definitions:
updatedAt: updatedAt:
type: string type: string
type: object type: object
models.TodayInvoiceCount:
properties:
count:
type: integer
type:
type: string
type: object
models.TotalComponentStats:
properties:
totalQuantity:
type: integer
totalTypes:
type: integer
type: object
models.Warehouse: models.Warehouse:
properties: properties:
address: address:
@@ -703,6 +750,19 @@ definitions:
status: status:
type: integer type: integer
type: object type: object
responses.BodyProfileResponse:
properties:
info:
$ref: '#/definitions/responses.UserInfoResponse'
permissions:
items:
type: string
type: array
roles:
items:
$ref: '#/definitions/responses.RoleItem'
type: array
type: object
responses.BodyRegisterResponse: responses.BodyRegisterResponse:
properties: properties:
id: id:
@@ -775,6 +835,15 @@ definitions:
id: id:
type: integer type: integer
type: object type: object
responses.RoleItem:
properties:
description:
type: string
id:
type: string
name:
type: string
type: object
responses.UpdateAlternativeComponentResponse: responses.UpdateAlternativeComponentResponse:
properties: properties:
alternativeComponentId: alternativeComponentId:
@@ -973,6 +1042,19 @@ definitions:
name: name:
type: string type: string
type: object type: object
responses.UserInfoResponse:
properties:
email:
type: string
fullName:
type: string
id:
type: string
isActive:
type: boolean
username:
type: string
type: object
host: localhost:3000 host: localhost:3000
info: info:
contact: {} contact: {}
@@ -1800,6 +1882,30 @@ paths:
summary: Health check summary: Health check
tags: tags:
- health - health
/profile:
get:
description: Returns user info with roles and permissions (requires auth)
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.SuccessResponse'
- properties:
data:
$ref: '#/definitions/responses.BodyProfileResponse'
type: object
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: Get current user profile
tags:
- auth
/v1/alternative-components: /v1/alternative-components:
get: get:
consumes: consumes:
@@ -2310,6 +2416,30 @@ paths:
summary: Update container summary: Update container
tags: tags:
- container - container
/v1/dashboard/summary:
get:
consumes:
- application/json
description: Retrieve dashboard summary with key statistics
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.SuccessResponse'
- properties:
data:
$ref: '#/definitions/models.DashboardSummary'
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: Get dashboard summary
tags:
- dashboard
/v1/invoice-config-items: /v1/invoice-config-items:
get: get:
consumes: consumes:

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.PUT("/:id", utils.AsyncHandler(services.AlternativeComponentUpdate))
alternativeComponent.DELETE("/:id", utils.AsyncHandler(services.AlternativeComponentDelete)) 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
}

151
sqlc_gen/dashboard.sql.go Normal file
View File

@@ -0,0 +1,151 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: dashboard.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countLowStockComponents = `-- name: CountLowStockComponents :one
SELECT COUNT(*) FROM components
WHERE total_quantity <= min_quantity
`
func (q *Queries) CountLowStockComponents(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, countLowStockComponents)
var count int64
err := row.Scan(&count)
return count, err
}
const countPendingInvoices = `-- name: CountPendingInvoices :one
SELECT COUNT(*) FROM invoices
WHERE status IN ('draft', 'pending')
`
func (q *Queries) CountPendingInvoices(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, countPendingInvoices)
var count int64
err := row.Scan(&count)
return count, err
}
const getAbnormalItemCounts = `-- name: GetAbnormalItemCounts :many
SELECT ci.status, COUNT(*) AS count
FROM component_items ci
JOIN containers con ON ci.container_id = con.id
JOIN shelves s ON con.shelf_id = s.id
JOIN cabinets cab ON s.cabinet_id = cab.id
JOIN rooms r ON cab.room_id = r.id
WHERE ci.status != 'normal'
AND ($1::bigint IS NULL OR r.warehouse_id = $1::bigint)
GROUP BY ci.status
`
type GetAbnormalItemCountsRow struct {
Status ComponentItemStatusEnum `db:"status" json:"status"`
Count int64 `db:"count" json:"count"`
}
func (q *Queries) GetAbnormalItemCounts(ctx context.Context, warehouseID pgtype.Int8) ([]GetAbnormalItemCountsRow, error) {
rows, err := q.db.Query(ctx, getAbnormalItemCounts, warehouseID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAbnormalItemCountsRow
for rows.Next() {
var i GetAbnormalItemCountsRow
if err := rows.Scan(&i.Status, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getContainerStats = `-- name: GetContainerStats :one
SELECT
COUNT(*) AS total_containers,
COUNT(*) - COUNT(DISTINCT ci.container_id) AS empty_containers
FROM containers c
JOIN shelves s ON c.shelf_id = s.id
JOIN cabinets cab ON s.cabinet_id = cab.id
JOIN rooms r ON cab.room_id = r.id
LEFT JOIN component_items ci ON c.id = ci.container_id
WHERE $1::bigint IS NULL OR r.warehouse_id = $1::bigint
`
type GetContainerStatsRow struct {
TotalContainers int64 `db:"total_containers" json:"totalContainers"`
EmptyContainers int32 `db:"empty_containers" json:"emptyContainers"`
}
func (q *Queries) GetContainerStats(ctx context.Context, warehouseID pgtype.Int8) (GetContainerStatsRow, error) {
row := q.db.QueryRow(ctx, getContainerStats, warehouseID)
var i GetContainerStatsRow
err := row.Scan(&i.TotalContainers, &i.EmptyContainers)
return i, err
}
const getTodayInvoiceCounts = `-- name: GetTodayInvoiceCounts :many
SELECT type, COUNT(*) AS count
FROM invoices
WHERE created_at::date = CURRENT_DATE
GROUP BY type
`
type GetTodayInvoiceCountsRow struct {
Type InvoiceTypeEnum `db:"type" json:"type"`
Count int64 `db:"count" json:"count"`
}
func (q *Queries) GetTodayInvoiceCounts(ctx context.Context) ([]GetTodayInvoiceCountsRow, error) {
rows, err := q.db.Query(ctx, getTodayInvoiceCounts)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetTodayInvoiceCountsRow
for rows.Next() {
var i GetTodayInvoiceCountsRow
if err := rows.Scan(&i.Type, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getTotalComponentStats = `-- name: GetTotalComponentStats :one
SELECT COUNT(DISTINCT ci.component_id) AS total_types, COALESCE(SUM(ci.quantity), 0)::bigint AS total_quantity
FROM component_items ci
JOIN containers con ON ci.container_id = con.id
JOIN shelves s ON con.shelf_id = s.id
JOIN cabinets cab ON s.cabinet_id = cab.id
JOIN rooms r ON cab.room_id = r.id
WHERE $1::bigint IS NULL OR r.warehouse_id = $1::bigint
`
type GetTotalComponentStatsRow struct {
TotalTypes int64 `db:"total_types" json:"totalTypes"`
TotalQuantity int64 `db:"total_quantity" json:"totalQuantity"`
}
func (q *Queries) GetTotalComponentStats(ctx context.Context, warehouseID pgtype.Int8) (GetTotalComponentStatsRow, error) {
row := q.db.QueryRow(ctx, getTotalComponentStats, warehouseID)
var i GetTotalComponentStatsRow
err := row.Scan(&i.TotalTypes, &i.TotalQuantity)
return i, err
}

View File

@@ -8,10 +8,13 @@ import (
"context" "context"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
) )
type Querier interface { type Querier interface {
AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error) AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error)
CountLowStockComponents(ctx context.Context) (int64, error)
CountPendingInvoices(ctx context.Context) (int64, error)
CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error) CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error)
CreateAlternativeComponent(ctx context.Context, arg CreateAlternativeComponentParams) (AlternativeComponent, error) CreateAlternativeComponent(ctx context.Context, arg CreateAlternativeComponentParams) (AlternativeComponent, error)
CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error) CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error)
@@ -46,6 +49,7 @@ type Querier interface {
DeleteShelve(ctx context.Context, id int64) (int64, error) DeleteShelve(ctx context.Context, id int64) (int64, error)
DeleteWarehouse(ctx context.Context, id int64) (int64, error) DeleteWarehouse(ctx context.Context, id int64) (int64, error)
FindComponentItem(ctx context.Context, componentid int64) ([]FindComponentItemRow, error) FindComponentItem(ctx context.Context, componentid int64) ([]FindComponentItemRow, error)
GetAbnormalItemCounts(ctx context.Context, warehouseID pgtype.Int8) ([]GetAbnormalItemCountsRow, error)
GetAlternativeComponentByID(ctx context.Context, id int64) (AlternativeComponent, error) GetAlternativeComponentByID(ctx context.Context, id int64) (AlternativeComponent, error)
GetCabinetByID(ctx context.Context, id int64) (Cabinet, error) GetCabinetByID(ctx context.Context, id int64) (Cabinet, error)
GetComponentByID(ctx context.Context, id int64) (Component, error) GetComponentByID(ctx context.Context, id int64) (Component, error)
@@ -54,6 +58,7 @@ type Querier interface {
GetComponentItemByID(ctx context.Context, id int64) (ComponentItem, error) GetComponentItemByID(ctx context.Context, id int64) (ComponentItem, error)
GetComponentTypeByID(ctx context.Context, id int64) (ComponentType, error) GetComponentTypeByID(ctx context.Context, id int64) (ComponentType, error)
GetContainerByID(ctx context.Context, id int64) (Container, error) GetContainerByID(ctx context.Context, id int64) (Container, error)
GetContainerStats(ctx context.Context, warehouseID pgtype.Int8) (GetContainerStatsRow, error)
GetInvoiceByID(ctx context.Context, id int64) (Invoice, error) GetInvoiceByID(ctx context.Context, id int64) (Invoice, error)
GetInvoiceConfigByID(ctx context.Context, id int64) (InvoiceConfig, error) GetInvoiceConfigByID(ctx context.Context, id int64) (InvoiceConfig, error)
GetInvoiceConfigItemByID(ctx context.Context, id int64) (InvoiceConfigItem, error) GetInvoiceConfigItemByID(ctx context.Context, id int64) (InvoiceConfigItem, error)
@@ -62,6 +67,8 @@ type Querier interface {
GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error) GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error)
GetRoomByID(ctx context.Context, id int64) (Room, error) GetRoomByID(ctx context.Context, id int64) (Room, error)
GetShelveByID(ctx context.Context, id int64) (Shelf, error) GetShelveByID(ctx context.Context, id int64) (Shelf, error)
GetTodayInvoiceCounts(ctx context.Context) ([]GetTodayInvoiceCountsRow, error)
GetTotalComponentStats(ctx context.Context, warehouseID pgtype.Int8) (GetTotalComponentStatsRow, error)
GetUserByEmail(ctx context.Context, email string) (User, error) GetUserByEmail(ctx context.Context, email string) (User, error)
GetUserByID(ctx context.Context, id uuid.UUID) (User, error) GetUserByID(ctx context.Context, id uuid.UUID) (User, error)
GetUserByUsername(ctx context.Context, username string) (User, error) GetUserByUsername(ctx context.Context, username string) (User, error)