apskel-pos-backend/internal/repository/inventory_repository.go

302 lines
8.9 KiB
Go
Raw Normal View History

2025-07-18 20:10:29 +07:00
package repository
import (
"context"
"errors"
"fmt"
"apskel-pos-be/internal/entities"
"github.com/google/uuid"
"gorm.io/gorm"
)
type InventoryRepositoryImpl struct {
db *gorm.DB
}
func NewInventoryRepositoryImpl(db *gorm.DB) *InventoryRepositoryImpl {
return &InventoryRepositoryImpl{
db: db,
}
}
func (r *InventoryRepositoryImpl) Create(ctx context.Context, inventory *entities.Inventory) error {
return r.db.WithContext(ctx).Create(inventory).Error
}
func (r *InventoryRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.Inventory, error) {
var inventory entities.Inventory
err := r.db.WithContext(ctx).First(&inventory, "id = ?", id).Error
if err != nil {
return nil, err
}
return &inventory, nil
}
func (r *InventoryRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Inventory, error) {
var inventory entities.Inventory
err := r.db.WithContext(ctx).
Preload("Product").
Preload("Product.Category").
Preload("Outlet").
First(&inventory, "id = ?", id).Error
if err != nil {
return nil, err
}
return &inventory, nil
}
func (r *InventoryRepositoryImpl) GetByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID) (*entities.Inventory, error) {
var inventory entities.Inventory
err := r.db.WithContext(ctx).Where("product_id = ? AND outlet_id = ?", productID, outletID).First(&inventory).Error
if err != nil {
return nil, err
}
return &inventory, nil
}
func (r *InventoryRepositoryImpl) GetByOutlet(ctx context.Context, outletID uuid.UUID) ([]*entities.Inventory, error) {
var inventory []*entities.Inventory
err := r.db.WithContext(ctx).
Preload("Product").
Preload("Product.Category").
Where("outlet_id = ?", outletID).
Find(&inventory).Error
return inventory, err
}
func (r *InventoryRepositoryImpl) GetByProduct(ctx context.Context, productID uuid.UUID) ([]*entities.Inventory, error) {
var inventory []*entities.Inventory
err := r.db.WithContext(ctx).
Preload("Outlet").
Where("product_id = ?", productID).
Find(&inventory).Error
return inventory, err
}
func (r *InventoryRepositoryImpl) GetLowStock(ctx context.Context, outletID uuid.UUID) ([]*entities.Inventory, error) {
var inventory []*entities.Inventory
err := r.db.WithContext(ctx).
Preload("Product").
Preload("Product.Category").
Where("outlet_id = ? AND quantity <= reorder_level", outletID).
Find(&inventory).Error
return inventory, err
}
func (r *InventoryRepositoryImpl) GetZeroStock(ctx context.Context, outletID uuid.UUID) ([]*entities.Inventory, error) {
var inventory []*entities.Inventory
err := r.db.WithContext(ctx).
Preload("Product").
Preload("Product.Category").
Where("outlet_id = ? AND quantity = 0", outletID).
Find(&inventory).Error
return inventory, err
}
func (r *InventoryRepositoryImpl) Update(ctx context.Context, inventory *entities.Inventory) error {
return r.db.WithContext(ctx).Save(inventory).Error
}
func (r *InventoryRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.Inventory{}, "id = ?", id).Error
}
func (r *InventoryRepositoryImpl) List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Inventory, int64, error) {
var inventory []*entities.Inventory
var total int64
query := r.db.WithContext(ctx).Model(&entities.Inventory{}).
Preload("Product").
Preload("Product.Category").
Preload("Outlet")
for key, value := range filters {
switch key {
case "search":
searchValue := "%" + value.(string) + "%"
query = query.Joins("JOIN products ON inventory.product_id = products.id").
Where("products.name ILIKE ? OR products.sku ILIKE ?", searchValue, searchValue)
case "low_stock":
if value.(bool) {
query = query.Where("quantity <= reorder_level")
}
case "zero_stock":
if value.(bool) {
query = query.Where("quantity = 0")
}
case "category_id":
query = query.Joins("JOIN products ON inventory.product_id = products.id").
Where("products.category_id = ?", value)
default:
query = query.Where(key+" = ?", value)
}
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
err := query.Limit(limit).Offset(offset).Find(&inventory).Error
return inventory, total, err
}
func (r *InventoryRepositoryImpl) Count(ctx context.Context, filters map[string]interface{}) (int64, error) {
var count int64
query := r.db.WithContext(ctx).Model(&entities.Inventory{})
for key, value := range filters {
switch key {
case "search":
searchValue := "%" + value.(string) + "%"
query = query.Joins("JOIN products ON inventory.product_id = products.id").
Where("products.name ILIKE ? OR products.sku ILIKE ?", searchValue, searchValue)
case "low_stock":
if value.(bool) {
query = query.Where("quantity <= reorder_level")
}
case "zero_stock":
if value.(bool) {
query = query.Where("quantity = 0")
}
case "category_id":
query = query.Joins("JOIN products ON inventory.product_id = products.id").
Where("products.category_id = ?", value)
default:
query = query.Where(key+" = ?", value)
}
}
err := query.Count(&count).Error
return count, err
}
func (r *InventoryRepositoryImpl) AdjustQuantity(ctx context.Context, productID, outletID uuid.UUID, delta int) (*entities.Inventory, error) {
var inventory entities.Inventory
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Try to find existing inventory
if err := tx.Where("product_id = ? AND outlet_id = ?", productID, outletID).First(&inventory).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Inventory doesn't exist, create it with initial quantity
inventory = entities.Inventory{
ProductID: productID,
OutletID: outletID,
Quantity: 0,
ReorderLevel: 0,
}
if err := tx.Create(&inventory).Error; err != nil {
return fmt.Errorf("failed to create inventory record: %w", err)
}
} else {
return err
}
}
inventory.UpdateQuantity(delta)
return tx.Save(&inventory).Error
})
if err != nil {
return nil, err
}
return &inventory, nil
}
func (r *InventoryRepositoryImpl) SetQuantity(ctx context.Context, productID, outletID uuid.UUID, quantity int) (*entities.Inventory, error) {
var inventory entities.Inventory
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Get current inventory
if err := tx.Where("product_id = ? AND outlet_id = ?", productID, outletID).First(&inventory).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Inventory doesn't exist, create it with the specified quantity
inventory = entities.Inventory{
ProductID: productID,
OutletID: outletID,
Quantity: quantity,
ReorderLevel: 0,
}
if inventory.Quantity < 0 {
inventory.Quantity = 0
}
if err := tx.Create(&inventory).Error; err != nil {
return fmt.Errorf("failed to create inventory record: %w", err)
}
return nil
}
return err
}
// Set new quantity
inventory.Quantity = quantity
if inventory.Quantity < 0 {
inventory.Quantity = 0
}
// Save updated inventory
return tx.Save(&inventory).Error
})
if err != nil {
return nil, err
}
return &inventory, nil
}
func (r *InventoryRepositoryImpl) UpdateReorderLevel(ctx context.Context, id uuid.UUID, reorderLevel int) error {
return r.db.WithContext(ctx).Model(&entities.Inventory{}).
Where("id = ?", id).
Update("reorder_level", reorderLevel).Error
}
func (r *InventoryRepositoryImpl) BulkCreate(ctx context.Context, inventoryItems []*entities.Inventory) error {
return r.db.WithContext(ctx).CreateInBatches(inventoryItems, 100).Error
}
func (r *InventoryRepositoryImpl) BulkAdjustQuantity(ctx context.Context, adjustments map[uuid.UUID]int, outletID uuid.UUID) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for productID, delta := range adjustments {
var inventory entities.Inventory
if err := tx.Where("product_id = ? AND outlet_id = ?", productID, outletID).First(&inventory).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Inventory doesn't exist, create it with initial quantity
inventory = entities.Inventory{
ProductID: productID,
OutletID: outletID,
Quantity: 0,
ReorderLevel: 0,
}
if err := tx.Create(&inventory).Error; err != nil {
return fmt.Errorf("failed to create inventory record for product %s: %w", productID, err)
}
} else {
return err
}
}
inventory.UpdateQuantity(delta)
if err := tx.Save(&inventory).Error; err != nil {
return err
}
}
return nil
})
}
func (r *InventoryRepositoryImpl) GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, error) {
var totalValue float64
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
return totalValue, err
}