package service import ( "context" "time" "apskel-pos-be/internal/appcontext" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" "apskel-pos-be/internal/models" "apskel-pos-be/internal/processor" "apskel-pos-be/internal/transformer" "github.com/google/uuid" ) type InventoryService interface { CreateInventory(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateInventoryRequest) *contract.Response UpdateInventory(ctx context.Context, id uuid.UUID, req *contract.UpdateInventoryRequest) *contract.Response DeleteInventory(ctx context.Context, id uuid.UUID) *contract.Response GetInventoryByID(ctx context.Context, id uuid.UUID) *contract.Response ListInventory(ctx context.Context, req *contract.ListInventoryRequest) *contract.Response AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response GetLowStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response GetZeroStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response GetInventoryReportSummary(ctx context.Context, outletID, organizationID uuid.UUID) (*contract.InventoryReportSummaryResponse, error) GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter, organizationID uuid.UUID) (*contract.InventoryReportDetailResponse, error) } type InventoryServiceImpl struct { inventoryProcessor processor.InventoryProcessor } func NewInventoryService(inventoryProcessor processor.InventoryProcessor) *InventoryServiceImpl { return &InventoryServiceImpl{ inventoryProcessor: inventoryProcessor, } } func (s *InventoryServiceImpl) CreateInventory(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateInventoryRequest) *contract.Response { modelReq := transformer.CreateInventoryRequestToModel(req) inventoryResponse, err := s.inventoryProcessor.Create(ctx, modelReq, apctx.OrganizationID) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } contractResponse := transformer.InventoryModelResponseToResponse(inventoryResponse) return contract.BuildSuccessResponse(contractResponse) } func (s *InventoryServiceImpl) UpdateInventory(ctx context.Context, id uuid.UUID, req *contract.UpdateInventoryRequest) *contract.Response { modelReq := transformer.UpdateInventoryRequestToModel(req) inventoryResponse, err := s.inventoryProcessor.Update(ctx, id, modelReq, uuid.Nil) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } contractResponse := transformer.InventoryModelResponseToResponse(inventoryResponse) return contract.BuildSuccessResponse(contractResponse) } func (s *InventoryServiceImpl) DeleteInventory(ctx context.Context, id uuid.UUID) *contract.Response { err := s.inventoryProcessor.Delete(ctx, id, uuid.Nil) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(map[string]interface{}{ "message": "Inventory deleted successfully", }) } func (s *InventoryServiceImpl) GetInventoryByID(ctx context.Context, id uuid.UUID) *contract.Response { inventoryResponse, err := s.inventoryProcessor.GetByID(ctx, id, uuid.Nil) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } contractResponse := transformer.InventoryModelResponseToResponse(inventoryResponse) return contract.BuildSuccessResponse(contractResponse) } func (s *InventoryServiceImpl) ListInventory(ctx context.Context, req *contract.ListInventoryRequest) *contract.Response { // Build filters filters := make(map[string]interface{}) if req.OutletID != nil { filters["outlet_id"] = *req.OutletID } if req.ProductID != nil { filters["product_id"] = *req.ProductID } if req.CategoryID != nil { filters["category_id"] = *req.CategoryID } if req.LowStockOnly != nil && *req.LowStockOnly { filters["low_stock"] = true } if req.ZeroStockOnly != nil && *req.ZeroStockOnly { filters["zero_stock"] = true } if req.Search != "" { filters["search"] = req.Search } inventory, totalCount, err := s.inventoryProcessor.List(ctx, filters, req.Limit, req.Page, uuid.Nil) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } // Convert from []*models.InventoryResponse to []models.InventoryResponse var inventoryResponses []models.InventoryResponse for _, inv := range inventory { inventoryResponses = append(inventoryResponses, *inv) } // Convert to contract responses contractResponses := transformer.InventoryToResponses(inventoryResponses) // Calculate total pages totalPages := int(totalCount) / req.Limit if int(totalCount)%req.Limit > 0 { totalPages++ } listResponse := &contract.ListInventoryResponse{ Inventory: contractResponses, TotalCount: int(totalCount), Page: req.Page, Limit: req.Limit, TotalPages: totalPages, } return contract.BuildSuccessResponse(listResponse) } func (s *InventoryServiceImpl) AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response { modelReq := transformer.AdjustInventoryRequestToModel(req) inventoryResponse, err := s.inventoryProcessor.AdjustQuantity(ctx, modelReq.ProductID, modelReq.OutletID, uuid.Nil, modelReq.Delta) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } contractResponse := transformer.InventoryModelResponseToResponse(inventoryResponse) return contract.BuildSuccessResponse(contractResponse) } func (s *InventoryServiceImpl) GetLowStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response { inventory, err := s.inventoryProcessor.GetLowStock(ctx, outletID, uuid.Nil) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } // Convert from []*models.InventoryResponse to []models.InventoryResponse var inventoryResponses []models.InventoryResponse for _, inv := range inventory { inventoryResponses = append(inventoryResponses, *inv) } contractResponses := transformer.InventoryToResponses(inventoryResponses) return contract.BuildSuccessResponse(contractResponses) } func (s *InventoryServiceImpl) GetZeroStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response { inventory, err := s.inventoryProcessor.GetZeroStock(ctx, outletID, uuid.Nil) // TODO: Get organizationID from context if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } // Convert from []*models.InventoryResponse to []models.InventoryResponse var inventoryResponses []models.InventoryResponse for _, inv := range inventory { inventoryResponses = append(inventoryResponses, *inv) } contractResponses := transformer.InventoryToResponses(inventoryResponses) return contract.BuildSuccessResponse(contractResponses) } // GetInventoryReportSummary returns summary statistics for inventory report func (s *InventoryServiceImpl) GetInventoryReportSummary(ctx context.Context, outletID, organizationID uuid.UUID) (*contract.InventoryReportSummaryResponse, error) { summary, err := s.inventoryProcessor.GetInventoryReportSummary(ctx, outletID, organizationID) if err != nil { return nil, err } return &contract.InventoryReportSummaryResponse{ TotalProducts: summary.TotalProducts, TotalIngredients: summary.TotalIngredients, TotalValue: summary.TotalValue, LowStockProducts: summary.LowStockProducts, LowStockIngredients: summary.LowStockIngredients, ZeroStockProducts: summary.ZeroStockProducts, ZeroStockIngredients: summary.ZeroStockIngredients, OutletID: summary.OutletID.String(), OutletName: summary.OutletName, GeneratedAt: summary.GeneratedAt.Format(time.RFC3339), }, nil } // GetInventoryReportDetails returns detailed inventory report with products and ingredients func (s *InventoryServiceImpl) GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter, organizationID uuid.UUID) (*contract.InventoryReportDetailResponse, error) { report, err := s.inventoryProcessor.GetInventoryReportDetails(ctx, filter, organizationID) if err != nil { return nil, err } response := &contract.InventoryReportDetailResponse{} // Transform summary if report.Summary != nil { response.Summary = &contract.InventoryReportSummaryResponse{ TotalProducts: report.Summary.TotalProducts, TotalIngredients: report.Summary.TotalIngredients, TotalValue: report.Summary.TotalValue, LowStockProducts: report.Summary.LowStockProducts, LowStockIngredients: report.Summary.LowStockIngredients, ZeroStockProducts: report.Summary.ZeroStockProducts, ZeroStockIngredients: report.Summary.ZeroStockIngredients, OutletID: report.Summary.OutletID.String(), OutletName: report.Summary.OutletName, GeneratedAt: report.Summary.GeneratedAt.Format(time.RFC3339), } } // Transform products response.Products = make([]*contract.InventoryProductDetailResponse, len(report.Products)) for i, product := range report.Products { response.Products[i] = &contract.InventoryProductDetailResponse{ ID: product.ID.String(), ProductID: product.ProductID.String(), ProductName: product.ProductName, CategoryName: product.CategoryName, Quantity: product.Quantity, ReorderLevel: product.ReorderLevel, UnitCost: product.UnitCost, TotalValue: product.TotalValue, IsLowStock: product.IsLowStock, IsZeroStock: product.IsZeroStock, UpdatedAt: product.UpdatedAt.Format(time.RFC3339), } } // Transform ingredients response.Ingredients = make([]*contract.InventoryIngredientDetailResponse, len(report.Ingredients)) for i, ingredient := range report.Ingredients { response.Ingredients[i] = &contract.InventoryIngredientDetailResponse{ ID: ingredient.ID.String(), IngredientID: ingredient.IngredientID.String(), IngredientName: ingredient.IngredientName, UnitName: ingredient.UnitName, Quantity: ingredient.Quantity, ReorderLevel: ingredient.ReorderLevel, UnitCost: ingredient.UnitCost, TotalValue: ingredient.TotalValue, IsLowStock: ingredient.IsLowStock, IsZeroStock: ingredient.IsZeroStock, UpdatedAt: ingredient.UpdatedAt.Format(time.RFC3339), } } return response, nil }