package repository import ( "context" "database/sql" "encoding/json" "fmt" "erp-mvp/core-service/internal/models" "github.com/google/uuid" ) type OperationsRepository interface { PlaceItem(ctx context.Context, placement *models.ItemPlacement) error MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error GetByItem(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) GetByLocation(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.ItemPlacement, error) UpdateQuantity(ctx context.Context, id uuid.UUID, quantity int, orgID uuid.UUID) error Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error Search(ctx context.Context, orgID uuid.UUID, query string, category string, address string) ([]*models.ItemWithLocation, error) } type operationsRepository struct { db *sql.DB } func NewOperationsRepository(db *sql.DB) OperationsRepository { return &operationsRepository{db: db} } func (r *operationsRepository) PlaceItem(ctx context.Context, placement *models.ItemPlacement) error { query := ` INSERT INTO item_placements (id, organization_id, item_id, location_id, quantity, created_at) VALUES ($1, $2, $3, $4, $5, $6) ` _, err := r.db.ExecContext(ctx, query, placement.ID, placement.OrganizationID, placement.ItemID, placement.LocationID, placement.Quantity, placement.CreatedAt, ) if err != nil { return fmt.Errorf("failed to place item: %w", err) } return nil } func (r *operationsRepository) MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error { query := ` UPDATE item_placements SET location_id = $2 WHERE id = $1 AND organization_id = $3 ` result, err := r.db.ExecContext(ctx, query, placementID, newLocationID, orgID) if err != nil { return fmt.Errorf("failed to move item: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { return fmt.Errorf("item placement not found") } return nil } func (r *operationsRepository) GetByItem(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) { query := ` SELECT id, organization_id, item_id, location_id, quantity, created_at FROM item_placements WHERE item_id = $1 AND organization_id = $2 ORDER BY created_at DESC ` rows, err := r.db.QueryContext(ctx, query, itemID, orgID) if err != nil { return nil, fmt.Errorf("failed to query item placements: %w", err) } defer rows.Close() var placements []*models.ItemPlacement for rows.Next() { placement := &models.ItemPlacement{} err := rows.Scan( &placement.ID, &placement.OrganizationID, &placement.ItemID, &placement.LocationID, &placement.Quantity, &placement.CreatedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan item placement: %w", err) } placements = append(placements, placement) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating item placements: %w", err) } return placements, nil } func (r *operationsRepository) GetByLocation(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) { query := ` SELECT id, organization_id, item_id, location_id, quantity, created_at FROM item_placements WHERE location_id = $1 AND organization_id = $2 ORDER BY created_at DESC ` rows, err := r.db.QueryContext(ctx, query, locationID, orgID) if err != nil { return nil, fmt.Errorf("failed to query location placements: %w", err) } defer rows.Close() var placements []*models.ItemPlacement for rows.Next() { placement := &models.ItemPlacement{} err := rows.Scan( &placement.ID, &placement.OrganizationID, &placement.ItemID, &placement.LocationID, &placement.Quantity, &placement.CreatedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan item placement: %w", err) } placements = append(placements, placement) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating location placements: %w", err) } return placements, nil } func (r *operationsRepository) GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.ItemPlacement, error) { query := ` SELECT id, organization_id, item_id, location_id, quantity, created_at FROM item_placements WHERE id = $1 AND organization_id = $2 ` placement := &models.ItemPlacement{} err := r.db.QueryRowContext(ctx, query, id, orgID).Scan( &placement.ID, &placement.OrganizationID, &placement.ItemID, &placement.LocationID, &placement.Quantity, &placement.CreatedAt, ) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("item placement not found") } return nil, fmt.Errorf("failed to get item placement: %w", err) } return placement, nil } func (r *operationsRepository) UpdateQuantity(ctx context.Context, id uuid.UUID, quantity int, orgID uuid.UUID) error { query := ` UPDATE item_placements SET quantity = $2 WHERE id = $1 AND organization_id = $3 ` result, err := r.db.ExecContext(ctx, query, id, quantity, orgID) if err != nil { return fmt.Errorf("failed to update quantity: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { return fmt.Errorf("item placement not found") } return nil } func (r *operationsRepository) Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error { query := ` DELETE FROM item_placements WHERE id = $1 AND organization_id = $2 ` result, err := r.db.ExecContext(ctx, query, id, orgID) if err != nil { return fmt.Errorf("failed to delete item placement: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { return fmt.Errorf("item placement not found") } return nil } func (r *operationsRepository) Search(ctx context.Context, orgID uuid.UUID, query string, category string, address string) ([]*models.ItemWithLocation, error) { baseQuery := ` SELECT i.id, i.organization_id, i.name, i.description, i.category, i.created_at, sl.id, sl.organization_id, sl.parent_id, sl.name, sl.address, sl.type, sl.coordinates, sl.created_at, ip.quantity FROM items i JOIN item_placements ip ON i.id = ip.item_id JOIN storage_locations sl ON ip.location_id = sl.id WHERE i.organization_id = $1 AND sl.organization_id = $1 ` var args []interface{} args = append(args, orgID) argIndex := 2 if query != "" { baseQuery += fmt.Sprintf(" AND (i.name ILIKE $%d OR i.description ILIKE $%d)", argIndex, argIndex) args = append(args, "%"+query+"%") argIndex++ } if category != "" { baseQuery += fmt.Sprintf(" AND i.category = $%d", argIndex) args = append(args, category) argIndex++ } if address != "" { baseQuery += fmt.Sprintf(" AND sl.address ILIKE $%d", argIndex) args = append(args, "%"+address+"%") argIndex++ } baseQuery += " ORDER BY i.name, sl.name" rows, err := r.db.QueryContext(ctx, baseQuery, args...) if err != nil { return nil, fmt.Errorf("failed to search items with locations: %w", err) } defer rows.Close() var results []*models.ItemWithLocation for rows.Next() { var coordinatesJSON []byte itemWithLocation := &models.ItemWithLocation{} err := rows.Scan( &itemWithLocation.Item.ID, &itemWithLocation.Item.OrganizationID, &itemWithLocation.Item.Name, &itemWithLocation.Item.Description, &itemWithLocation.Item.Category, &itemWithLocation.Item.CreatedAt, &itemWithLocation.Location.ID, &itemWithLocation.Location.OrganizationID, &itemWithLocation.Location.ParentID, &itemWithLocation.Location.Name, &itemWithLocation.Location.Address, &itemWithLocation.Location.Type, &coordinatesJSON, &itemWithLocation.Location.CreatedAt, &itemWithLocation.Quantity, ) if err != nil { return nil, fmt.Errorf("failed to scan item with location: %w", err) } // Конвертируем JSON строку в map if len(coordinatesJSON) > 0 { err = json.Unmarshal(coordinatesJSON, &itemWithLocation.Location.Coordinates) if err != nil { return nil, fmt.Errorf("failed to unmarshal coordinates: %w", err) } } else { itemWithLocation.Location.Coordinates = make(models.JSON) } results = append(results, itemWithLocation) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating search results: %w", err) } return results, nil }