package processor import ( "context" "fmt" "apskel-pos-be/internal/entities" "apskel-pos-be/internal/mappers" "apskel-pos-be/internal/models" "apskel-pos-be/internal/repository" "github.com/google/uuid" ) type InventoryMovementProcessor interface { CreateMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error) GetMovementByID(ctx context.Context, id uuid.UUID) (*models.InventoryMovementResponse, error) ListMovements(ctx context.Context, req *models.ListInventoryMovementsRequest) (*models.ListInventoryMovementsResponse, error) GetMovementsByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) (*models.ListInventoryMovementsResponse, error) GetMovementsByOrderID(ctx context.Context, orderID uuid.UUID) ([]models.InventoryMovementResponse, error) GetMovementsByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]models.InventoryMovementResponse, error) } type InventoryMovementRepository interface { Create(ctx context.Context, movement *entities.InventoryMovement) error GetByID(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error) List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.InventoryMovement, int64, error) GetByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) ([]*entities.InventoryMovement, int64, error) GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.InventoryMovement, error) GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.InventoryMovement, error) Count(ctx context.Context, filters map[string]interface{}) (int64, error) } type InventoryMovementProcessorImpl struct { movementRepo InventoryMovementRepository inventoryRepo repository.InventoryRepository } func NewInventoryMovementProcessorImpl( movementRepo InventoryMovementRepository, inventoryRepo repository.InventoryRepository, ) *InventoryMovementProcessorImpl { return &InventoryMovementProcessorImpl{ movementRepo: movementRepo, inventoryRepo: inventoryRepo, } } func (p *InventoryMovementProcessorImpl) CreateInventoryMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error) { currentInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, req.ItemID, req.OutletID) if err != nil { return nil, fmt.Errorf("failed to get current inventory: %w", err) } previousQuantity := currentInventory.Quantity newQuantity := previousQuantity + req.Quantity movement := &entities.InventoryMovement{ OrganizationID: req.OrganizationID, OutletID: req.OutletID, ItemID: req.ItemID, ItemType: req.ItemType, MovementType: entities.InventoryMovementType(req.MovementType), Quantity: float64(req.Quantity), PreviousQuantity: float64(previousQuantity), NewQuantity: float64(newQuantity), UnitCost: req.UnitCost, TotalCost: float64(req.Quantity) * req.UnitCost, ReferenceType: (*entities.InventoryMovementReferenceType)(req.ReferenceType), ReferenceID: req.ReferenceID, OrderID: req.OrderID, PaymentID: req.PaymentID, UserID: req.UserID, Reason: req.Reason, Notes: req.Notes, Metadata: entities.Metadata(req.Metadata), } if err := p.movementRepo.Create(ctx, movement); err != nil { return nil, fmt.Errorf("failed to create inventory movement: %w", err) } movementWithRelations, err := p.movementRepo.GetWithRelations(ctx, movement.ID) if err != nil { return nil, fmt.Errorf("failed to retrieve created movement: %w", err) } response := mappers.InventoryMovementEntityToResponse(movementWithRelations) return response, nil } func (p *InventoryMovementProcessorImpl) GetInventoryMovementByID(ctx context.Context, id uuid.UUID) (*models.InventoryMovementResponse, error) { movement, err := p.movementRepo.GetWithRelations(ctx, id) if err != nil { return nil, fmt.Errorf("movement not found: %w", err) } response := mappers.InventoryMovementEntityToResponse(movement) return response, nil } func (p *InventoryMovementProcessorImpl) ListInventoryMovements(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string) (*models.PaginatedResponse[models.InventoryMovementResponse], error) { // Set default values if page < 1 { page = 1 } if limit < 1 { limit = 10 } if limit > 100 { limit = 100 } filters := make(map[string]interface{}) filters["organization_id"] = organizationID if outletID != nil { filters["outlet_id"] = *outletID } if search != "" { filters["search"] = search } offset := (page - 1) * limit movements, total, err := p.movementRepo.List(ctx, filters, limit, offset) if err != nil { return nil, fmt.Errorf("failed to list movements: %w", err) } // Convert to responses movementResponses := make([]models.InventoryMovementResponse, len(movements)) for i, movement := range movements { response := mappers.InventoryMovementEntityToResponse(movement) if response != nil { movementResponses[i] = *response } } // Create paginated response paginatedResponse := &models.PaginatedResponse[models.InventoryMovementResponse]{ Data: movementResponses, Pagination: models.Pagination{ Page: page, Limit: limit, Total: total, TotalPages: int((total + int64(limit) - 1) / int64(limit)), }, } return paginatedResponse, nil } func (p *InventoryMovementProcessorImpl) GetMovementsByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) (*models.ListInventoryMovementsResponse, error) { movements, total, err := p.movementRepo.GetByProductAndOutlet(ctx, productID, outletID, limit, offset) if err != nil { return nil, fmt.Errorf("failed to get movements by product and outlet: %w", err) } movementResponses := make([]models.InventoryMovementResponse, len(movements)) for i, movement := range movements { response := mappers.InventoryMovementEntityToResponse(movement) if response != nil { movementResponses[i] = *response } } totalPages := int(total) / limit if int(total)%limit > 0 { totalPages++ } return &models.ListInventoryMovementsResponse{ Movements: movementResponses, TotalCount: int(total), Page: 1, Limit: limit, TotalPages: totalPages, }, nil } func (p *InventoryMovementProcessorImpl) GetMovementsByOrderID(ctx context.Context, orderID uuid.UUID) ([]models.InventoryMovementResponse, error) { movements, err := p.movementRepo.GetByOrderID(ctx, orderID) if err != nil { return nil, fmt.Errorf("failed to get movements by order ID: %w", err) } responses := mappers.InventoryMovementEntitiesToResponses(movements) return responses, nil } func (p *InventoryMovementProcessorImpl) GetMovementsByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]models.InventoryMovementResponse, error) { movements, err := p.movementRepo.GetByPaymentID(ctx, paymentID) if err != nil { return nil, fmt.Errorf("failed to get movements by payment ID: %w", err) } responses := mappers.InventoryMovementEntitiesToResponses(movements) return responses, nil }