feat: add endpoints and logic for retrieving warehouse space usage and status distribution, including SQL queries, models, and service integration
This commit is contained in:
@@ -95,3 +95,26 @@ WHERE st.transaction_type = 'export'
|
|||||||
GROUP BY c.id, c.name, c.unit, ct.name
|
GROUP BY c.id, c.name, c.unit, ct.name
|
||||||
ORDER BY total_exported DESC
|
ORDER BY total_exported DESC
|
||||||
LIMIT sqlc.arg('limit_count')::int;
|
LIMIT sqlc.arg('limit_count')::int;
|
||||||
|
|
||||||
|
-- name: GetStatusDistribution :many
|
||||||
|
SELECT status, COUNT(*) AS count, COALESCE(SUM(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)
|
||||||
|
GROUP BY ci.status;
|
||||||
|
|
||||||
|
-- name: GetSpaceUsage :many
|
||||||
|
SELECT w.name AS warehouse, r.name AS room,
|
||||||
|
COUNT(DISTINCT c.id)::bigint AS total_containers,
|
||||||
|
COUNT(DISTINCT ci.container_id)::bigint AS used_containers
|
||||||
|
FROM warehouses w
|
||||||
|
JOIN rooms r ON r.warehouse_id = w.id
|
||||||
|
JOIN cabinets cb ON cb.room_id = r.id
|
||||||
|
JOIN shelves s ON s.cabinet_id = cb.id
|
||||||
|
JOIN containers c ON c.shelf_id = s.id
|
||||||
|
LEFT JOIN component_items ci ON ci.container_id = c.id
|
||||||
|
WHERE (sqlc.narg('warehouse_id')::bigint IS NULL OR w.id = sqlc.narg('warehouse_id')::bigint)
|
||||||
|
GROUP BY w.name, r.name;
|
||||||
|
|||||||
@@ -2247,6 +2247,122 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/dashboard/space-usage": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve warehouse space usage statistics showing total vs used containers per room",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Get space usage",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Filter by warehouse ID",
|
||||||
|
"name": "warehouse_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.SpaceUsageItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/dashboard/status-distribution": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve component items count grouped by status (normal, damaged, expired, etc.)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Get status distribution",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Filter by warehouse ID",
|
||||||
|
"name": "warehouse_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.StatusDistributionItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/dashboard/stock-alerts": {
|
"/v1/dashboard/stock-alerts": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Retrieve list of components that are low on stock (total_quantity \u003c= min_quantity)",
|
"description": "Retrieve list of components that are low on stock (total_quantity \u003c= min_quantity)",
|
||||||
@@ -4598,6 +4714,37 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.SpaceUsageItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"room": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totalContainers": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"usedContainers": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"warehouse": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.StatusDistributionItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totalQuantity": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"models.StockAlert": {
|
"models.StockAlert": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -2241,6 +2241,122 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/dashboard/space-usage": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve warehouse space usage statistics showing total vs used containers per room",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Get space usage",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Filter by warehouse ID",
|
||||||
|
"name": "warehouse_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.SpaceUsageItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/dashboard/status-distribution": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve component items count grouped by status (normal, damaged, expired, etc.)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"dashboard"
|
||||||
|
],
|
||||||
|
"summary": "Get status distribution",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Filter by warehouse ID",
|
||||||
|
"name": "warehouse_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.SuccessResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.StatusDistributionItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/dashboard/stock-alerts": {
|
"/v1/dashboard/stock-alerts": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Retrieve list of components that are low on stock (total_quantity \u003c= min_quantity)",
|
"description": "Retrieve list of components that are low on stock (total_quantity \u003c= min_quantity)",
|
||||||
@@ -4592,6 +4708,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.SpaceUsageItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"room": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totalContainers": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"usedContainers": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"warehouse": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.StatusDistributionItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totalQuantity": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"models.StockAlert": {
|
"models.StockAlert": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -321,6 +321,26 @@ definitions:
|
|||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
models.SpaceUsageItem:
|
||||||
|
properties:
|
||||||
|
room:
|
||||||
|
type: string
|
||||||
|
totalContainers:
|
||||||
|
type: integer
|
||||||
|
usedContainers:
|
||||||
|
type: integer
|
||||||
|
warehouse:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
models.StatusDistributionItem:
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
totalQuantity:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
models.StockAlert:
|
models.StockAlert:
|
||||||
properties:
|
properties:
|
||||||
componentTypeId:
|
componentTypeId:
|
||||||
@@ -2519,6 +2539,78 @@ paths:
|
|||||||
summary: Get anomaly items
|
summary: Get anomaly items
|
||||||
tags:
|
tags:
|
||||||
- dashboard
|
- dashboard
|
||||||
|
/v1/dashboard/space-usage:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve warehouse space usage statistics showing total vs used
|
||||||
|
containers per room
|
||||||
|
parameters:
|
||||||
|
- description: Filter by warehouse ID
|
||||||
|
in: query
|
||||||
|
name: warehouse_id
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.SpaceUsageItem'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Get space usage
|
||||||
|
tags:
|
||||||
|
- dashboard
|
||||||
|
/v1/dashboard/status-distribution:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve component items count grouped by status (normal, damaged,
|
||||||
|
expired, etc.)
|
||||||
|
parameters:
|
||||||
|
- description: Filter by warehouse ID
|
||||||
|
in: query
|
||||||
|
name: warehouse_id
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.SuccessResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.StatusDistributionItem'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.ErrorResponse'
|
||||||
|
summary: Get status distribution
|
||||||
|
tags:
|
||||||
|
- dashboard
|
||||||
/v1/dashboard/stock-alerts:
|
/v1/dashboard/stock-alerts:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -76,3 +76,20 @@ func ToDomainTopExportedComponent(r db.GetTopExportedComponentsRow) models.TopEx
|
|||||||
TotalExported: r.TotalExported,
|
TotalExported: r.TotalExported,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToDomainStatusDistribution(r db.GetStatusDistributionRow) models.StatusDistributionItem {
|
||||||
|
return models.StatusDistributionItem{
|
||||||
|
Status: string(r.Status),
|
||||||
|
Count: r.Count,
|
||||||
|
TotalQuantity: r.TotalQuantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToDomainSpaceUsage(r db.GetSpaceUsageRow) models.SpaceUsageItem {
|
||||||
|
return models.SpaceUsageItem{
|
||||||
|
Warehouse: r.Warehouse,
|
||||||
|
Room: r.Room,
|
||||||
|
TotalContainers: r.TotalContainers,
|
||||||
|
UsedContainers: r.UsedContainers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,3 +76,16 @@ type TopExportedComponent struct {
|
|||||||
ComponentTypeName string `json:"componentTypeName"`
|
ComponentTypeName string `json:"componentTypeName"`
|
||||||
TotalExported int64 `json:"totalExported"`
|
TotalExported int64 `json:"totalExported"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StatusDistributionItem struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
TotalQuantity int64 `json:"totalQuantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpaceUsageItem struct {
|
||||||
|
Warehouse string `json:"warehouse"`
|
||||||
|
Room string `json:"room"`
|
||||||
|
TotalContainers int64 `json:"totalContainers"`
|
||||||
|
UsedContainers int64 `json:"usedContainers"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -142,3 +142,27 @@ func GetTopExportedComponents(ctx context.Context, queries *db.Queries, startDat
|
|||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetStatusDistribution(ctx context.Context, queries *db.Queries, warehouseID pgtype.Int8) ([]models.StatusDistributionItem, error) {
|
||||||
|
results, err := queries.GetStatusDistribution(ctx, warehouseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items := make([]models.StatusDistributionItem, 0, len(results))
|
||||||
|
for _, r := range results {
|
||||||
|
items = append(items, mapper.ToDomainStatusDistribution(r))
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSpaceUsage(ctx context.Context, queries *db.Queries, warehouseID pgtype.Int8) ([]models.SpaceUsageItem, error) {
|
||||||
|
results, err := queries.GetSpaceUsage(ctx, warehouseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items := make([]models.SpaceUsageItem, 0, len(results))
|
||||||
|
for _, r := range results {
|
||||||
|
items = append(items, mapper.ToDomainSpaceUsage(r))
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ func NewRouter() *gin.Engine {
|
|||||||
dashboard.GET("/anomalies", utils.AsyncHandler(services.DashboardAnomalies))
|
dashboard.GET("/anomalies", utils.AsyncHandler(services.DashboardAnomalies))
|
||||||
dashboard.GET("/transactions-chart", utils.AsyncHandler(services.DashboardTransactionsChart))
|
dashboard.GET("/transactions-chart", utils.AsyncHandler(services.DashboardTransactionsChart))
|
||||||
dashboard.GET("/top-components", utils.AsyncHandler(services.DashboardTopComponents))
|
dashboard.GET("/top-components", utils.AsyncHandler(services.DashboardTopComponents))
|
||||||
|
dashboard.GET("/status-distribution", utils.AsyncHandler(services.DashboardStatusDistribution))
|
||||||
|
dashboard.GET("/space-usage", utils.AsyncHandler(services.DashboardSpaceUsage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,3 +211,65 @@ func DashboardTopComponents(c *gin.Context) error {
|
|||||||
response.Ok(c, "Success", data)
|
response.Ok(c, "Success", data)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Get status distribution
|
||||||
|
// @Description Retrieve component items count grouped by status (normal, damaged, expired, etc.)
|
||||||
|
// @Tags dashboard
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param warehouse_id query int false "Filter by warehouse ID"
|
||||||
|
// @Success 200 {object} response.SuccessResponse{data=[]models.StatusDistributionItem}
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/dashboard/status-distribution [get]
|
||||||
|
func DashboardStatusDistribution(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}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := repositories.GetStatusDistribution(c.Request.Context(), global.Queries, warehouseID)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Error when Get Status Distribution")
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to get status distribution")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Ok(c, "Success", data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Get space usage
|
||||||
|
// @Description Retrieve warehouse space usage statistics showing total vs used containers per room
|
||||||
|
// @Tags dashboard
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param warehouse_id query int false "Filter by warehouse ID"
|
||||||
|
// @Success 200 {object} response.SuccessResponse{data=[]models.SpaceUsageItem}
|
||||||
|
// @Failure 400 {object} response.ErrorResponse
|
||||||
|
// @Failure 500 {object} response.ErrorResponse
|
||||||
|
// @Router /v1/dashboard/space-usage [get]
|
||||||
|
func DashboardSpaceUsage(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}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := repositories.GetSpaceUsage(c.Request.Context(), global.Queries, warehouseID)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Error when Get Space Usage")
|
||||||
|
response.InternalServerError(c, http.StatusInternalServerError, "Failed to get space usage")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
response.Ok(c, "Success", data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,6 +153,89 @@ func (q *Queries) GetContainerStats(ctx context.Context, warehouseID pgtype.Int8
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSpaceUsage = `-- name: GetSpaceUsage :many
|
||||||
|
SELECT w.name AS warehouse, r.name AS room,
|
||||||
|
COUNT(DISTINCT c.id)::bigint AS total_containers,
|
||||||
|
COUNT(DISTINCT ci.container_id)::bigint AS used_containers
|
||||||
|
FROM warehouses w
|
||||||
|
JOIN rooms r ON r.warehouse_id = w.id
|
||||||
|
JOIN cabinets cb ON cb.room_id = r.id
|
||||||
|
JOIN shelves s ON s.cabinet_id = cb.id
|
||||||
|
JOIN containers c ON c.shelf_id = s.id
|
||||||
|
LEFT JOIN component_items ci ON ci.container_id = c.id
|
||||||
|
WHERE ($1::bigint IS NULL OR w.id = $1::bigint)
|
||||||
|
GROUP BY w.name, r.name
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetSpaceUsageRow struct {
|
||||||
|
Warehouse string `db:"warehouse" json:"warehouse"`
|
||||||
|
Room string `db:"room" json:"room"`
|
||||||
|
TotalContainers int64 `db:"total_containers" json:"totalContainers"`
|
||||||
|
UsedContainers int64 `db:"used_containers" json:"usedContainers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetSpaceUsage(ctx context.Context, warehouseID pgtype.Int8) ([]GetSpaceUsageRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getSpaceUsage, warehouseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetSpaceUsageRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetSpaceUsageRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.Warehouse,
|
||||||
|
&i.Room,
|
||||||
|
&i.TotalContainers,
|
||||||
|
&i.UsedContainers,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusDistribution = `-- name: GetStatusDistribution :many
|
||||||
|
SELECT status, COUNT(*) AS count, COALESCE(SUM(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)
|
||||||
|
GROUP BY ci.status
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetStatusDistributionRow struct {
|
||||||
|
Status ComponentItemStatusEnum `db:"status" json:"status"`
|
||||||
|
Count int64 `db:"count" json:"count"`
|
||||||
|
TotalQuantity int64 `db:"total_quantity" json:"totalQuantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetStatusDistribution(ctx context.Context, warehouseID pgtype.Int8) ([]GetStatusDistributionRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getStatusDistribution, warehouseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetStatusDistributionRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetStatusDistributionRow
|
||||||
|
if err := rows.Scan(&i.Status, &i.Count, &i.TotalQuantity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const getStockAlerts = `-- name: GetStockAlerts :many
|
const getStockAlerts = `-- name: GetStockAlerts :many
|
||||||
SELECT c.id, c.name, c.unit, c.total_quantity, c.min_quantity, c.component_type_id,
|
SELECT c.id, c.name, c.unit, c.total_quantity, c.min_quantity, c.component_type_id,
|
||||||
ct.name AS component_type_name
|
ct.name AS component_type_name
|
||||||
|
|||||||
@@ -68,6 +68,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)
|
||||||
|
GetSpaceUsage(ctx context.Context, warehouseID pgtype.Int8) ([]GetSpaceUsageRow, error)
|
||||||
|
GetStatusDistribution(ctx context.Context, warehouseID pgtype.Int8) ([]GetStatusDistributionRow, error)
|
||||||
GetStockAlerts(ctx context.Context) ([]GetStockAlertsRow, error)
|
GetStockAlerts(ctx context.Context) ([]GetStockAlertsRow, error)
|
||||||
GetTodayInvoiceCounts(ctx context.Context) ([]GetTodayInvoiceCountsRow, error)
|
GetTodayInvoiceCounts(ctx context.Context) ([]GetTodayInvoiceCountsRow, error)
|
||||||
GetTopExportedComponents(ctx context.Context, arg GetTopExportedComponentsParams) ([]GetTopExportedComponentsRow, error)
|
GetTopExportedComponents(ctx context.Context, arg GetTopExportedComponentsParams) ([]GetTopExportedComponentsRow, error)
|
||||||
|
|||||||
Reference in New Issue
Block a user