package processor import ( "context" "fmt" "apskel-pos-be/internal/mappers" "apskel-pos-be/internal/models" "apskel-pos-be/internal/repository" "github.com/google/uuid" ) type InventoryProcessor interface { CreateInventory(ctx context.Context, req *models.CreateInventoryRequest) (*models.InventoryResponse, error) UpdateInventory(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest) (*models.InventoryResponse, error) DeleteInventory(ctx context.Context, id uuid.UUID) error GetInventoryByID(ctx context.Context, id uuid.UUID) (*models.InventoryResponse, error) ListInventory(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.InventoryResponse, int, error) AdjustInventory(ctx context.Context, productID, outletID uuid.UUID, req *models.InventoryAdjustmentRequest) (*models.InventoryResponse, error) GetLowStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error) GetZeroStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error) } type InventoryProcessorImpl struct { inventoryRepo repository.InventoryRepository productRepo ProductRepository outletRepo OutletRepository } func NewInventoryProcessorImpl( inventoryRepo repository.InventoryRepository, productRepo ProductRepository, outletRepo OutletRepository, ) *InventoryProcessorImpl { return &InventoryProcessorImpl{ inventoryRepo: inventoryRepo, productRepo: productRepo, outletRepo: outletRepo, } } func (p *InventoryProcessorImpl) CreateInventory(ctx context.Context, req *models.CreateInventoryRequest) (*models.InventoryResponse, error) { _, err := p.productRepo.GetByID(ctx, req.ProductID) if err != nil { return nil, fmt.Errorf("invalid product: %w", err) } // Validate outlet exists _, err = p.outletRepo.GetByID(ctx, req.OutletID) if err != nil { return nil, fmt.Errorf("invalid outlet: %w", err) } // Check if inventory already exists for this product-outlet combination existingInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, req.ProductID, req.OutletID) if err == nil && existingInventory != nil { return nil, fmt.Errorf("inventory already exists for product %s in outlet %s", req.ProductID, req.OutletID) } // Map request to entity inventoryEntity := mappers.CreateInventoryRequestToEntity(req) // Create inventory if err := p.inventoryRepo.Create(ctx, inventoryEntity); err != nil { return nil, fmt.Errorf("failed to create inventory: %w", err) } // Get inventory with relations for response inventoryWithRelations, err := p.inventoryRepo.GetWithRelations(ctx, inventoryEntity.ID) if err != nil { return nil, fmt.Errorf("failed to retrieve created inventory: %w", err) } // Map entity to response model response := mappers.InventoryEntityToResponse(inventoryWithRelations) return response, nil } func (p *InventoryProcessorImpl) UpdateInventory(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest) (*models.InventoryResponse, error) { // Get existing inventory existingInventory, err := p.inventoryRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("inventory not found: %w", err) } // Apply updates to entity mappers.UpdateInventoryEntityFromRequest(existingInventory, req) // Update inventory if err := p.inventoryRepo.Update(ctx, existingInventory); err != nil { return nil, fmt.Errorf("failed to update inventory: %w", err) } // Get updated inventory with relations for response inventoryWithRelations, err := p.inventoryRepo.GetWithRelations(ctx, id) if err != nil { return nil, fmt.Errorf("failed to retrieve updated inventory: %w", err) } // Map entity to response model response := mappers.InventoryEntityToResponse(inventoryWithRelations) return response, nil } func (p *InventoryProcessorImpl) DeleteInventory(ctx context.Context, id uuid.UUID) error { // Check if inventory exists _, err := p.inventoryRepo.GetByID(ctx, id) if err != nil { return fmt.Errorf("inventory not found: %w", err) } // Delete inventory if err := p.inventoryRepo.Delete(ctx, id); err != nil { return fmt.Errorf("failed to delete inventory: %w", err) } return nil } func (p *InventoryProcessorImpl) GetInventoryByID(ctx context.Context, id uuid.UUID) (*models.InventoryResponse, error) { inventoryEntity, err := p.inventoryRepo.GetWithRelations(ctx, id) if err != nil { return nil, fmt.Errorf("inventory not found: %w", err) } response := mappers.InventoryEntityToResponse(inventoryEntity) return response, nil } func (p *InventoryProcessorImpl) ListInventory(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.InventoryResponse, int, error) { offset := (page - 1) * limit inventoryEntities, total, err := p.inventoryRepo.List(ctx, filters, limit, offset) if err != nil { return nil, 0, fmt.Errorf("failed to list inventory: %w", err) } responses := make([]models.InventoryResponse, len(inventoryEntities)) for i, entity := range inventoryEntities { response := mappers.InventoryEntityToResponse(entity) if response != nil { responses[i] = *response } } return responses, int(total), nil } func (p *InventoryProcessorImpl) AdjustInventory(ctx context.Context, productID, outletID uuid.UUID, req *models.InventoryAdjustmentRequest) (*models.InventoryResponse, error) { // Validate product exists _, err := p.productRepo.GetByID(ctx, productID) if err != nil { return nil, fmt.Errorf("invalid product: %w", err) } // Validate outlet exists _, err = p.outletRepo.GetByID(ctx, outletID) if err != nil { return nil, fmt.Errorf("invalid outlet: %w", err) } // Perform quantity adjustment adjustedInventory, err := p.inventoryRepo.AdjustQuantity(ctx, productID, outletID, req.Delta) if err != nil { return nil, fmt.Errorf("failed to adjust inventory quantity: %w", err) } // Get inventory with relations for response inventoryWithRelations, err := p.inventoryRepo.GetWithRelations(ctx, adjustedInventory.ID) if err != nil { return nil, fmt.Errorf("failed to retrieve adjusted inventory: %w", err) } // Map entity to response model response := mappers.InventoryEntityToResponse(inventoryWithRelations) return response, nil } func (p *InventoryProcessorImpl) GetLowStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error) { // Validate outlet exists _, err := p.outletRepo.GetByID(ctx, outletID) if err != nil { return nil, fmt.Errorf("invalid outlet: %w", err) } inventoryEntities, err := p.inventoryRepo.GetLowStock(ctx, outletID) if err != nil { return nil, fmt.Errorf("failed to get low stock items: %w", err) } responses := make([]models.InventoryResponse, len(inventoryEntities)) for i, entity := range inventoryEntities { response := mappers.InventoryEntityToResponse(entity) if response != nil { responses[i] = *response } } return responses, nil } func (p *InventoryProcessorImpl) GetZeroStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error) { // Validate outlet exists _, err := p.outletRepo.GetByID(ctx, outletID) if err != nil { return nil, fmt.Errorf("invalid outlet: %w", err) } inventoryEntities, err := p.inventoryRepo.GetZeroStock(ctx, outletID) if err != nil { return nil, fmt.Errorf("failed to get zero stock items: %w", err) } responses := make([]models.InventoryResponse, len(inventoryEntities)) for i, entity := range inventoryEntities { response := mappers.InventoryEntityToResponse(entity) if response != nil { responses[i] = *response } } return responses, nil }