274 lines
11 KiB
Go
274 lines
11 KiB
Go
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
|
|
}
|