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) CreateMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error) { currentInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, req.ProductID, 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, ProductID: req.ProductID, MovementType: entities.InventoryMovementType(req.MovementType), Quantity: req.Quantity, PreviousQuantity: previousQuantity, NewQuantity: 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) GetMovementByID(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) ListMovements(ctx context.Context, req *models.ListInventoryMovementsRequest) (*models.ListInventoryMovementsResponse, error) { filters := make(map[string]interface{}) if req.OrganizationID != nil { filters["organization_id"] = *req.OrganizationID } if req.OutletID != nil { filters["outlet_id"] = *req.OutletID } if req.ProductID != nil { filters["product_id"] = *req.ProductID } if req.MovementType != nil { filters["movement_type"] = string(*req.MovementType) } if req.ReferenceType != nil { filters["reference_type"] = string(*req.ReferenceType) } if req.ReferenceID != nil { filters["reference_id"] = *req.ReferenceID } if req.OrderID != nil { filters["order_id"] = *req.OrderID } if req.PaymentID != nil { filters["payment_id"] = *req.PaymentID } if req.UserID != nil { filters["user_id"] = *req.UserID } if req.DateFrom != nil { filters["date_from"] = *req.DateFrom } if req.DateTo != nil { filters["date_to"] = *req.DateTo } offset := (req.Page - 1) * req.Limit movements, total, err := p.movementRepo.List(ctx, filters, req.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 } } // Calculate total pages totalPages := int(total) / req.Limit if int(total)%req.Limit > 0 { totalPages++ } return &models.ListInventoryMovementsResponse{ Movements: movementResponses, TotalCount: int(total), Page: req.Page, Limit: req.Limit, TotalPages: totalPages, }, 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 }