diff --git a/internal/contract/inventory_contract.go b/internal/contract/inventory_contract.go index ae8da7e..816c0a2 100644 --- a/internal/contract/inventory_contract.go +++ b/internal/contract/inventory_contract.go @@ -99,7 +99,8 @@ type InventoryProductDetailResponse struct { ReorderLevel int `json:"reorder_level"` UnitCost float64 `json:"unit_cost"` TotalValue float64 `json:"total_value"` - TotalSold float64 `json:"total_sold"` + TotalIn float64 `json:"total_in"` + TotalOut float64 `json:"total_out"` IsLowStock bool `json:"is_low_stock"` IsZeroStock bool `json:"is_zero_stock"` UpdatedAt string `json:"updated_at"` @@ -114,7 +115,8 @@ type InventoryIngredientDetailResponse struct { ReorderLevel int `json:"reorder_level"` UnitCost float64 `json:"unit_cost"` TotalValue float64 `json:"total_value"` - TotalSold float64 `json:"total_sold"` + TotalIn float64 `json:"total_in"` + TotalOut float64 `json:"total_out"` IsLowStock bool `json:"is_low_stock"` IsZeroStock bool `json:"is_zero_stock"` UpdatedAt string `json:"updated_at"` diff --git a/internal/models/inventory.go b/internal/models/inventory.go index ad98b19..500abfc 100644 --- a/internal/models/inventory.go +++ b/internal/models/inventory.go @@ -92,7 +92,8 @@ type InventoryProductDetail struct { ReorderLevel int `json:"reorder_level"` UnitCost float64 `json:"unit_cost"` TotalValue float64 `json:"total_value"` - TotalSold float64 `json:"total_sold"` + TotalIn float64 `json:"total_in"` + TotalOut float64 `json:"total_out"` IsLowStock bool `json:"is_low_stock"` IsZeroStock bool `json:"is_zero_stock"` UpdatedAt time.Time `json:"updated_at"` @@ -107,7 +108,8 @@ type InventoryIngredientDetail struct { ReorderLevel int `json:"reorder_level"` UnitCost float64 `json:"unit_cost"` TotalValue float64 `json:"total_value"` - TotalSold float64 `json:"total_sold"` + TotalIn float64 `json:"total_in"` + TotalOut float64 `json:"total_out"` IsLowStock bool `json:"is_low_stock"` IsZeroStock bool `json:"is_zero_stock"` UpdatedAt time.Time `json:"updated_at"` diff --git a/internal/repository/inventory_repository.go b/internal/repository/inventory_repository.go index dd2237e..36536f0 100644 --- a/internal/repository/inventory_repository.go +++ b/internal/repository/inventory_repository.go @@ -513,27 +513,39 @@ func (r *InventoryRepositoryImpl) getInventoryProductsDetails(ctx context.Contex return nil, err } - // Now get total sold for each product + // Now get total in and out for each product var products []*models.InventoryProductDetail for _, result := range baseResults { - var totalSold float64 + var totalIn, totalOut float64 - // Query total sold for this specific product - soldQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). + // Query total in (positive movements) for this specific product + inQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). + Select("COALESCE(SUM(quantity), 0)"). + Where("item_id = ? AND outlet_id = ? AND item_type = ? AND quantity > 0", + result.ProductID, filter.OutletID, "PRODUCT") + + // Query total out (negative movements) for this specific product + outQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). Select("COALESCE(SUM(ABS(quantity)), 0)"). - Where("item_id = ? AND outlet_id = ? AND item_type = ? AND movement_type = ? AND quantity < 0", - result.ProductID, filter.OutletID, "PRODUCT", entities.InventoryMovementTypeSale) + Where("item_id = ? AND outlet_id = ? AND item_type = ? AND quantity < 0", + result.ProductID, filter.OutletID, "PRODUCT") // Apply date filters if provided if filter.DateFrom != nil { - soldQuery = soldQuery.Where("created_at >= ?", *filter.DateFrom) + inQuery = inQuery.Where("created_at >= ?", *filter.DateFrom) + outQuery = outQuery.Where("created_at >= ?", *filter.DateFrom) } if filter.DateTo != nil { - soldQuery = soldQuery.Where("created_at <= ?", *filter.DateTo) + inQuery = inQuery.Where("created_at <= ?", *filter.DateTo) + outQuery = outQuery.Where("created_at <= ?", *filter.DateTo) } - if err := soldQuery.Scan(&totalSold).Error; err != nil { - return nil, fmt.Errorf("failed to get total sold for product %s: %w", result.ProductID, err) + if err := inQuery.Scan(&totalIn).Error; err != nil { + return nil, fmt.Errorf("failed to get total in for product %s: %w", result.ProductID, err) + } + + if err := outQuery.Scan(&totalOut).Error; err != nil { + return nil, fmt.Errorf("failed to get total out for product %s: %w", result.ProductID, err) } categoryName := "" @@ -550,7 +562,8 @@ func (r *InventoryRepositoryImpl) getInventoryProductsDetails(ctx context.Contex ReorderLevel: result.ReorderLevel, UnitCost: result.UnitCost, TotalValue: result.TotalValue, - TotalSold: totalSold, + TotalIn: totalIn, + TotalOut: totalOut, IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0, IsZeroStock: result.Quantity == 0, UpdatedAt: result.UpdatedAt, @@ -617,22 +630,35 @@ func (r *InventoryRepositoryImpl) getInventoryIngredientsDetails(ctx context.Con var ingredients []*models.InventoryIngredientDetail for _, result := range baseResults { - var totalSold float64 + var totalIn, totalOut float64 - soldQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). + // Query total in (positive movements) for this specific ingredient + inQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). + Select("COALESCE(SUM(quantity), 0)"). + Where("item_id = ? AND outlet_id = ? AND item_type = ? AND quantity > 0", + result.IngredientID, filter.OutletID, "INGREDIENT") + + // Query total out (negative movements) for this specific ingredient + outQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). Select("COALESCE(SUM(ABS(quantity)), 0)"). - Where("item_id = ? AND outlet_id = ? AND item_type = ? AND movement_type = ? AND quantity < 0", - result.IngredientID, filter.OutletID, "INGREDIENT", entities.InventoryMovementTypeSale) + Where("item_id = ? AND outlet_id = ? AND item_type = ? AND quantity < 0", + result.IngredientID, filter.OutletID, "INGREDIENT") if filter.DateFrom != nil { - soldQuery = soldQuery.Where("created_at >= ?", *filter.DateFrom) + inQuery = inQuery.Where("created_at >= ?", *filter.DateFrom) + outQuery = outQuery.Where("created_at >= ?", *filter.DateFrom) } if filter.DateTo != nil { - soldQuery = soldQuery.Where("created_at <= ?", *filter.DateTo) + inQuery = inQuery.Where("created_at <= ?", *filter.DateTo) + outQuery = outQuery.Where("created_at <= ?", *filter.DateTo) } - if err := soldQuery.Scan(&totalSold).Error; err != nil { - return nil, fmt.Errorf("failed to get total sold for ingredient %s: %w", result.IngredientID, err) + if err := inQuery.Scan(&totalIn).Error; err != nil { + return nil, fmt.Errorf("failed to get total in for ingredient %s: %w", result.IngredientID, err) + } + + if err := outQuery.Scan(&totalOut).Error; err != nil { + return nil, fmt.Errorf("failed to get total out for ingredient %s: %w", result.IngredientID, err) } unitName := "" @@ -649,7 +675,8 @@ func (r *InventoryRepositoryImpl) getInventoryIngredientsDetails(ctx context.Con ReorderLevel: result.ReorderLevel, UnitCost: result.UnitCost, TotalValue: result.TotalValue, - TotalSold: totalSold, + TotalIn: totalIn, + TotalOut: totalOut, IsLowStock: result.Quantity <= 0 && result.Quantity > 0, IsZeroStock: result.Quantity == 0, UpdatedAt: result.UpdatedAt, diff --git a/internal/service/inventory_service.go b/internal/service/inventory_service.go index 3040bf2..38c1470 100644 --- a/internal/service/inventory_service.go +++ b/internal/service/inventory_service.go @@ -247,7 +247,8 @@ func (s *InventoryServiceImpl) GetInventoryReportDetails(ctx context.Context, fi ReorderLevel: product.ReorderLevel, UnitCost: product.UnitCost, TotalValue: product.TotalValue, - TotalSold: product.TotalSold, + TotalIn: product.TotalIn, + TotalOut: product.TotalOut, IsLowStock: product.IsLowStock, IsZeroStock: product.IsZeroStock, UpdatedAt: product.UpdatedAt.Format(time.RFC3339), @@ -266,7 +267,8 @@ func (s *InventoryServiceImpl) GetInventoryReportDetails(ctx context.Context, fi ReorderLevel: ingredient.ReorderLevel, UnitCost: ingredient.UnitCost, TotalValue: ingredient.TotalValue, - TotalSold: ingredient.TotalSold, + TotalIn: ingredient.TotalIn, + TotalOut: ingredient.TotalOut, IsLowStock: ingredient.IsLowStock, IsZeroStock: ingredient.IsZeroStock, UpdatedAt: ingredient.UpdatedAt.Format(time.RFC3339),