update order status

This commit is contained in:
Aditya Siregar
2025-08-13 23:36:31 +07:00
parent ccb0458189
commit ee7d0e529b
9 changed files with 846 additions and 35 deletions
+291 -4
View File
@@ -4,8 +4,10 @@ import (
"context"
"errors"
"fmt"
"time"
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
"github.com/google/uuid"
"gorm.io/gorm"
@@ -31,6 +33,8 @@ type InventoryRepository interface {
BulkUpdate(ctx context.Context, inventoryItems []*entities.Inventory) error
BulkAdjustQuantity(ctx context.Context, adjustments map[uuid.UUID]int, outletID uuid.UUID) error
GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, error)
GetInventoryReportSummary(ctx context.Context, outletID uuid.UUID) (*models.InventoryReportSummary, error)
GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter) (*models.InventoryReportDetail, error)
}
type InventoryRepositoryImpl struct {
@@ -281,7 +285,7 @@ func (r *InventoryRepositoryImpl) BulkUpdate(ctx context.Context, inventoryItems
if len(inventoryItems) == 0 {
return nil
}
// Use GORM's transaction for bulk updates
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, inventory := range inventoryItems {
@@ -326,12 +330,295 @@ func (r *InventoryRepositoryImpl) BulkAdjustQuantity(ctx context.Context, adjust
func (r *InventoryRepositoryImpl) GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, error) {
var totalValue float64
err := r.db.WithContext(ctx).
if err := r.db.WithContext(ctx).
Table("inventory").
Select("SUM(inventory.quantity * products.cost)").
Joins("JOIN products ON inventory.product_id = products.id").
Where("inventory.outlet_id = ?", outletID).
Scan(&totalValue).Error
Scan(&totalValue).Error; err != nil {
return 0, fmt.Errorf("failed to get total value: %w", err)
}
return totalValue, err
return totalValue, nil
}
// GetInventoryReportSummary returns summary statistics for inventory report
func (r *InventoryRepositoryImpl) GetInventoryReportSummary(ctx context.Context, outletID uuid.UUID) (*models.InventoryReportSummary, error) {
var summary models.InventoryReportSummary
summary.OutletID = outletID
summary.GeneratedAt = time.Now()
// Get outlet name
var outlet entities.Outlet
if err := r.db.WithContext(ctx).Select("name").First(&outlet, "id = ?", outletID).Error; err != nil {
return nil, fmt.Errorf("failed to get outlet name: %w", err)
}
summary.OutletName = outlet.Name
// Get total products count
var totalProducts int64
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Joins("JOIN products ON inventory.product_id = products.id").
Where("inventory.outlet_id = ? AND products.has_ingredients = false", outletID).
Count(&totalProducts).Error; err != nil {
return nil, fmt.Errorf("failed to count total products: %w", err)
}
summary.TotalProducts = int(totalProducts)
// Get total ingredients count
var totalIngredients int64
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
Where("inventory.outlet_id = ?", outletID).
Count(&totalIngredients).Error; err != nil {
return nil, fmt.Errorf("failed to count total ingredients: %w", err)
}
summary.TotalIngredients = int(totalIngredients)
// Get low stock products count
var lowStockProducts int64
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Joins("JOIN products ON inventory.product_id = products.id").
Where("inventory.outlet_id = ? AND products.has_ingredients = false AND inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0", outletID).
Count(&lowStockProducts).Error; err != nil {
return nil, fmt.Errorf("failed to count low stock products: %w", err)
}
summary.LowStockProducts = int(lowStockProducts)
// Get low stock ingredients count
var lowStockIngredients int64
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
Where("inventory.outlet_id = ? AND inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0", outletID).
Count(&lowStockIngredients).Error; err != nil {
return nil, fmt.Errorf("failed to count low stock ingredients: %w", err)
}
summary.LowStockIngredients = int(lowStockIngredients)
// Get zero stock products count
var zeroStockProducts int64
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Joins("JOIN products ON inventory.product_id = products.id").
Where("inventory.outlet_id = ? AND products.has_ingredients = false AND inventory.quantity = 0", outletID).
Count(&zeroStockProducts).Error; err != nil {
return nil, fmt.Errorf("failed to count zero stock products: %w", err)
}
summary.ZeroStockProducts = int(zeroStockProducts)
// Get zero stock ingredients count
var zeroStockIngredients int64
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
Where("inventory.outlet_id = ? AND inventory.quantity = 0", outletID).
Count(&zeroStockIngredients).Error; err != nil {
return nil, fmt.Errorf("failed to count zero stock ingredients: %w", err)
}
summary.ZeroStockIngredients = int(zeroStockIngredients)
// Get total value
totalValue, err := r.GetTotalValueByOutlet(ctx, outletID)
if err != nil {
return nil, fmt.Errorf("failed to get total value: %w", err)
}
summary.TotalValue = totalValue
return &summary, nil
}
// GetInventoryReportDetails returns detailed inventory report with products and ingredients
func (r *InventoryRepositoryImpl) GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter) (*models.InventoryReportDetail, error) {
report := &models.InventoryReportDetail{}
// Get summary
if filter.OutletID != nil {
summary, err := r.GetInventoryReportSummary(ctx, *filter.OutletID)
if err != nil {
return nil, fmt.Errorf("failed to get report summary: %w", err)
}
report.Summary = summary
}
// Get products details
products, err := r.getInventoryProductsDetails(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to get products details: %w", err)
}
report.Products = products
// Get ingredients details
ingredients, err := r.getInventoryIngredientsDetails(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to get ingredients details: %w", err)
}
report.Ingredients = ingredients
return report, nil
}
// getInventoryProductsDetails retrieves detailed product inventory information
func (r *InventoryRepositoryImpl) getInventoryProductsDetails(ctx context.Context, filter *models.InventoryReportFilter) ([]*models.InventoryProductDetail, error) {
query := r.db.WithContext(ctx).Table("inventory").
Select(`
inventory.id,
inventory.product_id,
products.name as product_name,
categories.name as category_name,
inventory.quantity,
inventory.reorder_level,
COALESCE(product_variants.cost, products.cost) as unit_cost,
(COALESCE(product_variants.cost, products.cost) * inventory.quantity) as total_value,
inventory.updated_at
`).
Joins("JOIN products ON inventory.product_id = products.id").
Joins("LEFT JOIN categories ON products.category_id = categories.id").
Joins("LEFT JOIN product_variants ON products.id = product_variants.product_id").
Where("inventory.outlet_id = ? AND products.has_ingredients = false", filter.OutletID)
// Apply filters
if filter.CategoryID != nil {
query = query.Where("products.category_id = ?", *filter.CategoryID)
}
if filter.ShowLowStock != nil && *filter.ShowLowStock {
query = query.Where("inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0")
}
if filter.ShowZeroStock != nil && *filter.ShowZeroStock {
query = query.Where("inventory.quantity = 0")
}
if filter.Search != nil && *filter.Search != "" {
searchTerm := "%" + *filter.Search + "%"
query = query.Where("products.name ILIKE ? OR categories.name ILIKE ?", searchTerm, searchTerm)
}
// Apply pagination
if filter.Limit != nil {
query = query.Limit(*filter.Limit)
}
if filter.Offset != nil {
query = query.Offset(*filter.Offset)
}
query = query.Order("products.name ASC")
var results []struct {
ID uuid.UUID
ProductID uuid.UUID
ProductName string
CategoryName *string
Quantity int
ReorderLevel int
UnitCost float64
TotalValue float64
UpdatedAt time.Time
}
if err := query.Find(&results).Error; err != nil {
return nil, err
}
var products []*models.InventoryProductDetail
for _, result := range results {
categoryName := ""
if result.CategoryName != nil {
categoryName = *result.CategoryName
}
product := &models.InventoryProductDetail{
ID: result.ID,
ProductID: result.ProductID,
ProductName: result.ProductName,
CategoryName: categoryName,
Quantity: result.Quantity,
ReorderLevel: result.ReorderLevel,
UnitCost: result.UnitCost,
TotalValue: result.TotalValue,
IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0,
IsZeroStock: result.Quantity == 0,
UpdatedAt: result.UpdatedAt,
}
products = append(products, product)
}
return products, nil
}
// getInventoryIngredientsDetails retrieves detailed ingredient inventory information
func (r *InventoryRepositoryImpl) getInventoryIngredientsDetails(ctx context.Context, filter *models.InventoryReportFilter) ([]*models.InventoryIngredientDetail, error) {
query := r.db.WithContext(ctx).Table("inventory").
Select(`
inventory.id,
inventory.product_id as ingredient_id,
ingredients.name as ingredient_name,
units.name as unit_name,
inventory.quantity,
inventory.reorder_level,
ingredients.cost as unit_cost,
(ingredients.cost * inventory.quantity) as total_value,
inventory.updated_at
`).
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
Joins("LEFT JOIN units ON ingredients.unit_id = units.id").
Where("inventory.outlet_id = ?", filter.OutletID)
// Apply filters
if filter.ShowLowStock != nil && *filter.ShowLowStock {
query = query.Where("inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0")
}
if filter.ShowZeroStock != nil && *filter.ShowZeroStock {
query = query.Where("inventory.quantity = 0")
}
if filter.Search != nil && *filter.Search != "" {
searchTerm := "%" + *filter.Search + "%"
query = query.Where("ingredients.name ILIKE ? OR units.name ILIKE ?", searchTerm, searchTerm)
}
// Apply pagination
if filter.Limit != nil {
query = query.Limit(*filter.Limit)
}
if filter.Offset != nil {
query = query.Offset(*filter.Offset)
}
query = query.Order("ingredients.name ASC")
var results []struct {
ID uuid.UUID
IngredientID uuid.UUID
IngredientName string
UnitName *string
Quantity int
ReorderLevel int
UnitCost float64
TotalValue float64
UpdatedAt time.Time
}
if err := query.Find(&results).Error; err != nil {
return nil, err
}
var ingredients []*models.InventoryIngredientDetail
for _, result := range results {
unitName := ""
if result.UnitName != nil {
unitName = *result.UnitName
}
ingredient := &models.InventoryIngredientDetail{
ID: result.ID,
IngredientID: result.IngredientID,
IngredientName: result.IngredientName,
UnitName: unitName,
Quantity: result.Quantity,
ReorderLevel: result.ReorderLevel,
UnitCost: result.UnitCost,
TotalValue: result.TotalValue,
IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0,
IsZeroStock: result.Quantity == 0,
UpdatedAt: result.UpdatedAt,
}
ingredients = append(ingredients, ingredient)
}
return ingredients, nil
}
+15
View File
@@ -74,6 +74,21 @@ func (r *OrderRepositoryImpl) Update(ctx context.Context, order *entities.Order)
return r.db.WithContext(ctx).Save(order).Error
}
func (r *OrderRepositoryImpl) UpdateStatusSuccess(
ctx context.Context,
id uuid.UUID,
orderStatus entities.OrderStatus,
paymentStatus entities.PaymentStatus,
) error {
return r.db.WithContext(ctx).
Model(&entities.Order{}).
Where("id = ?", id).
Updates(map[string]interface{}{
"order_status": orderStatus,
"payment_status": paymentStatus,
}).Error
}
func (r *OrderRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.Order{}, "id = ?", id).Error
}