feat: add dashboard summary endpoint and related models, queries, and services
This commit is contained in:
@@ -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
44
db/queries/dashboard.sql
Normal 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;
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
34
internal/mapper/dashboard_mapper.go
Normal file
34
internal/mapper/dashboard_mapper.go
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
30
internal/models/dashboard_model.go
Normal file
30
internal/models/dashboard_model.go
Normal 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"`
|
||||||
|
}
|
||||||
61
internal/repositories/dashboard_repository.go
Normal file
61
internal/repositories/dashboard_repository.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
internal/services/dashboard_service.go
Normal file
43
internal/services/dashboard_service.go
Normal 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
151
sqlc_gen/dashboard.sql.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user