From 022a6e4caa9f26d2a6ff5f933102b28c756a6e5d Mon Sep 17 00:00:00 2001 From: Natthapol SERMSARAN Date: Tue, 1 Apr 2025 15:17:39 +0700 Subject: [PATCH] feat: update inventory API for status, category and harvest relations --- backend/internal/api/inventory.go | 346 ++++++++++++++++++++---------- 1 file changed, 230 insertions(+), 116 deletions(-) diff --git a/backend/internal/api/inventory.go b/backend/internal/api/inventory.go index 78c95b4..17e7eca 100644 --- a/backend/internal/api/inventory.go +++ b/backend/internal/api/inventory.go @@ -48,32 +48,66 @@ func (a *api) registerInventoryRoutes(_ chi.Router, api huma.API) { Path: prefix + "/{id}", Tags: tags, }, a.deleteInventoryItemHandler) + + huma.Register(api, huma.Operation{ + OperationID: "getInventoryStatus", + Method: http.MethodGet, + Path: prefix + "/status", + Tags: tags, + }, a.getInventoryStatusHandler) + + huma.Register(api, huma.Operation{ + OperationID: "getInventoryCategory", + Method: http.MethodGet, + Path: prefix + "/category", + Tags: tags, + }, a.getInventoryCategoryHandler) + + huma.Register(api, huma.Operation{ + OperationID: "getHarvestUnits", + Method: http.MethodGet, + Path: "/harvest/units", + Tags: []string{"harvest"}, + }, a.getHarvestUnitsHandler) } type InventoryItemResponse struct { - ID string `json:"id"` - Name string `json:"name"` - Category string `json:"category"` - Type string `json:"type"` - Quantity float64 `json:"quantity"` - Unit string `json:"unit"` - DateAdded time.Time `json:"date_added"` - Status string `json:"status"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Category InventoryCategory `json:"category"` + Quantity float64 `json:"quantity"` + Unit HarvestUnit `json:"unit"` + DateAdded time.Time `json:"date_added"` + Status InventoryStatus `json:"status"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` +} + +type InventoryStatus struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type InventoryCategory struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type HarvestUnit struct { + ID int `json:"id"` + Name string `json:"name"` } type CreateInventoryItemInput struct { Header string `header:"Authorization" required:"true" example:"Bearer token"` UserID string `header:"user_id" required:"true" example:"user-uuid"` Body struct { - Name string `json:"name" required:"true"` - Category string `json:"category" required:"true"` - Type string `json:"type" required:"true"` - Quantity float64 `json:"quantity" required:"true"` - Unit string `json:"unit" required:"true"` - DateAdded time.Time `json:"date_added" required:"true"` - Status string `json:"status" required:"true" enum:"In Stock,Low Stock,Out of Stock"` + Name string `json:"name" required:"true"` + CategoryID int `json:"category_id" required:"true"` + Quantity float64 `json:"quantity" required:"true"` + UnitID int `json:"unit_id" required:"true"` + DateAdded time.Time `json:"date_added" required:"true"` + StatusID int `json:"status_id" required:"true"` } } @@ -83,16 +117,83 @@ type CreateInventoryItemOutput struct { } } +type UpdateInventoryItemInput struct { + Header string `header:"Authorization" required:"true" example:"Bearer token"` + UserID string `header:"user_id" required:"true" example:"user-uuid"` + ID string `path:"id"` + Body struct { + Name string `json:"name"` + CategoryID int `json:"category_id"` + Quantity float64 `json:"quantity"` + UnitID int `json:"unit_id"` + DateAdded time.Time `json:"date_added"` + StatusID int `json:"status_id"` + } +} + +type UpdateInventoryItemOutput struct { + Body InventoryItemResponse +} + +type GetInventoryItemsInput struct { + Header string `header:"Authorization" required:"true" example:"Bearer token"` + UserID string `header:"user_id" required:"true" example:"user-uuid"` + CategoryID int `query:"category_id"` + StatusID int `query:"status_id"` + StartDate time.Time `query:"start_date" format:"date-time"` + EndDate time.Time `query:"end_date" format:"date-time"` + SearchQuery string `query:"search"` + SortBy string `query:"sort_by" enum:"name,quantity,date_added,created_at"` + SortOrder string `query:"sort_order" enum:"asc,desc" default:"desc"` +} + +type GetInventoryItemsOutput struct { + Body []InventoryItemResponse +} + +type GetInventoryItemInput struct { + Header string `header:"Authorization" required:"true" example:"Bearer token"` + UserID string `header:"user_id" required:"true" example:"user-uuid"` + ID string `path:"id"` +} + +type GetInventoryItemOutput struct { + Body InventoryItemResponse +} + +type DeleteInventoryItemInput struct { + Header string `header:"Authorization" required:"true" example:"Bearer token"` + UserID string `header:"user_id" required:"true" example:"user-uuid"` + ID string `path:"id"` +} + +type DeleteInventoryItemOutput struct { + Body struct { + Message string `json:"message"` + } +} + +type GetInventoryStatusOutput struct { + Body []InventoryStatus +} + +type GetInventoryCategoryOutput struct { + Body []InventoryCategory +} + +type GetHarvestUnitsOutput struct { + Body []HarvestUnit +} + func (a *api) createInventoryItemHandler(ctx context.Context, input *CreateInventoryItemInput) (*CreateInventoryItemOutput, error) { item := &domain.InventoryItem{ - UserID: input.UserID, - Name: input.Body.Name, - Category: input.Body.Category, - Type: input.Body.Type, - Quantity: input.Body.Quantity, - Unit: input.Body.Unit, - DateAdded: input.Body.DateAdded, - Status: domain.InventoryStatus(input.Body.Status), + UserID: input.UserID, + Name: input.Body.Name, + CategoryID: input.Body.CategoryID, + Quantity: input.Body.Quantity, + UnitID: input.Body.UnitID, + DateAdded: input.Body.DateAdded, + StatusID: input.Body.StatusID, } if err := item.Validate(); err != nil { @@ -109,29 +210,11 @@ func (a *api) createInventoryItemHandler(ctx context.Context, input *CreateInven }{ID: item.ID}}, nil } -type GetInventoryItemsInput struct { - Header string `header:"Authorization" required:"true" example:"Bearer token"` - UserID string `header:"user_id" required:"true" example:"user-uuid"` - Category string `query:"category"` - Type string `query:"type"` - Status string `query:"status" enum:"In Stock,Low Stock,Out of Stock"` - StartDate time.Time `query:"start_date" format:"date-time"` - EndDate time.Time `query:"end_date" format:"date-time"` - SearchQuery string `query:"search"` - SortBy string `query:"sort_by" enum:"name,category,type,quantity,date_added,status,created_at"` - SortOrder string `query:"sort_order" enum:"asc,desc" default:"desc"` -} - -type GetInventoryItemsOutput struct { - Body []InventoryItemResponse -} - func (a *api) getInventoryItemsByUserHandler(ctx context.Context, input *GetInventoryItemsInput) (*GetInventoryItemsOutput, error) { filter := domain.InventoryFilter{ UserID: input.UserID, - Category: input.Category, - Type: input.Type, - Status: domain.InventoryStatus(input.Status), + CategoryID: input.CategoryID, + StatusID: input.StatusID, StartDate: input.StartDate, EndDate: input.EndDate, SearchQuery: input.SearchQuery, @@ -150,14 +233,22 @@ func (a *api) getInventoryItemsByUserHandler(ctx context.Context, input *GetInve response := make([]InventoryItemResponse, len(items)) for i, item := range items { response[i] = InventoryItemResponse{ - ID: item.ID, - Name: item.Name, - Category: item.Category, - Type: item.Type, - Quantity: item.Quantity, - Unit: item.Unit, + ID: item.ID, + Name: item.Name, + Category: InventoryCategory{ + ID: item.Category.ID, + Name: item.Category.Name, + }, + Quantity: item.Quantity, + Unit: HarvestUnit{ + ID: item.Unit.ID, + Name: item.Unit.Name, + }, DateAdded: item.DateAdded, - Status: string(item.Status), + Status: InventoryStatus{ + ID: item.Status.ID, + Name: item.Status.Name, + }, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, } @@ -166,16 +257,6 @@ func (a *api) getInventoryItemsByUserHandler(ctx context.Context, input *GetInve return &GetInventoryItemsOutput{Body: response}, nil } -type GetInventoryItemInput struct { - Header string `header:"Authorization" required:"true" example:"Bearer token"` - UserID string `header:"user_id" required:"true" example:"user-uuid"` - ID string `path:"id"` -} - -type GetInventoryItemOutput struct { - Body InventoryItemResponse -} - func (a *api) getInventoryItemHandler(ctx context.Context, input *GetInventoryItemInput) (*GetInventoryItemOutput, error) { item, err := a.inventoryRepo.GetByID(ctx, input.ID, input.UserID) if err != nil { @@ -183,38 +264,27 @@ func (a *api) getInventoryItemHandler(ctx context.Context, input *GetInventoryIt } return &GetInventoryItemOutput{Body: InventoryItemResponse{ - ID: item.ID, - Name: item.Name, - Category: item.Category, - Type: item.Type, - Quantity: item.Quantity, - Unit: item.Unit, + ID: item.ID, + Name: item.Name, + Category: InventoryCategory{ + ID: item.Category.ID, + Name: item.Category.Name, + }, + Quantity: item.Quantity, + Unit: HarvestUnit{ + ID: item.Unit.ID, + Name: item.Unit.Name, + }, DateAdded: item.DateAdded, - Status: string(item.Status), + Status: InventoryStatus{ + ID: item.Status.ID, + Name: item.Status.Name, + }, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, }}, nil } -type UpdateInventoryItemInput struct { - Header string `header:"Authorization" required:"true" example:"Bearer token"` - UserID string `header:"user_id" required:"true" example:"user-uuid"` - ID string `path:"id"` - Body struct { - Name string `json:"name"` - Category string `json:"category"` - Type string `json:"type"` - Quantity float64 `json:"quantity"` - Unit string `json:"unit"` - DateAdded time.Time `json:"date_added"` - Status string `json:"status" enum:"In Stock,Low Stock,Out of Stock"` - } -} - -type UpdateInventoryItemOutput struct { - Body InventoryItemResponse -} - func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInventoryItemInput) (*UpdateInventoryItemOutput, error) { item, err := a.inventoryRepo.GetByID(ctx, input.ID, input.UserID) if err != nil { @@ -224,23 +294,20 @@ func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInven if input.Body.Name != "" { item.Name = input.Body.Name } - if input.Body.Category != "" { - item.Category = input.Body.Category - } - if input.Body.Type != "" { - item.Type = input.Body.Type + if input.Body.CategoryID != 0 { + item.CategoryID = input.Body.CategoryID } if input.Body.Quantity != 0 { item.Quantity = input.Body.Quantity } - if input.Body.Unit != "" { - item.Unit = input.Body.Unit + if input.Body.UnitID != 0 { + item.UnitID = input.Body.UnitID } if !input.Body.DateAdded.IsZero() { item.DateAdded = input.Body.DateAdded } - if input.Body.Status != "" { - item.Status = domain.InventoryStatus(input.Body.Status) + if input.Body.StatusID != 0 { + item.StatusID = input.Body.StatusID } if err := item.Validate(); err != nil { @@ -258,31 +325,27 @@ func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInven } return &UpdateInventoryItemOutput{Body: InventoryItemResponse{ - ID: updatedItem.ID, - Name: updatedItem.Name, - Category: updatedItem.Category, - Type: updatedItem.Type, - Quantity: updatedItem.Quantity, - Unit: updatedItem.Unit, + ID: updatedItem.ID, + Name: updatedItem.Name, + Category: InventoryCategory{ + ID: updatedItem.Category.ID, + Name: updatedItem.Category.Name, + }, + Quantity: updatedItem.Quantity, + Unit: HarvestUnit{ + ID: updatedItem.Unit.ID, + Name: updatedItem.Unit.Name, + }, DateAdded: updatedItem.DateAdded, - Status: string(updatedItem.Status), + Status: InventoryStatus{ + ID: updatedItem.Status.ID, + Name: updatedItem.Status.Name, + }, CreatedAt: updatedItem.CreatedAt, UpdatedAt: updatedItem.UpdatedAt, }}, nil } -type DeleteInventoryItemInput struct { - Header string `header:"Authorization" required:"true" example:"Bearer token"` - UserID string `header:"user_id" required:"true" example:"user-uuid"` - ID string `path:"id"` -} - -type DeleteInventoryItemOutput struct { - Body struct { - Message string `json:"message"` - } -} - func (a *api) deleteInventoryItemHandler(ctx context.Context, input *DeleteInventoryItemInput) (*DeleteInventoryItemOutput, error) { err := a.inventoryRepo.Delete(ctx, input.ID, input.UserID) if err != nil { @@ -293,3 +356,54 @@ func (a *api) deleteInventoryItemHandler(ctx context.Context, input *DeleteInven Message string `json:"message"` }{Message: "Inventory item deleted successfully"}}, nil } + +func (a *api) getInventoryStatusHandler(ctx context.Context, input *struct{}) (*GetInventoryStatusOutput, error) { + statuses, err := a.inventoryRepo.GetStatuses(ctx) + if err != nil { + return nil, err + } + + response := make([]InventoryStatus, len(statuses)) + for i, status := range statuses { + response[i] = InventoryStatus{ + ID: status.ID, + Name: status.Name, + } + } + + return &GetInventoryStatusOutput{Body: response}, nil +} + +func (a *api) getInventoryCategoryHandler(ctx context.Context, input *struct{}) (*GetInventoryCategoryOutput, error) { + categories, err := a.inventoryRepo.GetCategories(ctx) + if err != nil { + return nil, err + } + + response := make([]InventoryCategory, len(categories)) + for i, category := range categories { + response[i] = InventoryCategory{ + ID: category.ID, + Name: category.Name, + } + } + + return &GetInventoryCategoryOutput{Body: response}, nil +} + +func (a *api) getHarvestUnitsHandler(ctx context.Context, input *struct{}) (*GetHarvestUnitsOutput, error) { + units, err := a.harvestRepo.GetUnits(ctx) + if err != nil { + return nil, err + } + + response := make([]HarvestUnit, len(units)) + for i, unit := range units { + response[i] = HarvestUnit{ + ID: unit.ID, + Name: unit.Name, + } + } + + return &GetHarvestUnitsOutput{Body: response}, nil +}