addd inventory movement

This commit is contained in:
Aditya Siregar 2025-08-14 01:00:36 +07:00
parent 3a0c262c77
commit 07b3eda263
4 changed files with 59 additions and 26 deletions

View File

@ -99,7 +99,8 @@ type InventoryProductDetailResponse struct {
ReorderLevel int `json:"reorder_level"` ReorderLevel int `json:"reorder_level"`
UnitCost float64 `json:"unit_cost"` UnitCost float64 `json:"unit_cost"`
TotalValue float64 `json:"total_value"` 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"` IsLowStock bool `json:"is_low_stock"`
IsZeroStock bool `json:"is_zero_stock"` IsZeroStock bool `json:"is_zero_stock"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
@ -114,7 +115,8 @@ type InventoryIngredientDetailResponse struct {
ReorderLevel int `json:"reorder_level"` ReorderLevel int `json:"reorder_level"`
UnitCost float64 `json:"unit_cost"` UnitCost float64 `json:"unit_cost"`
TotalValue float64 `json:"total_value"` 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"` IsLowStock bool `json:"is_low_stock"`
IsZeroStock bool `json:"is_zero_stock"` IsZeroStock bool `json:"is_zero_stock"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`

View File

@ -92,7 +92,8 @@ type InventoryProductDetail struct {
ReorderLevel int `json:"reorder_level"` ReorderLevel int `json:"reorder_level"`
UnitCost float64 `json:"unit_cost"` UnitCost float64 `json:"unit_cost"`
TotalValue float64 `json:"total_value"` 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"` IsLowStock bool `json:"is_low_stock"`
IsZeroStock bool `json:"is_zero_stock"` IsZeroStock bool `json:"is_zero_stock"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
@ -107,7 +108,8 @@ type InventoryIngredientDetail struct {
ReorderLevel int `json:"reorder_level"` ReorderLevel int `json:"reorder_level"`
UnitCost float64 `json:"unit_cost"` UnitCost float64 `json:"unit_cost"`
TotalValue float64 `json:"total_value"` 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"` IsLowStock bool `json:"is_low_stock"`
IsZeroStock bool `json:"is_zero_stock"` IsZeroStock bool `json:"is_zero_stock"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`

View File

@ -513,27 +513,39 @@ func (r *InventoryRepositoryImpl) getInventoryProductsDetails(ctx context.Contex
return nil, err return nil, err
} }
// Now get total sold for each product // Now get total in and out for each product
var products []*models.InventoryProductDetail var products []*models.InventoryProductDetail
for _, result := range baseResults { for _, result := range baseResults {
var totalSold float64 var totalIn, totalOut float64
// Query total sold for this specific product // Query total in (positive movements) for this specific product
soldQuery := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}). 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)"). Select("COALESCE(SUM(ABS(quantity)), 0)").
Where("item_id = ? AND outlet_id = ? AND item_type = ? AND movement_type = ? AND quantity < 0", Where("item_id = ? AND outlet_id = ? AND item_type = ? AND quantity < 0",
result.ProductID, filter.OutletID, "PRODUCT", entities.InventoryMovementTypeSale) result.ProductID, filter.OutletID, "PRODUCT")
// Apply date filters if provided // Apply date filters if provided
if filter.DateFrom != nil { 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 { 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 { if err := inQuery.Scan(&totalIn).Error; err != nil {
return nil, fmt.Errorf("failed to get total sold for product %s: %w", result.ProductID, err) 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 := "" categoryName := ""
@ -550,7 +562,8 @@ func (r *InventoryRepositoryImpl) getInventoryProductsDetails(ctx context.Contex
ReorderLevel: result.ReorderLevel, ReorderLevel: result.ReorderLevel,
UnitCost: result.UnitCost, UnitCost: result.UnitCost,
TotalValue: result.TotalValue, TotalValue: result.TotalValue,
TotalSold: totalSold, TotalIn: totalIn,
TotalOut: totalOut,
IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0, IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0,
IsZeroStock: result.Quantity == 0, IsZeroStock: result.Quantity == 0,
UpdatedAt: result.UpdatedAt, UpdatedAt: result.UpdatedAt,
@ -617,22 +630,35 @@ func (r *InventoryRepositoryImpl) getInventoryIngredientsDetails(ctx context.Con
var ingredients []*models.InventoryIngredientDetail var ingredients []*models.InventoryIngredientDetail
for _, result := range baseResults { 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)"). Select("COALESCE(SUM(ABS(quantity)), 0)").
Where("item_id = ? AND outlet_id = ? AND item_type = ? AND movement_type = ? AND quantity < 0", Where("item_id = ? AND outlet_id = ? AND item_type = ? AND quantity < 0",
result.IngredientID, filter.OutletID, "INGREDIENT", entities.InventoryMovementTypeSale) result.IngredientID, filter.OutletID, "INGREDIENT")
if filter.DateFrom != nil { 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 { 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 { if err := inQuery.Scan(&totalIn).Error; err != nil {
return nil, fmt.Errorf("failed to get total sold for ingredient %s: %w", result.IngredientID, err) 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 := "" unitName := ""
@ -649,7 +675,8 @@ func (r *InventoryRepositoryImpl) getInventoryIngredientsDetails(ctx context.Con
ReorderLevel: result.ReorderLevel, ReorderLevel: result.ReorderLevel,
UnitCost: result.UnitCost, UnitCost: result.UnitCost,
TotalValue: result.TotalValue, TotalValue: result.TotalValue,
TotalSold: totalSold, TotalIn: totalIn,
TotalOut: totalOut,
IsLowStock: result.Quantity <= 0 && result.Quantity > 0, IsLowStock: result.Quantity <= 0 && result.Quantity > 0,
IsZeroStock: result.Quantity == 0, IsZeroStock: result.Quantity == 0,
UpdatedAt: result.UpdatedAt, UpdatedAt: result.UpdatedAt,

View File

@ -247,7 +247,8 @@ func (s *InventoryServiceImpl) GetInventoryReportDetails(ctx context.Context, fi
ReorderLevel: product.ReorderLevel, ReorderLevel: product.ReorderLevel,
UnitCost: product.UnitCost, UnitCost: product.UnitCost,
TotalValue: product.TotalValue, TotalValue: product.TotalValue,
TotalSold: product.TotalSold, TotalIn: product.TotalIn,
TotalOut: product.TotalOut,
IsLowStock: product.IsLowStock, IsLowStock: product.IsLowStock,
IsZeroStock: product.IsZeroStock, IsZeroStock: product.IsZeroStock,
UpdatedAt: product.UpdatedAt.Format(time.RFC3339), UpdatedAt: product.UpdatedAt.Format(time.RFC3339),
@ -266,7 +267,8 @@ func (s *InventoryServiceImpl) GetInventoryReportDetails(ctx context.Context, fi
ReorderLevel: ingredient.ReorderLevel, ReorderLevel: ingredient.ReorderLevel,
UnitCost: ingredient.UnitCost, UnitCost: ingredient.UnitCost,
TotalValue: ingredient.TotalValue, TotalValue: ingredient.TotalValue,
TotalSold: ingredient.TotalSold, TotalIn: ingredient.TotalIn,
TotalOut: ingredient.TotalOut,
IsLowStock: ingredient.IsLowStock, IsLowStock: ingredient.IsLowStock,
IsZeroStock: ingredient.IsZeroStock, IsZeroStock: ingredient.IsZeroStock,
UpdatedAt: ingredient.UpdatedAt.Format(time.RFC3339), UpdatedAt: ingredient.UpdatedAt.Format(time.RFC3339),