diff --git a/configs/constants/constants.go b/configs/constants/constants.go index 887a951..383b28e 100644 --- a/configs/constants/constants.go +++ b/configs/constants/constants.go @@ -20,8 +20,10 @@ const ( API_GROUP_COMPONENT = "/components" API_GROUP_COMPONENT_CODE = "/component-codes" API_GROUP_COMPONENT_ITEM = "/component-items" - API_GROUP_INVOICE_CONFIG = "/invoice-configs" - API_GROUP_INVOICE_CONFIG_ITEM = "/invoice-config-items" + API_GROUP_INVOICE_CONFIG = "/invoice-configs" + API_GROUP_INVOICE_CONFIG_ITEM = "/invoice-config-items" + API_GROUP_INVOICE = "/invoices" + API_GROUP_ALTERNATIVE_COMPONENT = "/alternative-components" ) const ( diff --git a/db/queries/alternative_component.sql b/db/queries/alternative_component.sql new file mode 100644 index 0000000..cc5e21f --- /dev/null +++ b/db/queries/alternative_component.sql @@ -0,0 +1,35 @@ +-- name: GetAlternativeComponentByID :one +SELECT * FROM alternative_components +WHERE id = sqlc.arg(id); + +-- name: ListAlternativeComponents :many +SELECT * FROM alternative_components; + +-- name: CreateAlternativeComponent :one +INSERT INTO alternative_components (invoice_config_item_id, alternative_component_id, conversion_ratio, priority, note, metadata) +VALUES ( + sqlc.arg(invoice_config_item_id), + sqlc.arg(alternative_component_id), + sqlc.arg(conversion_ratio), + sqlc.arg(priority), + sqlc.arg(note), + sqlc.arg(metadata) +) +RETURNING *; + +-- name: UpdateAlternativeComponent :one +UPDATE alternative_components +SET invoice_config_item_id = coalesce(sqlc.arg(invoice_config_item_id), invoice_config_item_id), + alternative_component_id = coalesce(sqlc.arg(alternative_component_id), alternative_component_id), + conversion_ratio = coalesce(sqlc.arg(conversion_ratio), conversion_ratio), + priority = coalesce(sqlc.arg(priority), priority), + note = coalesce(sqlc.arg(note), note), + metadata = coalesce(sqlc.arg(metadata), metadata) +WHERE id = sqlc.arg(id) +RETURNING *; + +-- name: DeleteAlternativeComponent :execrows +DELETE FROM alternative_components +WHERE id = sqlc.arg(id); + + diff --git a/db/queries/invoice.sql b/db/queries/invoice.sql new file mode 100644 index 0000000..344f712 --- /dev/null +++ b/db/queries/invoice.sql @@ -0,0 +1,40 @@ +-- name: GetInvoiceByID :one +SELECT * FROM invoices +WHERE id = sqlc.arg(id); + +-- name: ListInvoices :many +SELECT * FROM invoices +ORDER BY created_at DESC; + +-- name: CreateInvoice :one +INSERT INTO invoices (type, status, invoice_config_id, total_items, note, created_by, approved_by, created_at, metadata) +VALUES ( + sqlc.arg(type), + sqlc.arg(status), + sqlc.arg(invoice_config_id), + sqlc.arg(total_items), + sqlc.arg(note), + sqlc.arg(created_by), + sqlc.arg(approved_by), + sqlc.arg(created_at), + sqlc.arg(metadata) +) +RETURNING *; + +-- name: UpdateInvoice :one +UPDATE invoices +SET type = coalesce(sqlc.arg(type), type), + status = coalesce(sqlc.arg(status), status), + invoice_config_id = coalesce(sqlc.arg(invoice_config_id), invoice_config_id), + total_items = coalesce(sqlc.arg(total_items), total_items), + note = coalesce(sqlc.arg(note), note), + metadata = coalesce(sqlc.arg(metadata), metadata), + updated_at = sqlc.arg(updated_at) +WHERE id = sqlc.arg(id) +RETURNING *; + +-- name: DeleteInvoice :execrows +DELETE FROM invoices +WHERE id = sqlc.arg(id); + + diff --git a/db/queries/invoice_item.sql b/db/queries/invoice_item.sql new file mode 100644 index 0000000..176d025 --- /dev/null +++ b/db/queries/invoice_item.sql @@ -0,0 +1,38 @@ +-- name: GetInvoiceItemByID :one +SELECT * FROM invoice_items +WHERE id = sqlc.arg(id); + +-- name: ListInvoiceItems :many +SELECT * FROM invoice_items; + +-- name: CreateInvoiceItem :one +INSERT INTO invoice_items (invoice_id,component_id,original_component_id, required_quantity,actual_quantity, is_substituted, is_short, shortage_quantity, note, metadata) +VALUES ( + sqlc.arg(invoice_id), + sqlc.arg(component_id), + sqlc.arg(original_component_id), + sqlc.arg(required_quantity), + sqlc.arg(actual_quantity), + sqlc.arg(is_substituted), + sqlc.arg(is_short), + sqlc.arg(shortage_quantity), + sqlc.arg(note), + sqlc.arg(metadata) +) +RETURNING *; + +-- name: UpdateInvoiceItem :one +UPDATE invoice_items +SET required_quantity = coalesce(sqlc.arg(required_quantity), required_quantity), + actual_quantity = coalesce(sqlc.arg(actual_quantity), actual_quantity), + is_substituted = coalesce(sqlc.arg(is_substituted), is_substituted), + is_short = coalesce(sqlc.arg(is_short), is_short), + shortage_quantity = coalesce(sqlc.arg(shortage_quantity), shortage_quantity), + note = coalesce(sqlc.arg(note), note), + metadata = coalesce(sqlc.arg(metadata), metadata) +WHERE id = sqlc.arg(id) +RETURNING *; + +-- name: DeleteInvoiceItem :execrows +DELETE FROM invoice_items +WHERE id = sqlc.arg(id); diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 302d791..d365cad 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -1327,6 +1327,279 @@ const docTemplate = `{ } } }, + "/v1/alternative-components": { + "get": { + "description": "Retrieve a list of all alternative components", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "List all alternative components", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.AlternativeComponent" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new alternative component with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Create a new alternative component", + "parameters": [ + { + "description": "Alternative component request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CreateAlternativeComponentRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.CreateAlternativeComponentResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/v1/alternative-components/{id}": { + "get": { + "description": "Retrieve a single alternative component using its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Get alternative component by ID", + "parameters": [ + { + "type": "integer", + "description": "Alternative component ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.AlternativeComponent" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "put": { + "description": "Update an existing alternative component by its ID. Only non-empty fields will be updated.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Update alternative component", + "parameters": [ + { + "type": "integer", + "description": "Alternative component ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Alternative component request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UpdateAlternativeComponentRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.UpdateAlternativeComponentResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Delete an alternative component by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Delete alternative component", + "parameters": [ + { + "type": "integer", + "description": "Alternative component ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/v1/cabinets": { "get": { "description": "Retrieve a list of all cabinets ordered by creation date", @@ -2419,6 +2692,279 @@ const docTemplate = `{ } } }, + "/v1/invoices": { + "get": { + "description": "Retrieve a list of all invoices ordered by creation date", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "List all invoices", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Invoice" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new invoice with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Create a new invoice", + "parameters": [ + { + "description": "Invoice request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CreateInvoiceRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.CreateInvoiceResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/v1/invoices/{id}": { + "get": { + "description": "Retrieve a single invoice using its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Get invoice by ID", + "parameters": [ + { + "type": "integer", + "description": "Invoice ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Invoice" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "put": { + "description": "Update an existing invoice by its ID. Only non-empty fields will be updated.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Update invoice", + "parameters": [ + { + "type": "integer", + "description": "Invoice ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Invoice request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UpdateInvoiceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.UpdateInvoiceResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Delete an invoice by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Delete invoice", + "parameters": [ + { + "type": "integer", + "description": "Invoice ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/v1/rooms": { "get": { "description": "Retrieve a list of all rooms ordered by creation date", @@ -3234,6 +3780,35 @@ const docTemplate = `{ } }, "definitions": { + "models.AlternativeComponent": { + "type": "object", + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "models.Cabinet": { "type": "object", "properties": { @@ -3452,6 +4027,53 @@ const docTemplate = `{ } } }, + "models.Invoice": { + "type": "object", + "properties": { + "approvedBy": { + "type": "string" + }, + "completedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdBy": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "invoiceCode": { + "type": "string" + }, + "invoiceConfigId": { + "type": "integer" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "models.InvoiceConfig": { "type": "object", "properties": { @@ -3611,6 +4233,32 @@ const docTemplate = `{ } } }, + "requests.CreateAlternativeComponentRequest": { + "type": "object", + "required": [ + "alternativeComponentId", + "conversionRatio", + "invoiceConfigItemId", + "priority" + ], + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "requests.CreateCabinetRequest": { "type": "object", "required": [ @@ -3818,6 +4466,36 @@ const docTemplate = `{ } } }, + "requests.CreateInvoiceRequest": { + "type": "object", + "required": [ + "status", + "type" + ], + "properties": { + "approvedBy": { + "type": "string" + }, + "createdBy": { + "type": "string" + }, + "invoiceConfigId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, "requests.CreateRoomRequest": { "type": "object", "required": [ @@ -3876,6 +4554,26 @@ const docTemplate = `{ } } }, + "requests.UpdateAlternativeComponentRequest": { + "type": "object", + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "requests.UpdateCabinetRequest": { "type": "object", "properties": { @@ -4051,6 +4749,26 @@ const docTemplate = `{ } } }, + "requests.UpdateInvoiceRequest": { + "type": "object", + "properties": { + "invoiceConfigId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, "requests.UpdateRoomRequest": { "type": "object", "properties": { @@ -4131,6 +4849,14 @@ const docTemplate = `{ } } }, + "responses.CreateAlternativeComponentResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, "responses.CreateCabinetResponse": { "type": "object", "properties": { @@ -4195,6 +4921,17 @@ const docTemplate = `{ } } }, + "responses.CreateInvoiceResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "invoiceCode": { + "type": "string" + } + } + }, "responses.CreateRoomResponse": { "type": "object", "properties": { @@ -4219,6 +4956,29 @@ const docTemplate = `{ } } }, + "responses.UpdateAlternativeComponentResponse": { + "type": "object", + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "responses.UpdateCabinetResponse": { "type": "object", "properties": { @@ -4420,6 +5180,32 @@ const docTemplate = `{ } } }, + "responses.UpdateInvoiceResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "invoiceCode": { + "type": "string" + }, + "invoiceConfigId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, "responses.UpdateRoomResponse": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index b54f038..39a7640 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -1321,6 +1321,279 @@ } } }, + "/v1/alternative-components": { + "get": { + "description": "Retrieve a list of all alternative components", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "List all alternative components", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.AlternativeComponent" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new alternative component with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Create a new alternative component", + "parameters": [ + { + "description": "Alternative component request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CreateAlternativeComponentRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.CreateAlternativeComponentResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/v1/alternative-components/{id}": { + "get": { + "description": "Retrieve a single alternative component using its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Get alternative component by ID", + "parameters": [ + { + "type": "integer", + "description": "Alternative component ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.AlternativeComponent" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "put": { + "description": "Update an existing alternative component by its ID. Only non-empty fields will be updated.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Update alternative component", + "parameters": [ + { + "type": "integer", + "description": "Alternative component ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Alternative component request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UpdateAlternativeComponentRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.UpdateAlternativeComponentResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Delete an alternative component by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "alternative-component" + ], + "summary": "Delete alternative component", + "parameters": [ + { + "type": "integer", + "description": "Alternative component ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/v1/cabinets": { "get": { "description": "Retrieve a list of all cabinets ordered by creation date", @@ -2413,6 +2686,279 @@ } } }, + "/v1/invoices": { + "get": { + "description": "Retrieve a list of all invoices ordered by creation date", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "List all invoices", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Invoice" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new invoice with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Create a new invoice", + "parameters": [ + { + "description": "Invoice request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CreateInvoiceRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.CreateInvoiceResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/v1/invoices/{id}": { + "get": { + "description": "Retrieve a single invoice using its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Get invoice by ID", + "parameters": [ + { + "type": "integer", + "description": "Invoice ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Invoice" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "put": { + "description": "Update an existing invoice by its ID. Only non-empty fields will be updated.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Update invoice", + "parameters": [ + { + "type": "integer", + "description": "Invoice ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Invoice request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UpdateInvoiceRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.UpdateInvoiceResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Delete an invoice by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "invoice" + ], + "summary": "Delete invoice", + "parameters": [ + { + "type": "integer", + "description": "Invoice ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, "/v1/rooms": { "get": { "description": "Retrieve a list of all rooms ordered by creation date", @@ -3228,6 +3774,35 @@ } }, "definitions": { + "models.AlternativeComponent": { + "type": "object", + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "models.Cabinet": { "type": "object", "properties": { @@ -3446,6 +4021,53 @@ } } }, + "models.Invoice": { + "type": "object", + "properties": { + "approvedBy": { + "type": "string" + }, + "completedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdBy": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "invoiceCode": { + "type": "string" + }, + "invoiceConfigId": { + "type": "integer" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "models.InvoiceConfig": { "type": "object", "properties": { @@ -3605,6 +4227,32 @@ } } }, + "requests.CreateAlternativeComponentRequest": { + "type": "object", + "required": [ + "alternativeComponentId", + "conversionRatio", + "invoiceConfigItemId", + "priority" + ], + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "requests.CreateCabinetRequest": { "type": "object", "required": [ @@ -3812,6 +4460,36 @@ } } }, + "requests.CreateInvoiceRequest": { + "type": "object", + "required": [ + "status", + "type" + ], + "properties": { + "approvedBy": { + "type": "string" + }, + "createdBy": { + "type": "string" + }, + "invoiceConfigId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, "requests.CreateRoomRequest": { "type": "object", "required": [ @@ -3870,6 +4548,26 @@ } } }, + "requests.UpdateAlternativeComponentRequest": { + "type": "object", + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "requests.UpdateCabinetRequest": { "type": "object", "properties": { @@ -4045,6 +4743,26 @@ } } }, + "requests.UpdateInvoiceRequest": { + "type": "object", + "properties": { + "invoiceConfigId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, "requests.UpdateRoomRequest": { "type": "object", "properties": { @@ -4125,6 +4843,14 @@ } } }, + "responses.CreateAlternativeComponentResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, "responses.CreateCabinetResponse": { "type": "object", "properties": { @@ -4189,6 +4915,17 @@ } } }, + "responses.CreateInvoiceResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "invoiceCode": { + "type": "string" + } + } + }, "responses.CreateRoomResponse": { "type": "object", "properties": { @@ -4213,6 +4950,29 @@ } } }, + "responses.UpdateAlternativeComponentResponse": { + "type": "object", + "properties": { + "alternativeComponentId": { + "type": "integer" + }, + "conversionRatio": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "invoiceConfigItemId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + }, "responses.UpdateCabinetResponse": { "type": "object", "properties": { @@ -4414,6 +5174,32 @@ } } }, + "responses.UpdateInvoiceResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "invoiceCode": { + "type": "string" + }, + "invoiceConfigId": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalItems": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, "responses.UpdateRoomResponse": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 0c3274d..84229f0 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1,5 +1,24 @@ basePath: /api/v1 definitions: + models.AlternativeComponent: + properties: + alternativeComponentId: + type: integer + conversionRatio: + type: string + id: + type: integer + invoiceConfigItemId: + type: integer + metadata: + items: + type: integer + type: array + note: + type: string + priority: + type: integer + type: object models.Cabinet: properties: createdAt: @@ -143,6 +162,37 @@ definitions: warehouseName: type: string type: object + models.Invoice: + properties: + approvedBy: + type: string + completedAt: + type: string + createdAt: + type: string + createdBy: + type: string + id: + type: integer + invoiceCode: + type: string + invoiceConfigId: + type: integer + metadata: + items: + type: integer + type: array + note: + type: string + status: + type: string + totalItems: + type: integer + type: + type: string + updatedAt: + type: string + type: object models.InvoiceConfig: properties: createdAt: @@ -248,6 +298,24 @@ definitions: - password - username type: object + requests.CreateAlternativeComponentRequest: + properties: + alternativeComponentId: + type: integer + conversionRatio: + type: string + invoiceConfigItemId: + type: integer + note: + type: string + priority: + type: integer + required: + - alternativeComponentId + - conversionRatio + - invoiceConfigItemId + - priority + type: object requests.CreateCabinetRequest: properties: description: @@ -388,6 +456,26 @@ definitions: - name - type type: object + requests.CreateInvoiceRequest: + properties: + approvedBy: + type: string + createdBy: + type: string + invoiceConfigId: + type: integer + note: + type: string + status: + type: string + totalItems: + type: integer + type: + type: string + required: + - status + - type + type: object requests.CreateRoomRequest: properties: description: @@ -427,6 +515,19 @@ definitions: - address - name type: object + requests.UpdateAlternativeComponentRequest: + properties: + alternativeComponentId: + type: integer + conversionRatio: + type: string + invoiceConfigItemId: + type: integer + note: + type: string + priority: + type: integer + type: object requests.UpdateCabinetRequest: properties: description: @@ -542,6 +643,19 @@ definitions: type: type: string type: object + requests.UpdateInvoiceRequest: + properties: + invoiceConfigId: + type: integer + note: + type: string + status: + type: string + totalItems: + type: integer + type: + type: string + type: object requests.UpdateRoomRequest: properties: description: @@ -594,6 +708,11 @@ definitions: id: type: string type: object + responses.CreateAlternativeComponentResponse: + properties: + id: + type: integer + type: object responses.CreateCabinetResponse: properties: id: @@ -634,6 +753,13 @@ definitions: id: type: integer type: object + responses.CreateInvoiceResponse: + properties: + id: + type: integer + invoiceCode: + type: string + type: object responses.CreateRoomResponse: properties: id: @@ -649,6 +775,21 @@ definitions: id: type: integer type: object + responses.UpdateAlternativeComponentResponse: + properties: + alternativeComponentId: + type: integer + conversionRatio: + type: string + id: + type: integer + invoiceConfigItemId: + type: integer + note: + type: string + priority: + type: integer + type: object responses.UpdateCabinetResponse: properties: description: @@ -780,6 +921,23 @@ definitions: type: type: string type: object + responses.UpdateInvoiceResponse: + properties: + id: + type: integer + invoiceCode: + type: string + invoiceConfigId: + type: integer + note: + type: string + status: + type: string + totalItems: + type: integer + type: + type: string + type: object responses.UpdateRoomResponse: properties: description: @@ -1642,6 +1800,176 @@ paths: summary: Health check tags: - health + /v1/alternative-components: + get: + consumes: + - application/json + description: Retrieve a list of all alternative components + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + items: + $ref: '#/definitions/models.AlternativeComponent' + type: array + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: List all alternative components + tags: + - alternative-component + post: + consumes: + - application/json + description: Create a new alternative component with the provided details + parameters: + - description: Alternative component request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/requests.CreateAlternativeComponentRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/responses.CreateAlternativeComponentResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Create a new alternative component + tags: + - alternative-component + /v1/alternative-components/{id}: + delete: + consumes: + - application/json + description: Delete an alternative component by its unique identifier + parameters: + - description: Alternative component ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.SuccessResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Delete alternative component + tags: + - alternative-component + get: + consumes: + - application/json + description: Retrieve a single alternative component using its unique identifier + parameters: + - description: Alternative component ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/models.AlternativeComponent' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Get alternative component by ID + tags: + - alternative-component + put: + consumes: + - application/json + description: Update an existing alternative component by its ID. Only non-empty + fields will be updated. + parameters: + - description: Alternative component ID + in: path + name: id + required: true + type: integer + - description: Alternative component request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/requests.UpdateAlternativeComponentRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/responses.UpdateAlternativeComponentResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Update alternative component + tags: + - alternative-component /v1/cabinets: get: consumes: @@ -2322,6 +2650,176 @@ paths: summary: Update invoice config tags: - invoice-config + /v1/invoices: + get: + consumes: + - application/json + description: Retrieve a list of all invoices ordered by creation date + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + items: + $ref: '#/definitions/models.Invoice' + type: array + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: List all invoices + tags: + - invoice + post: + consumes: + - application/json + description: Create a new invoice with the provided details + parameters: + - description: Invoice request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/requests.CreateInvoiceRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/responses.CreateInvoiceResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Create a new invoice + tags: + - invoice + /v1/invoices/{id}: + delete: + consumes: + - application/json + description: Delete an invoice by its unique identifier + parameters: + - description: Invoice ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.SuccessResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Delete invoice + tags: + - invoice + get: + consumes: + - application/json + description: Retrieve a single invoice using its unique identifier + parameters: + - description: Invoice ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/models.Invoice' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Get invoice by ID + tags: + - invoice + put: + consumes: + - application/json + description: Update an existing invoice by its ID. Only non-empty fields will + be updated. + parameters: + - description: Invoice ID + in: path + name: id + required: true + type: integer + - description: Invoice request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/requests.UpdateInvoiceRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/responses.UpdateInvoiceResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: Update invoice + tags: + - invoice /v1/rooms: get: consumes: diff --git a/internal/mapper/alternative_component_mapper.go b/internal/mapper/alternative_component_mapper.go new file mode 100644 index 0000000..518220c --- /dev/null +++ b/internal/mapper/alternative_component_mapper.go @@ -0,0 +1,67 @@ +package mapper + +import ( + "encoding/json" + "wm-backend/internal/models" + db "wm-backend/sqlc_gen" + + "github.com/jackc/pgx/v5/pgtype" +) + +func numericToString(n pgtype.Numeric) string { + if !n.Valid { + return "" + } + b, _ := json.Marshal(n) + return string(b) +} + +func stringToNumeric(s string) pgtype.Numeric { + var n pgtype.Numeric + if s == "" { + return n + } + _ = json.Unmarshal([]byte(s), &n) + return n +} + +func ToDomainAlternativeComponent(r db.AlternativeComponent) *models.AlternativeComponent { + return &models.AlternativeComponent{ + ID: r.ID, + InvoiceConfigItemID: r.InvoiceConfigItemID, + AlternativeComponentID: r.AlternativeComponentID, + ConversionRatio: numericToString(r.ConversionRatio), + Priority: r.Priority, + Note: r.Note.String, + Metadata: r.Metadata, + } +} + +func ToModelAlternativeComponent(r *models.AlternativeComponent) *db.CreateAlternativeComponentParams { + return &db.CreateAlternativeComponentParams{ + InvoiceConfigItemID: r.InvoiceConfigItemID, + AlternativeComponentID: r.AlternativeComponentID, + ConversionRatio: stringToNumeric(r.ConversionRatio), + Priority: r.Priority, + Note: pgtype.Text{ + String: r.Note, + Valid: r.Note != "", + }, + Metadata: r.Metadata, + } +} + +func ToUpdateModelAlternativeComponent(r *models.AlternativeComponent) *db.UpdateAlternativeComponentParams { + return &db.UpdateAlternativeComponentParams{ + InvoiceConfigItemID: r.InvoiceConfigItemID, + AlternativeComponentID: r.AlternativeComponentID, + ConversionRatio: stringToNumeric(r.ConversionRatio), + Priority: r.Priority, + Note: pgtype.Text{ + String: r.Note, + Valid: r.Note != "", + }, + Metadata: r.Metadata, + ID: r.ID, + } +} diff --git a/internal/mapper/invoice_mapper.go b/internal/mapper/invoice_mapper.go new file mode 100644 index 0000000..738a810 --- /dev/null +++ b/internal/mapper/invoice_mapper.go @@ -0,0 +1,78 @@ +package mapper + +import ( + "wm-backend/internal/models" + db "wm-backend/sqlc_gen" + + "github.com/jackc/pgx/v5/pgtype" +) + +func timestampToTime(t pgtype.Timestamptz) string { + if !t.Valid { + return "" + } + return t.Time.String() +} + +func ToDomainInvoice(r db.Invoice) *models.Invoice { + return &models.Invoice{ + ID: r.ID, + InvoiceCode: r.InvoiceCode, + Type: string(r.Type), + Status: string(r.Status), + InvoiceConfigID: r.InvoiceConfigID.Int64, + TotalItems: r.TotalItems, + Note: r.Note.String, + CreatedBy: r.CreatedBy.String, + ApprovedBy: r.ApprovedBy.String, + CompletedAt: r.CompletedAt.Time, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + Metadata: r.Metadata, + } +} + +func ToModelInvoice(r *models.Invoice) *db.CreateInvoiceParams { + return &db.CreateInvoiceParams{ + Type: db.InvoiceTypeEnum(r.Type), + Status: db.InvoiceStatusEnum(r.Status), + InvoiceConfigID: pgtype.Int8{ + Int64: r.InvoiceConfigID, + Valid: r.InvoiceConfigID != 0, + }, + TotalItems: r.TotalItems, + Note: pgtype.Text{ + String: r.Note, + Valid: r.Note != "", + }, + CreatedBy: pgtype.Text{ + String: r.CreatedBy, + Valid: r.CreatedBy != "", + }, + ApprovedBy: pgtype.Text{ + String: r.ApprovedBy, + Valid: r.ApprovedBy != "", + }, + CreatedAt: r.CreatedAt, + Metadata: r.Metadata, + } +} + +func ToUpdateModelInvoice(r *models.Invoice) *db.UpdateInvoiceParams { + return &db.UpdateInvoiceParams{ + Type: db.InvoiceTypeEnum(r.Type), + Status: db.InvoiceStatusEnum(r.Status), + InvoiceConfigID: pgtype.Int8{ + Int64: r.InvoiceConfigID, + Valid: r.InvoiceConfigID != 0, + }, + TotalItems: r.TotalItems, + Note: pgtype.Text{ + String: r.Note, + Valid: r.Note != "", + }, + Metadata: r.Metadata, + UpdatedAt: r.UpdatedAt, + ID: r.ID, + } +} diff --git a/internal/models/alternative_component_model.go b/internal/models/alternative_component_model.go new file mode 100644 index 0000000..5155c02 --- /dev/null +++ b/internal/models/alternative_component_model.go @@ -0,0 +1,11 @@ +package models + +type AlternativeComponent struct { + ID int64 `json:"id"` + InvoiceConfigItemID int64 `json:"invoiceConfigItemId"` + AlternativeComponentID int64 `json:"alternativeComponentId"` + ConversionRatio string `json:"conversionRatio"` + Priority int32 `json:"priority"` + Note string `json:"note"` + Metadata []byte `json:"metadata"` +} diff --git a/internal/models/invoice_model.go b/internal/models/invoice_model.go new file mode 100644 index 0000000..4402ba7 --- /dev/null +++ b/internal/models/invoice_model.go @@ -0,0 +1,19 @@ +package models + +import "time" + +type Invoice struct { + ID int64 `json:"id"` + InvoiceCode string `json:"invoiceCode"` + Type string `json:"type"` + Status string `json:"status"` + InvoiceConfigID int64 `json:"invoiceConfigId"` + TotalItems int32 `json:"totalItems"` + Note string `json:"note"` + CreatedBy string `json:"createdBy"` + ApprovedBy string `json:"approvedBy"` + CompletedAt time.Time `json:"completedAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Metadata []byte `json:"metadata"` +} diff --git a/internal/models/requests/alternative_component_request.go b/internal/models/requests/alternative_component_request.go new file mode 100644 index 0000000..6f23576 --- /dev/null +++ b/internal/models/requests/alternative_component_request.go @@ -0,0 +1,17 @@ +package requests + +type CreateAlternativeComponentRequest struct { + InvoiceConfigItemID int64 `json:"invoiceConfigItemId" binding:"required"` + AlternativeComponentID int64 `json:"alternativeComponentId" binding:"required"` + ConversionRatio string `json:"conversionRatio" binding:"required"` + Priority int32 `json:"priority" binding:"required"` + Note string `json:"note"` +} + +type UpdateAlternativeComponentRequest struct { + InvoiceConfigItemID *int64 `json:"invoiceConfigItemId"` + AlternativeComponentID *int64 `json:"alternativeComponentId"` + ConversionRatio *string `json:"conversionRatio"` + Priority *int32 `json:"priority"` + Note string `json:"note"` +} diff --git a/internal/models/requests/invoice_request.go b/internal/models/requests/invoice_request.go new file mode 100644 index 0000000..f5bf2aa --- /dev/null +++ b/internal/models/requests/invoice_request.go @@ -0,0 +1,19 @@ +package requests + +type CreateInvoiceRequest struct { + Type string `json:"type" binding:"required"` + Status string `json:"status" binding:"required"` + InvoiceConfigID int64 `json:"invoiceConfigId"` + TotalItems int32 `json:"totalItems"` + Note string `json:"note"` + CreatedBy string `json:"createdBy"` + ApprovedBy string `json:"approvedBy"` +} + +type UpdateInvoiceRequest struct { + Type *string `json:"type"` + Status *string `json:"status"` + InvoiceConfigID *int64 `json:"invoiceConfigId"` + TotalItems *int32 `json:"totalItems"` + Note string `json:"note"` +} diff --git a/internal/models/responses/alternative_component_response.go b/internal/models/responses/alternative_component_response.go new file mode 100644 index 0000000..e60e325 --- /dev/null +++ b/internal/models/responses/alternative_component_response.go @@ -0,0 +1,14 @@ +package responses + +type CreateAlternativeComponentResponse struct { + ID int64 `json:"id"` +} + +type UpdateAlternativeComponentResponse struct { + ID int64 `json:"id"` + InvoiceConfigItemID int64 `json:"invoiceConfigItemId"` + AlternativeComponentID int64 `json:"alternativeComponentId"` + ConversionRatio string `json:"conversionRatio"` + Priority int32 `json:"priority"` + Note string `json:"note"` +} diff --git a/internal/models/responses/invoice_response.go b/internal/models/responses/invoice_response.go new file mode 100644 index 0000000..b3ebf78 --- /dev/null +++ b/internal/models/responses/invoice_response.go @@ -0,0 +1,16 @@ +package responses + +type CreateInvoiceResponse struct { + ID int64 `json:"id"` + InvoiceCode string `json:"invoiceCode"` +} + +type UpdateInvoiceResponse struct { + ID int64 `json:"id"` + InvoiceCode string `json:"invoiceCode"` + Type string `json:"type"` + Status string `json:"status"` + InvoiceConfigID int64 `json:"invoiceConfigId"` + TotalItems int32 `json:"totalItems"` + Note string `json:"note"` +} diff --git a/internal/repositories/alternative_component_repository.go b/internal/repositories/alternative_component_repository.go new file mode 100644 index 0000000..3bb01cc --- /dev/null +++ b/internal/repositories/alternative_component_repository.go @@ -0,0 +1,52 @@ +package repositories + +import ( + "context" + "wm-backend/internal/mapper" + "wm-backend/internal/models" + db "wm-backend/sqlc_gen" +) + +func CreateAlternativeComponent(ctx context.Context, queries *db.Queries, body models.AlternativeComponent) (models.AlternativeComponent, error) { + result, err := queries.CreateAlternativeComponent(ctx, *mapper.ToModelAlternativeComponent(&body)) + if err != nil { + return models.AlternativeComponent{}, err + } + return *mapper.ToDomainAlternativeComponent(result), nil +} + +func GetAlternativeComponentByID(ctx context.Context, queries *db.Queries, id int64) (models.AlternativeComponent, error) { + result, err := queries.GetAlternativeComponentByID(ctx, id) + if err != nil { + return models.AlternativeComponent{}, err + } + return *mapper.ToDomainAlternativeComponent(result), nil +} + +func ListAlternativeComponents(ctx context.Context, queries *db.Queries) ([]models.AlternativeComponent, error) { + results, err := queries.ListAlternativeComponents(ctx) + if err != nil { + return nil, err + } + var items []models.AlternativeComponent + for _, r := range results { + items = append(items, *mapper.ToDomainAlternativeComponent(r)) + } + return items, nil +} + +func UpdateAlternativeComponent(ctx context.Context, queries *db.Queries, body models.AlternativeComponent) (models.AlternativeComponent, error) { + result, err := queries.UpdateAlternativeComponent(ctx, *mapper.ToUpdateModelAlternativeComponent(&body)) + if err != nil { + return models.AlternativeComponent{}, err + } + return *mapper.ToDomainAlternativeComponent(result), nil +} + +func DeleteAlternativeComponent(ctx context.Context, queries *db.Queries, id int64) (int64, error) { + rowsAffected, err := queries.DeleteAlternativeComponent(ctx, id) + if err != nil { + return rowsAffected, err + } + return rowsAffected, nil +} diff --git a/internal/repositories/invoice_repository.go b/internal/repositories/invoice_repository.go new file mode 100644 index 0000000..ca2e857 --- /dev/null +++ b/internal/repositories/invoice_repository.go @@ -0,0 +1,52 @@ +package repositories + +import ( + "context" + "wm-backend/internal/mapper" + "wm-backend/internal/models" + db "wm-backend/sqlc_gen" +) + +func CreateInvoice(ctx context.Context, queries *db.Queries, body models.Invoice) (models.Invoice, error) { + result, err := queries.CreateInvoice(ctx, *mapper.ToModelInvoice(&body)) + if err != nil { + return models.Invoice{}, err + } + return *mapper.ToDomainInvoice(result), nil +} + +func GetInvoiceByID(ctx context.Context, queries *db.Queries, id int64) (models.Invoice, error) { + result, err := queries.GetInvoiceByID(ctx, id) + if err != nil { + return models.Invoice{}, err + } + return *mapper.ToDomainInvoice(result), nil +} + +func ListInvoices(ctx context.Context, queries *db.Queries) ([]models.Invoice, error) { + results, err := queries.ListInvoices(ctx) + if err != nil { + return nil, err + } + var items []models.Invoice + for _, r := range results { + items = append(items, *mapper.ToDomainInvoice(r)) + } + return items, nil +} + +func UpdateInvoice(ctx context.Context, queries *db.Queries, body models.Invoice) (models.Invoice, error) { + result, err := queries.UpdateInvoice(ctx, *mapper.ToUpdateModelInvoice(&body)) + if err != nil { + return models.Invoice{}, err + } + return *mapper.ToDomainInvoice(result), nil +} + +func DeleteInvoice(ctx context.Context, queries *db.Queries, id int64) (int64, error) { + rowsAffected, err := queries.DeleteInvoice(ctx, id) + if err != nil { + return rowsAffected, err + } + return rowsAffected, nil +} diff --git a/internal/routers/router.go b/internal/routers/router.go index f27625d..7e28f97 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -129,6 +129,24 @@ func NewRouter() *gin.Engine { invoiceConfigItem.PUT("/:id", utils.AsyncHandler(services.InvoiceConfigItemUpdate)) invoiceConfigItem.DELETE("/:id", utils.AsyncHandler(services.InvoiceConfigItemDelete)) } + + invoice := v1.Group(constants.API_GROUP_INVOICE) + { + invoice.GET("", utils.AsyncHandler(services.InvoiceList)) + invoice.GET("/:id", utils.AsyncHandler(services.InvoiceGetByID)) + invoice.POST("", utils.AsyncHandler(services.InvoiceCreate)) + invoice.PUT("/:id", utils.AsyncHandler(services.InvoiceUpdate)) + invoice.DELETE("/:id", utils.AsyncHandler(services.InvoiceDelete)) + } + + alternativeComponent := v1.Group(constants.API_GROUP_ALTERNATIVE_COMPONENT) + { + alternativeComponent.GET("", utils.AsyncHandler(services.AlternativeComponentList)) + alternativeComponent.GET("/:id", utils.AsyncHandler(services.AlternativeComponentGetByID)) + alternativeComponent.POST("", utils.AsyncHandler(services.AlternativeComponentCreate)) + alternativeComponent.PUT("/:id", utils.AsyncHandler(services.AlternativeComponentUpdate)) + alternativeComponent.DELETE("/:id", utils.AsyncHandler(services.AlternativeComponentDelete)) + } } r.GET(constants.API_PATH_PING, services.PingHandler) diff --git a/internal/services/alternative_component_service.go b/internal/services/alternative_component_service.go new file mode 100644 index 0000000..d3d48f1 --- /dev/null +++ b/internal/services/alternative_component_service.go @@ -0,0 +1,194 @@ +package services + +import ( + "net/http" + "strconv" + "wm-backend/global" + "wm-backend/internal/models" + "wm-backend/internal/models/requests" + "wm-backend/internal/models/responses" + "wm-backend/internal/repositories" + "wm-backend/pkg/helper" + "wm-backend/response" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +// AlternativeComponentCreate creates a new alternative component. +// +// @Summary Create a new alternative component +// @Description Create a new alternative component with the provided details +// @Tags alternative-component +// @Accept json +// @Produce json +// @Param body body requests.CreateAlternativeComponentRequest true "Alternative component request body" +// @Success 201 {object} response.SuccessResponse{data=responses.CreateAlternativeComponentResponse} +// @Failure 400 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/alternative-components [post] +func AlternativeComponentCreate(c *gin.Context) error { + requestBody := requests.CreateAlternativeComponentRequest{} + if helper.IsShouldBindJSON(c, &requestBody) { + return nil + } + entityModel := &models.AlternativeComponent{ + InvoiceConfigItemID: requestBody.InvoiceConfigItemID, + AlternativeComponentID: requestBody.AlternativeComponentID, + ConversionRatio: requestBody.ConversionRatio, + Priority: requestBody.Priority, + Note: requestBody.Note, + } + entity, err := repositories.CreateAlternativeComponent(c.Request.Context(), global.Queries, *entityModel) + if err != nil { + log.Error().Err(err).Msg("Failed to create alternative component") + response.InternalServerError(c, http.StatusInternalServerError, "Failed to create alternative component") + return nil + } + response.Created(c, "Alternative component created successfully", &responses.CreateAlternativeComponentResponse{ + ID: entity.ID, + }) + return nil +} + +// AlternativeComponentGetByID retrieves a single alternative component by its ID. +// +// @Summary Get alternative component by ID +// @Description Retrieve a single alternative component using its unique identifier +// @Tags alternative-component +// @Accept json +// @Produce json +// @Param id path int true "Alternative component ID" +// @Success 200 {object} response.SuccessResponse{data=models.AlternativeComponent} +// @Failure 400 {object} response.ErrorResponse +// @Failure 404 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/alternative-components/{id} [get] +func AlternativeComponentGetByID(c *gin.Context) error { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequestError(c, http.StatusBadRequest, "Invalid ID") + return nil + } + entity, err := repositories.GetAlternativeComponentByID(c.Request.Context(), global.Queries, id) + if err != nil { + log.Error().Err(err).Msgf("Failed to get alternative component by ID: %d", id) + response.NotFoundError(c, http.StatusNotFound, "Alternative component not found") + return nil + } + response.Ok(c, "Success", entity) + return nil +} + +// AlternativeComponentList retrieves all alternative components. +// +// @Summary List all alternative components +// @Description Retrieve a list of all alternative components +// @Tags alternative-component +// @Accept json +// @Produce json +// @Success 200 {object} response.SuccessResponse{data=[]models.AlternativeComponent} +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/alternative-components [get] +func AlternativeComponentList(c *gin.Context) error { + entities, err := repositories.ListAlternativeComponents(c.Request.Context(), global.Queries) + if err != nil { + response.InternalServerError(c, http.StatusInternalServerError, "Failed to list alternative components") + return nil + } + response.Ok(c, "Success", entities) + return nil +} + +// AlternativeComponentUpdate updates an existing alternative component by its ID. +// +// @Summary Update alternative component +// @Description Update an existing alternative component by its ID. Only non-empty fields will be updated. +// @Tags alternative-component +// @Accept json +// @Produce json +// @Param id path int true "Alternative component ID" +// @Param body body requests.UpdateAlternativeComponentRequest true "Alternative component request body" +// @Success 200 {object} response.SuccessResponse{data=responses.UpdateAlternativeComponentResponse} +// @Failure 400 {object} response.ErrorResponse +// @Failure 404 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/alternative-components/{id} [put] +func AlternativeComponentUpdate(c *gin.Context) error { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequestError(c, http.StatusBadRequest, "Invalid ID") + return nil + } + requestBody := requests.UpdateAlternativeComponentRequest{} + if helper.IsShouldBindJSON(c, &requestBody) { + return nil + } + existing, err := repositories.GetAlternativeComponentByID(c.Request.Context(), global.Queries, id) + if err != nil { + response.NotFoundError(c, http.StatusNotFound, "Alternative component not found") + return nil + } + if requestBody.InvoiceConfigItemID != nil { + existing.InvoiceConfigItemID = *requestBody.InvoiceConfigItemID + } + if requestBody.AlternativeComponentID != nil { + existing.AlternativeComponentID = *requestBody.AlternativeComponentID + } + if requestBody.ConversionRatio != nil { + existing.ConversionRatio = *requestBody.ConversionRatio + } + if requestBody.Priority != nil { + existing.Priority = *requestBody.Priority + } + if requestBody.Note != "" { + existing.Note = requestBody.Note + } + entity, err := repositories.UpdateAlternativeComponent(c.Request.Context(), global.Queries, existing) + if err != nil { + log.Error().Err(err).Msgf("Failed to update alternative component with ID: %d", id) + response.InternalServerError(c, http.StatusInternalServerError, "Failed to update alternative component") + return nil + } + response.Ok(c, "Alternative component updated successfully", &responses.UpdateAlternativeComponentResponse{ + ID: entity.ID, + InvoiceConfigItemID: entity.InvoiceConfigItemID, + AlternativeComponentID: entity.AlternativeComponentID, + ConversionRatio: entity.ConversionRatio, + Priority: entity.Priority, + Note: entity.Note, + }) + return nil +} + +// AlternativeComponentDelete deletes an alternative component by its ID. +// +// @Summary Delete alternative component +// @Description Delete an alternative component by its unique identifier +// @Tags alternative-component +// @Accept json +// @Produce json +// @Param id path int true "Alternative component ID" +// @Success 200 {object} response.SuccessResponse +// @Failure 400 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/alternative-components/{id} [delete] +func AlternativeComponentDelete(c *gin.Context) error { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequestError(c, http.StatusBadRequest, "Invalid ID") + return nil + } + rowsAffected, err := repositories.DeleteAlternativeComponent(c.Request.Context(), global.Queries, id) + if err != nil { + log.Error().Err(err).Msgf("Failed to delete alternative component with ID: %d", id) + response.InternalServerError(c, http.StatusInternalServerError, "Failed to delete alternative component") + return nil + } + if rowsAffected == 0 { + response.NotFoundError(c, http.StatusNotFound, "Alternative component not found") + return nil + } + response.Ok(c, "Delete Success", nil) + return nil +} diff --git a/internal/services/invoice_service.go b/internal/services/invoice_service.go new file mode 100644 index 0000000..d03439f --- /dev/null +++ b/internal/services/invoice_service.go @@ -0,0 +1,201 @@ +package services + +import ( + "net/http" + "strconv" + "time" + "wm-backend/global" + "wm-backend/internal/models" + "wm-backend/internal/models/requests" + "wm-backend/internal/models/responses" + "wm-backend/internal/repositories" + "wm-backend/pkg/helper" + "wm-backend/response" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +// InvoiceCreate creates a new invoice. +// +// @Summary Create a new invoice +// @Description Create a new invoice with the provided details +// @Tags invoice +// @Accept json +// @Produce json +// @Param body body requests.CreateInvoiceRequest true "Invoice request body" +// @Success 201 {object} response.SuccessResponse{data=responses.CreateInvoiceResponse} +// @Failure 400 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/invoices [post] +func InvoiceCreate(c *gin.Context) error { + requestBody := requests.CreateInvoiceRequest{} + if helper.IsShouldBindJSON(c, &requestBody) { + return nil + } + invoiceModel := &models.Invoice{ + Type: requestBody.Type, + Status: requestBody.Status, + InvoiceConfigID: requestBody.InvoiceConfigID, + TotalItems: requestBody.TotalItems, + Note: requestBody.Note, + CreatedBy: requestBody.CreatedBy, + ApprovedBy: requestBody.ApprovedBy, + CreatedAt: time.Now(), + } + invoice, err := repositories.CreateInvoice(c.Request.Context(), global.Queries, *invoiceModel) + if err != nil { + log.Error().Err(err).Msg("Failed to create invoice") + response.InternalServerError(c, http.StatusInternalServerError, "Failed to create invoice") + return nil + } + response.Created(c, "Invoice created successfully", &responses.CreateInvoiceResponse{ + ID: invoice.ID, + InvoiceCode: invoice.InvoiceCode, + }) + return nil +} + +// InvoiceGetByID retrieves a single invoice by its ID. +// +// @Summary Get invoice by ID +// @Description Retrieve a single invoice using its unique identifier +// @Tags invoice +// @Accept json +// @Produce json +// @Param id path int true "Invoice ID" +// @Success 200 {object} response.SuccessResponse{data=models.Invoice} +// @Failure 400 {object} response.ErrorResponse +// @Failure 404 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/invoices/{id} [get] +func InvoiceGetByID(c *gin.Context) error { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequestError(c, http.StatusBadRequest, "Invalid ID") + return nil + } + invoice, err := repositories.GetInvoiceByID(c.Request.Context(), global.Queries, id) + if err != nil { + log.Error().Err(err).Msgf("Failed to get invoice by ID: %d", id) + response.NotFoundError(c, http.StatusNotFound, "Invoice not found") + return nil + } + response.Ok(c, "Success", invoice) + return nil +} + +// InvoiceList retrieves all invoices. +// +// @Summary List all invoices +// @Description Retrieve a list of all invoices ordered by creation date +// @Tags invoice +// @Accept json +// @Produce json +// @Success 200 {object} response.SuccessResponse{data=[]models.Invoice} +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/invoices [get] +func InvoiceList(c *gin.Context) error { + invoices, err := repositories.ListInvoices(c.Request.Context(), global.Queries) + if err != nil { + response.InternalServerError(c, http.StatusInternalServerError, "Failed to list invoices") + return nil + } + response.Ok(c, "Success", invoices) + return nil +} + +// InvoiceUpdate updates an existing invoice by its ID. +// +// @Summary Update invoice +// @Description Update an existing invoice by its ID. Only non-empty fields will be updated. +// @Tags invoice +// @Accept json +// @Produce json +// @Param id path int true "Invoice ID" +// @Param body body requests.UpdateInvoiceRequest true "Invoice request body" +// @Success 200 {object} response.SuccessResponse{data=responses.UpdateInvoiceResponse} +// @Failure 400 {object} response.ErrorResponse +// @Failure 404 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/invoices/{id} [put] +func InvoiceUpdate(c *gin.Context) error { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequestError(c, http.StatusBadRequest, "Invalid ID") + return nil + } + requestBody := requests.UpdateInvoiceRequest{} + if helper.IsShouldBindJSON(c, &requestBody) { + return nil + } + existing, err := repositories.GetInvoiceByID(c.Request.Context(), global.Queries, id) + if err != nil { + response.NotFoundError(c, http.StatusNotFound, "Invoice not found") + return nil + } + if requestBody.Type != nil { + existing.Type = *requestBody.Type + } + if requestBody.Status != nil { + existing.Status = *requestBody.Status + } + if requestBody.InvoiceConfigID != nil { + existing.InvoiceConfigID = *requestBody.InvoiceConfigID + } + if requestBody.TotalItems != nil { + existing.TotalItems = *requestBody.TotalItems + } + if requestBody.Note != "" { + existing.Note = requestBody.Note + } + existing.UpdatedAt = time.Now() + invoice, err := repositories.UpdateInvoice(c.Request.Context(), global.Queries, existing) + if err != nil { + log.Error().Err(err).Msgf("Failed to update invoice with ID: %d", id) + response.InternalServerError(c, http.StatusInternalServerError, "Failed to update invoice") + return nil + } + response.Ok(c, "Invoice updated successfully", &responses.UpdateInvoiceResponse{ + ID: invoice.ID, + InvoiceCode: invoice.InvoiceCode, + Type: invoice.Type, + Status: invoice.Status, + InvoiceConfigID: invoice.InvoiceConfigID, + TotalItems: invoice.TotalItems, + Note: invoice.Note, + }) + return nil +} + +// InvoiceDelete deletes an invoice by its ID. +// +// @Summary Delete invoice +// @Description Delete an invoice by its unique identifier +// @Tags invoice +// @Accept json +// @Produce json +// @Param id path int true "Invoice ID" +// @Success 200 {object} response.SuccessResponse +// @Failure 400 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /v1/invoices/{id} [delete] +func InvoiceDelete(c *gin.Context) error { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequestError(c, http.StatusBadRequest, "Invalid ID") + return nil + } + rowsAffected, err := repositories.DeleteInvoice(c.Request.Context(), global.Queries, id) + if err != nil { + log.Error().Err(err).Msgf("Failed to delete invoice with ID: %d", id) + response.InternalServerError(c, http.StatusInternalServerError, "Failed to delete invoice") + return nil + } + if rowsAffected == 0 { + response.NotFoundError(c, http.StatusNotFound, "Invoice not found") + return nil + } + response.Ok(c, "Delete Success", nil) + return nil +} diff --git a/sqlc_gen/alternative_component.sql.go b/sqlc_gen/alternative_component.sql.go new file mode 100644 index 0000000..dca26fe --- /dev/null +++ b/sqlc_gen/alternative_component.sql.go @@ -0,0 +1,166 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: alternative_component.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createAlternativeComponent = `-- name: CreateAlternativeComponent :one +INSERT INTO alternative_components (invoice_config_item_id, alternative_component_id, conversion_ratio, priority, note, metadata) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6 +) +RETURNING id, invoice_config_item_id, alternative_component_id, conversion_ratio, priority, note, metadata +` + +type CreateAlternativeComponentParams struct { + InvoiceConfigItemID int64 `db:"invoice_config_item_id" json:"invoiceConfigItemId"` + AlternativeComponentID int64 `db:"alternative_component_id" json:"alternativeComponentId"` + ConversionRatio pgtype.Numeric `db:"conversion_ratio" json:"conversionRatio"` + Priority int32 `db:"priority" json:"priority"` + Note pgtype.Text `db:"note" json:"note"` + Metadata []byte `db:"metadata" json:"metadata"` +} + +func (q *Queries) CreateAlternativeComponent(ctx context.Context, arg CreateAlternativeComponentParams) (AlternativeComponent, error) { + row := q.db.QueryRow(ctx, createAlternativeComponent, + arg.InvoiceConfigItemID, + arg.AlternativeComponentID, + arg.ConversionRatio, + arg.Priority, + arg.Note, + arg.Metadata, + ) + var i AlternativeComponent + err := row.Scan( + &i.ID, + &i.InvoiceConfigItemID, + &i.AlternativeComponentID, + &i.ConversionRatio, + &i.Priority, + &i.Note, + &i.Metadata, + ) + return i, err +} + +const deleteAlternativeComponent = `-- name: DeleteAlternativeComponent :execrows +DELETE FROM alternative_components +WHERE id = $1 +` + +func (q *Queries) DeleteAlternativeComponent(ctx context.Context, id int64) (int64, error) { + result, err := q.db.Exec(ctx, deleteAlternativeComponent, id) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + +const getAlternativeComponentByID = `-- name: GetAlternativeComponentByID :one +SELECT id, invoice_config_item_id, alternative_component_id, conversion_ratio, priority, note, metadata FROM alternative_components +WHERE id = $1 +` + +func (q *Queries) GetAlternativeComponentByID(ctx context.Context, id int64) (AlternativeComponent, error) { + row := q.db.QueryRow(ctx, getAlternativeComponentByID, id) + var i AlternativeComponent + err := row.Scan( + &i.ID, + &i.InvoiceConfigItemID, + &i.AlternativeComponentID, + &i.ConversionRatio, + &i.Priority, + &i.Note, + &i.Metadata, + ) + return i, err +} + +const listAlternativeComponents = `-- name: ListAlternativeComponents :many +SELECT id, invoice_config_item_id, alternative_component_id, conversion_ratio, priority, note, metadata FROM alternative_components +` + +func (q *Queries) ListAlternativeComponents(ctx context.Context) ([]AlternativeComponent, error) { + rows, err := q.db.Query(ctx, listAlternativeComponents) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AlternativeComponent + for rows.Next() { + var i AlternativeComponent + if err := rows.Scan( + &i.ID, + &i.InvoiceConfigItemID, + &i.AlternativeComponentID, + &i.ConversionRatio, + &i.Priority, + &i.Note, + &i.Metadata, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateAlternativeComponent = `-- name: UpdateAlternativeComponent :one +UPDATE alternative_components +SET invoice_config_item_id = coalesce($1, invoice_config_item_id), + alternative_component_id = coalesce($2, alternative_component_id), + conversion_ratio = coalesce($3, conversion_ratio), + priority = coalesce($4, priority), + note = coalesce($5, note), + metadata = coalesce($6, metadata) +WHERE id = $7 +RETURNING id, invoice_config_item_id, alternative_component_id, conversion_ratio, priority, note, metadata +` + +type UpdateAlternativeComponentParams struct { + InvoiceConfigItemID int64 `db:"invoice_config_item_id" json:"invoiceConfigItemId"` + AlternativeComponentID int64 `db:"alternative_component_id" json:"alternativeComponentId"` + ConversionRatio pgtype.Numeric `db:"conversion_ratio" json:"conversionRatio"` + Priority int32 `db:"priority" json:"priority"` + Note pgtype.Text `db:"note" json:"note"` + Metadata []byte `db:"metadata" json:"metadata"` + ID int64 `db:"id" json:"id"` +} + +func (q *Queries) UpdateAlternativeComponent(ctx context.Context, arg UpdateAlternativeComponentParams) (AlternativeComponent, error) { + row := q.db.QueryRow(ctx, updateAlternativeComponent, + arg.InvoiceConfigItemID, + arg.AlternativeComponentID, + arg.ConversionRatio, + arg.Priority, + arg.Note, + arg.Metadata, + arg.ID, + ) + var i AlternativeComponent + err := row.Scan( + &i.ID, + &i.InvoiceConfigItemID, + &i.AlternativeComponentID, + &i.ConversionRatio, + &i.Priority, + &i.Note, + &i.Metadata, + ) + return i, err +} diff --git a/sqlc_gen/invoice.sql.go b/sqlc_gen/invoice.sql.go new file mode 100644 index 0000000..c8d670d --- /dev/null +++ b/sqlc_gen/invoice.sql.go @@ -0,0 +1,204 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: invoice.sql + +package db + +import ( + "context" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createInvoice = `-- name: CreateInvoice :one +INSERT INTO invoices (type, status, invoice_config_id, total_items, note, created_by, approved_by, created_at, metadata) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9 +) +RETURNING id, invoice_code, type, status, invoice_config_id, total_items, note, created_by, approved_by, completed_at, created_at, updated_at, metadata +` + +type CreateInvoiceParams struct { + Type InvoiceTypeEnum `db:"type" json:"type"` + Status InvoiceStatusEnum `db:"status" json:"status"` + InvoiceConfigID pgtype.Int8 `db:"invoice_config_id" json:"invoiceConfigId"` + TotalItems int32 `db:"total_items" json:"totalItems"` + Note pgtype.Text `db:"note" json:"note"` + CreatedBy pgtype.Text `db:"created_by" json:"createdBy"` + ApprovedBy pgtype.Text `db:"approved_by" json:"approvedBy"` + CreatedAt time.Time `db:"created_at" json:"createdAt"` + Metadata []byte `db:"metadata" json:"metadata"` +} + +func (q *Queries) CreateInvoice(ctx context.Context, arg CreateInvoiceParams) (Invoice, error) { + row := q.db.QueryRow(ctx, createInvoice, + arg.Type, + arg.Status, + arg.InvoiceConfigID, + arg.TotalItems, + arg.Note, + arg.CreatedBy, + arg.ApprovedBy, + arg.CreatedAt, + arg.Metadata, + ) + var i Invoice + err := row.Scan( + &i.ID, + &i.InvoiceCode, + &i.Type, + &i.Status, + &i.InvoiceConfigID, + &i.TotalItems, + &i.Note, + &i.CreatedBy, + &i.ApprovedBy, + &i.CompletedAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.Metadata, + ) + return i, err +} + +const deleteInvoice = `-- name: DeleteInvoice :execrows +DELETE FROM invoices +WHERE id = $1 +` + +func (q *Queries) DeleteInvoice(ctx context.Context, id int64) (int64, error) { + result, err := q.db.Exec(ctx, deleteInvoice, id) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + +const getInvoiceByID = `-- name: GetInvoiceByID :one +SELECT id, invoice_code, type, status, invoice_config_id, total_items, note, created_by, approved_by, completed_at, created_at, updated_at, metadata FROM invoices +WHERE id = $1 +` + +func (q *Queries) GetInvoiceByID(ctx context.Context, id int64) (Invoice, error) { + row := q.db.QueryRow(ctx, getInvoiceByID, id) + var i Invoice + err := row.Scan( + &i.ID, + &i.InvoiceCode, + &i.Type, + &i.Status, + &i.InvoiceConfigID, + &i.TotalItems, + &i.Note, + &i.CreatedBy, + &i.ApprovedBy, + &i.CompletedAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.Metadata, + ) + return i, err +} + +const listInvoices = `-- name: ListInvoices :many +SELECT id, invoice_code, type, status, invoice_config_id, total_items, note, created_by, approved_by, completed_at, created_at, updated_at, metadata FROM invoices +ORDER BY created_at DESC +` + +func (q *Queries) ListInvoices(ctx context.Context) ([]Invoice, error) { + rows, err := q.db.Query(ctx, listInvoices) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Invoice + for rows.Next() { + var i Invoice + if err := rows.Scan( + &i.ID, + &i.InvoiceCode, + &i.Type, + &i.Status, + &i.InvoiceConfigID, + &i.TotalItems, + &i.Note, + &i.CreatedBy, + &i.ApprovedBy, + &i.CompletedAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.Metadata, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateInvoice = `-- name: UpdateInvoice :one +UPDATE invoices +SET type = coalesce($1, type), + status = coalesce($2, status), + invoice_config_id = coalesce($3, invoice_config_id), + total_items = coalesce($4, total_items), + note = coalesce($5, note), + metadata = coalesce($6, metadata), + updated_at = $7 +WHERE id = $8 +RETURNING id, invoice_code, type, status, invoice_config_id, total_items, note, created_by, approved_by, completed_at, created_at, updated_at, metadata +` + +type UpdateInvoiceParams struct { + Type InvoiceTypeEnum `db:"type" json:"type"` + Status InvoiceStatusEnum `db:"status" json:"status"` + InvoiceConfigID pgtype.Int8 `db:"invoice_config_id" json:"invoiceConfigId"` + TotalItems int32 `db:"total_items" json:"totalItems"` + Note pgtype.Text `db:"note" json:"note"` + Metadata []byte `db:"metadata" json:"metadata"` + UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` + ID int64 `db:"id" json:"id"` +} + +func (q *Queries) UpdateInvoice(ctx context.Context, arg UpdateInvoiceParams) (Invoice, error) { + row := q.db.QueryRow(ctx, updateInvoice, + arg.Type, + arg.Status, + arg.InvoiceConfigID, + arg.TotalItems, + arg.Note, + arg.Metadata, + arg.UpdatedAt, + arg.ID, + ) + var i Invoice + err := row.Scan( + &i.ID, + &i.InvoiceCode, + &i.Type, + &i.Status, + &i.InvoiceConfigID, + &i.TotalItems, + &i.Note, + &i.CreatedBy, + &i.ApprovedBy, + &i.CompletedAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.Metadata, + ) + return i, err +} diff --git a/sqlc_gen/querier.go b/sqlc_gen/querier.go index bea25a5..f77ac41 100644 --- a/sqlc_gen/querier.go +++ b/sqlc_gen/querier.go @@ -13,6 +13,7 @@ import ( type Querier interface { AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error) CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error) + CreateAlternativeComponent(ctx context.Context, arg CreateAlternativeComponentParams) (AlternativeComponent, error) CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error) CreateComponent(ctx context.Context, arg CreateComponentParams) (Component, error) CreateComponentCode(ctx context.Context, arg CreateComponentCodeParams) (ComponentCode, error) @@ -20,6 +21,7 @@ type Querier interface { CreateComponentStatusHistory(ctx context.Context, arg CreateComponentStatusHistoryParams) (ComponentStatusHistory, error) CreateComponentType(ctx context.Context, arg CreateComponentTypeParams) (ComponentType, error) CreateContainer(ctx context.Context, arg CreateContainerParams) (Container, error) + CreateInvoice(ctx context.Context, arg CreateInvoiceParams) (Invoice, error) CreateInvoiceConfig(ctx context.Context, arg CreateInvoiceConfigParams) (InvoiceConfig, error) CreateInvoiceConfigItem(ctx context.Context, arg CreateInvoiceConfigItemParams) (InvoiceConfigItem, error) CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error) @@ -27,12 +29,14 @@ type Querier interface { CreateShelve(ctx context.Context, arg CreateShelveParams) (Shelf, error) CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error) CreateWarehouse(ctx context.Context, arg CreateWarehouseParams) (Warehouse, error) + DeleteAlternativeComponent(ctx context.Context, id int64) (int64, error) DeleteCabinet(ctx context.Context, id int64) (int64, error) DeleteComponent(ctx context.Context, id int64) (int64, error) DeleteComponentCode(ctx context.Context, id int64) (int64, error) DeleteComponentItem(ctx context.Context, id int64) (int64, error) DeleteComponentType(ctx context.Context, id int64) (int64, error) DeleteContainer(ctx context.Context, id int64) (int64, error) + DeleteInvoice(ctx context.Context, id int64) (int64, error) DeleteInvoiceConfig(ctx context.Context, id int64) (int64, error) DeleteInvoiceConfigItem(ctx context.Context, id int64) (int64, error) DeleteRole(ctx context.Context, id uuid.UUID) (int64, error) @@ -40,6 +44,7 @@ type Querier interface { DeleteShelve(ctx context.Context, id int64) (int64, error) DeleteWarehouse(ctx context.Context, id int64) (int64, error) FindComponentItem(ctx context.Context, componentid int64) ([]FindComponentItemRow, error) + GetAlternativeComponentByID(ctx context.Context, id int64) (AlternativeComponent, error) GetCabinetByID(ctx context.Context, id int64) (Cabinet, error) GetComponentByID(ctx context.Context, id int64) (Component, error) GetComponentCodeByID(ctx context.Context, id int64) (ComponentCode, error) @@ -47,6 +52,7 @@ type Querier interface { GetComponentItemByID(ctx context.Context, id int64) (ComponentItem, error) GetComponentTypeByID(ctx context.Context, id int64) (ComponentType, error) GetContainerByID(ctx context.Context, id int64) (Container, error) + GetInvoiceByID(ctx context.Context, id int64) (Invoice, error) GetInvoiceConfigByID(ctx context.Context, id int64) (InvoiceConfig, error) GetInvoiceConfigItemByID(ctx context.Context, id int64) (InvoiceConfigItem, error) GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error) @@ -59,6 +65,7 @@ type Querier interface { GetUserRolesByRoleID(ctx context.Context, roleID uuid.UUID) ([]GetUserRolesByRoleIDRow, error) GetUserRolesByUserID(ctx context.Context, userID uuid.UUID) ([]GetUserRolesByUserIDRow, error) GetWarehouseByID(ctx context.Context, id int64) (Warehouse, error) + ListAlternativeComponents(ctx context.Context) ([]AlternativeComponent, error) ListCabinets(ctx context.Context) ([]Cabinet, error) ListComponentCodes(ctx context.Context) ([]ComponentCode, error) ListComponentItems(ctx context.Context) ([]ComponentItem, error) @@ -67,12 +74,14 @@ type Querier interface { ListContainers(ctx context.Context) ([]Container, error) ListInvoiceConfigItems(ctx context.Context) ([]InvoiceConfigItem, error) ListInvoiceConfigs(ctx context.Context) ([]InvoiceConfig, error) + ListInvoices(ctx context.Context) ([]Invoice, error) ListRoles(ctx context.Context) ([]Role, error) ListRooms(ctx context.Context) ([]Room, error) ListShelves(ctx context.Context) ([]Shelf, error) ListWarehouses(ctx context.Context) ([]Warehouse, error) RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error RemoveRoleFromUser(ctx context.Context, arg RemoveRoleFromUserParams) error + UpdateAlternativeComponent(ctx context.Context, arg UpdateAlternativeComponentParams) (AlternativeComponent, error) UpdateCabinet(ctx context.Context, arg UpdateCabinetParams) (Cabinet, error) UpdateComponent(ctx context.Context, arg UpdateComponentParams) (Component, error) UpdateComponentCode(ctx context.Context, arg UpdateComponentCodeParams) (ComponentCode, error) @@ -81,6 +90,7 @@ type Querier interface { UpdateComponentItemStatus(ctx context.Context, arg UpdateComponentItemStatusParams) (ComponentItem, error) UpdateComponentType(ctx context.Context, arg UpdateComponentTypeParams) (ComponentType, error) UpdateContainer(ctx context.Context, arg UpdateContainerParams) (Container, error) + UpdateInvoice(ctx context.Context, arg UpdateInvoiceParams) (Invoice, error) UpdateInvoiceConfig(ctx context.Context, arg UpdateInvoiceConfigParams) (InvoiceConfig, error) UpdateInvoiceConfigItem(ctx context.Context, arg UpdateInvoiceConfigItemParams) (InvoiceConfigItem, error) UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error)