# 🏗️ ТЕХНИЧЕСКАЯ ДОКУМЕНТАЦИЯ: V2 АРХИТЕКТУРА УСЛУГ

> **Статус**: ✅ **PRODUCTION READY**  
> **Версия**: V2.0  
> **Дата создания**: 03.09.2025  
> **Область**: Модульная архитектура услуг фулфилмента

---

## 🎯 ОБЗОР V2 АРХИТЕКТУРЫ УСЛУГ

### ФИЛОСОФИЯ V2:
> **"Один домен - одна модель - одна ответственность"**

V2 архитектура услуг основана на принципе доменной специализации, где каждый тип данных имеет собственную оптимизированную структуру, резолверы и логику обработки.

### ОСНОВНЫЕ ПРИНЦИПЫ:

1. **Доменная изоляция** - каждый тип услуг в отдельной таблице
2. **Специализированные резолверы** - отдельные файлы для каждого домена  
3. **Безопасность по умолчанию** - автоматическая изоляция по fulfillmentId
4. **Масштабируемость** - независимое развитие каждого типа

---

## 🗄️ V2 МОДЕЛИ ДАННЫХ

### 1. FULFILLMENT SERVICE (Услуги)

**Назначение**: Управление услугами, предоставляемыми фулфилментом

```prisma
model FulfillmentService {
  id               String       @id @default(cuid())
  fulfillmentId    String       // Изоляция по домену
  name             String
  description      String?
  price            Decimal      @db.Decimal(10, 2)
  unit             String       @default("шт")
  isActive         Boolean      @default(true)
  imageUrl         String?      // Поддержка изображений
  sortOrder        Int          @default(0)
  createdAt        DateTime     @default(now())
  updatedAt        DateTime     @updatedAt

  // Связи
  fulfillment      Organization @relation("FulfillmentServicesV2", fields: [fulfillmentId], references: [id])

  // Индексы производительности  
  @@index([fulfillmentId, isActive])
  @@map("fulfillment_services_v2")
}
```

**Ключевые особенности:**
- Decimal для точных денежных расчетов
- Поддержка изображений услуг
- Сортировка для UI
- Мягкое удаление через isActive

### 2. FULFILLMENT CONSUMABLE (Расходники)

**Назначение**: Управление расходными материалами фулфилмента

```prisma  
model FulfillmentConsumable {
  id                    String       @id @default(cuid())
  fulfillmentId         String       // Доменная привязка
  warehouseConsumableId String       // Связь со складом
  name                  String
  description           String?
  pricePerUnit          Decimal?     @db.Decimal(10, 2)
  unit                  String       @default("шт")  
  imageUrl              String?
  warehouseStock        Int          @default(0)
  isAvailable           Boolean      @default(true)
  createdAt             DateTime     @default(now())
  updatedAt             DateTime     @updatedAt

  // Связи
  fulfillment           Organization @relation("FulfillmentConsumablesV2", fields: [fulfillmentId], references: [id])
  warehouseConsumable   WarehouseConsumable @relation("FulfillmentWarehouseConsumables", fields: [warehouseConsumableId], references: [id])

  // Индексы
  @@index([fulfillmentId, isAvailable])
  @@index([warehouseConsumableId])
  @@unique([fulfillmentId, warehouseConsumableId])
  @@map("fulfillment_consumables_v2")
}
```

**Ключевые особенности:**
- Связь с складскими остатками  
- Автоматическая синхронизация наличия
- Уникальность в рамках фулфилмента
- Опциональная цена (может устанавливаться позже)

### 3. FULFILLMENT LOGISTICS (Логистика)

**Назначение**: Управление логистическими маршрутами

```prisma
model FulfillmentLogistics {
  id               String       @id @default(cuid())
  fulfillmentId    String       // Изоляция домена
  fromLocation     String
  toLocation       String
  priceUnder1m3    Decimal      @db.Decimal(10, 2)
  priceOver1m3     Decimal      @db.Decimal(10, 2)
  estimatedDays    Int          @default(1)
  description      String?
  isActive         Boolean      @default(true)
  createdAt        DateTime     @default(now())
  updatedAt        DateTime     @updatedAt

  // Связи
  fulfillment      Organization @relation("FulfillmentLogisticsV2", fields: [fulfillmentId], references: [id])

  // Индексы
  @@index([fulfillmentId, isActive])
  @@index([fromLocation, toLocation])
  @@map("fulfillment_logistics_v2")
}
```

**Ключевые особенности:**
- Объемное ценообразование (до/свыше 1м³)
- Оценка времени доставки
- Географическая индексация
- Поддержка множественных маршрутов

---

## 🔌 V2 GRAPHQL API

### СХЕМА ТИПОВ

```graphql
# ОСНОВНЫЕ ТИПЫ
type FulfillmentService {
  id: ID!
  fulfillmentId: String!
  name: String!
  description: String
  price: Float!
  unit: String!
  isActive: Boolean!
  imageUrl: String
  sortOrder: Int!
  createdAt: DateTime!
  updatedAt: DateTime!
  fulfillment: Organization!
}

type FulfillmentConsumable {
  id: ID!
  fulfillmentId: String!
  warehouseConsumableId: String!
  name: String!
  description: String  
  pricePerUnit: Float
  unit: String!
  imageUrl: String
  warehouseStock: Int!
  isAvailable: Boolean!
  createdAt: DateTime!
  updatedAt: DateTime!
  fulfillment: Organization!
  warehouseConsumable: WarehouseConsumable!
}

type FulfillmentLogistics {
  id: ID!
  fulfillmentId: String!
  fromLocation: String!
  toLocation: String!
  priceUnder1m3: Float!
  priceOver1m3: Float!
  estimatedDays: Int!
  description: String
  isActive: Boolean!
  createdAt: DateTime!
  updatedAt: DateTime!
  fulfillment: Organization!
}
```

### V2 QUERIES

```graphql
type Query {
  # УСЛУГИ ФУЛФИЛМЕНТА  
  myFulfillmentServices: [FulfillmentService!]!
  fulfillmentServicesByFulfillment(fulfillmentId: ID!): [FulfillmentService!]!
  
  # РАСХОДНИКИ ФУЛФИЛМЕНТА
  myFulfillmentConsumables: [FulfillmentConsumable!]!
  fulfillmentConsumablesByFulfillment(fulfillmentId: ID!): [FulfillmentConsumable!]!
  
  # ЛОГИСТИКА ФУЛФИЛМЕНТА  
  myFulfillmentLogistics: [FulfillmentLogistics!]!
  fulfillmentLogisticsByFulfillment(fulfillmentId: ID!): [FulfillmentLogistics!]!
}
```

### V2 MUTATIONS

```graphql  
type Mutation {
  # УСЛУГИ - CRUD
  createFulfillmentService(input: CreateFulfillmentServiceInput!): FulfillmentServiceResponse!
  updateFulfillmentService(input: UpdateFulfillmentServiceInput!): FulfillmentServiceResponse!
  deleteFulfillmentService(id: ID!): Boolean!
  
  # РАСХОДНИКИ - CRUD  
  createFulfillmentConsumable(input: CreateFulfillmentConsumableInput!): FulfillmentConsumableResponse!
  updateFulfillmentConsumable(input: UpdateFulfillmentConsumableInput!): FulfillmentConsumableResponse!
  deleteFulfillmentConsumable(id: ID!): Boolean!
  
  # ЛОГИСТИКА - CRUD
  createFulfillmentLogistics(input: CreateFulfillmentLogisticsInput!): FulfillmentLogisticsResponse!
  updateFulfillmentLogistics(input: UpdateFulfillmentLogisticsInput!): FulfillmentLogisticsResponse!  
  deleteFulfillmentLogistics(id: ID!): Boolean!
}
```

### INPUT ТИПЫ

```graphql
# СОЗДАНИЕ УСЛУГИ
input CreateFulfillmentServiceInput {
  name: String!
  description: String
  price: Float!
  unit: String!
  imageUrl: String
  sortOrder: Int
}

# ОБНОВЛЕНИЕ УСЛУГИ  
input UpdateFulfillmentServiceInput {
  id: ID!
  name: String
  description: String
  price: Float
  unit: String
  isActive: Boolean
  imageUrl: String
  sortOrder: Int
}

# СОЗДАНИЕ РАСХОДНИКА
input CreateFulfillmentConsumableInput {
  warehouseConsumableId: String!
  pricePerUnit: Float
}

# ОБНОВЛЕНИЕ РАСХОДНИКА
input UpdateFulfillmentConsumableInput {
  id: ID!
  pricePerUnit: Float
}

# СОЗДАНИЕ ЛОГИСТИКИ
input CreateFulfillmentLogisticsInput {
  fromLocation: String!
  toLocation: String!
  priceUnder1m3: Float!
  priceOver1m3: Float!
  estimatedDays: Int!
  description: String
}

# ОБНОВЛЕНИЕ ЛОГИСТИКИ  
input UpdateFulfillmentLogisticsInput {
  id: ID!
  fromLocation: String
  toLocation: String
  priceUnder1m3: Float
  priceOver1m3: Float
  estimatedDays: Int
  description: String
  isActive: Boolean
}
```

### RESPONSE ТИПЫ

```graphql
# УНИВЕРСАЛЬНЫЕ ОТВЕТЫ
type FulfillmentServiceResponse {
  success: Boolean!
  message: String!
  service: FulfillmentService
}

type FulfillmentConsumableResponse {
  success: Boolean!
  message: String!
  consumable: FulfillmentConsumable
}

type FulfillmentLogisticsResponse {
  success: Boolean!
  message: String!  
  logistics: FulfillmentLogistics
}
```

---

## 🔧 V2 РЕЗОЛВЕРЫ IMPLEMENTATION

### АРХИТЕКТУРА РЕЗОЛВЕРОВ

```typescript
// Файл: /src/graphql/resolvers/fulfillment-services-v2.ts

// СТРУКТУРА:
export const fulfillmentServicesQueries = {
  myFulfillmentServices: [Query resolver],
  myFulfillmentConsumables: [Query resolver],
  myFulfillmentLogistics: [Query resolver],
  fulfillmentServicesByFulfillment: [Query resolver],
  fulfillmentConsumablesByFulfillment: [Query resolver], 
  fulfillmentLogisticsByFulfillment: [Query resolver],
}

export const fulfillmentServicesMutations = {
  createFulfillmentService: [Mutation resolver],
  updateFulfillmentService: [Mutation resolver],
  deleteFulfillmentService: [Mutation resolver],
  createFulfillmentConsumable: [Mutation resolver],
  updateFulfillmentConsumable: [Mutation resolver], 
  deleteFulfillmentConsumable: [Mutation resolver],
  createFulfillmentLogistics: [Mutation resolver],
  updateFulfillmentLogistics: [Mutation resolver],
  deleteFulfillmentLogistics: [Mutation resolver],
}
```

### ПРИМЕР QUERY РЕЗОЛВЕРА

```typescript
// УСЛУГИ ФУЛФИЛМЕНТА (собственные)
myFulfillmentServices: async (_: unknown, __: unknown, context: Context) => {
  try {
    const { user } = context
    if (!user?.organization?.id) {
      throw new Error('Организация не найдена')
    }

    // ДОМЕННАЯ БЕЗОПАСНОСТЬ: только свои услуги
    const services = await prisma.fulfillmentService.findMany({
      where: {
        fulfillmentId: user.organization.id,
        isActive: true,
      },
      include: {
        fulfillment: true,
      },
      orderBy: [
        { sortOrder: 'asc' },
        { name: 'asc' },
      ],
    })

    return services
  } catch (error) {
    console.error('Error fetching fulfillment services:', error)
    throw error
  }
},
```

### ПРИМЕР MUTATION РЕЗОЛВЕРА

```typescript
// СОЗДАНИЕ УСЛУГИ
createFulfillmentService: async (
  _: unknown,
  { input }: { input: CreateFulfillmentServiceInput },
  context: Context,
) => {
  try {
    const { user } = context
    if (!user?.organization?.id) {
      throw new Error('Организация не найдена')
    }

    // ВАЛИДАЦИЯ ВХОДНЫХ ДАННЫХ
    if (!input.name?.trim()) {
      return {
        success: false,
        message: 'Название услуги обязательно',
        service: null,
      }
    }

    if (!input.price || input.price <= 0) {
      return {
        success: false,
        message: 'Цена должна быть больше нуля',
        service: null,
      }
    }

    // СОЗДАНИЕ С ДОМЕННОЙ ИЗОЛЯЦИЕЙ
    const service = await prisma.fulfillmentService.create({
      data: {
        ...input,
        fulfillmentId: user.organization.id, // Автоматическая привязка
      },
      include: {
        fulfillment: true,
      },
    })

    return {
      success: true,
      message: 'Услуга успешно создана',
      service,
    }
  } catch (error) {
    console.error('Error creating fulfillment service:', error)
    return {
      success: false,
      message: 'Ошибка при создании услуги',
      service: null,
    }
  }
},
```

### БЕЗОПАСНОСТЬ РЕЗОЛВЕРОВ

#### ДОМЕННАЯ ИЗОЛЯЦИЯ:
```typescript
// ВСЕГДА ФИЛЬТРОВАТЬ ПО fulfillmentId
const services = await prisma.fulfillmentService.findMany({
  where: {
    fulfillmentId: user.organization.id, // ← КРИТИЧЕСКАЯ БЕЗОПАСНОСТЬ
    isActive: true,
  },
})
```

#### ВАЛИДАЦИЯ ДАННЫХ:
```typescript
// ПРОВЕРКА ОБЯЗАТЕЛЬНЫХ ПОЛЕЙ
if (!input.name?.trim()) {
  return { success: false, message: 'Название обязательно' }
}

// ПРОВЕРКА БИЗНЕС-ЛОГИКИ  
if (!input.price || input.price <= 0) {
  return { success: false, message: 'Цена должна быть больше нуля' }
}
```

#### ОБРАБОТКА ОШИБОК:
```typescript
try {
  // Логика резолвера
} catch (error) {
  console.error('Error in resolver:', error)
  return {
    success: false,
    message: 'Внутренняя ошибка сервера',
    [entity]: null,
  }
}
```

---

## 🎨 V2 FRONTEND КОМПОНЕНТЫ

### АРХИТЕКТУРА КОМПОНЕНТОВ

```
/src/components/services/
├── services-dashboard.tsx      # Главный dashboard с табами  
├── services-tab.tsx           # Управление услугами
├── supplies-tab.tsx           # Управление расходниками  
└── logistics-tab.tsx          # Управление логистикой
```

### ПАТТЕРН КОМПОНЕНТА V2

```typescript
// Пример: services-tab.tsx

'use client'

import { useQuery, useMutation } from '@apollo/client'
import { 
  GET_MY_FULFILLMENT_SERVICES_V2,
  CREATE_FULFILLMENT_SERVICE,
  UPDATE_FULFILLMENT_SERVICE,
  DELETE_FULFILLMENT_SERVICE 
} from '@/graphql/queries/fulfillment-services-v2'

export function ServicesTab() {
  // V2 QUERY
  const { data, loading, error, refetch } = useQuery(GET_MY_FULFILLMENT_SERVICES_V2, {
    fetchPolicy: 'cache-and-network',
  })
  
  // V2 MUTATIONS  
  const [createService] = useMutation(CREATE_FULFILLMENT_SERVICE, {
    update: (cache, { data }) => {
      if (data?.createFulfillmentService?.success) {
        // ОБНОВЛЕНИЕ APOLLO CACHE
        const existingData = cache.readQuery({ query: GET_MY_FULFILLMENT_SERVICES_V2 })
        if (existingData) {
          cache.writeQuery({
            query: GET_MY_FULFILLMENT_SERVICES_V2,
            data: {
              myFulfillmentServices: [
                ...existingData.myFulfillmentServices,
                data.createFulfillmentService.service
              ]
            }
          })
        }
      }
    }
  })
  
  // V2 DATA ACCESS
  const services = data?.myFulfillmentServices || []
  
  // UI ЛОГИКА ОСТАЕТСЯ НЕИЗМЕННОЙ
  return (
    <div className="services-management">
      {/* Рендер услуг */}
    </div>
  )
}
```

### URL МАРШРУТИЗАЦИЯ V2

```typescript
// services-dashboard.tsx - URL управление

import { usePathname, useRouter } from 'next/navigation'

export function ServicesDashboard() {
  const pathname = usePathname()
  const router = useRouter()
  
  // ОПРЕДЕЛЕНИЕ АКТИВНОГО ТАБА ПО URL
  const getActiveTab = () => {
    if (pathname.includes('/services/services')) return 'services'
    if (pathname.includes('/services/logistics')) return 'logistics'  
    if (pathname.includes('/services/consumables')) return 'consumables'
    return 'services'
  }
  
  // НАВИГАЦИЯ МЕЖДУ ТАБАМИ
  const handleTabChange = (tab: string) => {
    switch (tab) {
      case 'services':
        router.push('/fulfillment/services/services')
        break
      case 'consumables':
        router.push('/fulfillment/services/consumables')  
        break
      case 'logistics':
        router.push('/fulfillment/services/logistics')
        break
    }
  }
  
  return (
    <div className="services-dashboard">
      {/* Tab navigation с уникальными URL */}
    </div>
  )
}
```

### APOLLO CLIENT CACHE MANAGEMENT

```typescript
// Паттерн обновления кэша в V2

const [updateService] = useMutation(UPDATE_FULFILLMENT_SERVICE, {
  update: (cache, { data }) => {
    if (data?.updateFulfillmentService?.success && data.updateFulfillmentService.service) {
      
      // ОБНОВЛЕНИЕ СУЩЕСТВУЮЩИХ ДАННЫХ В КЭШЕ
      const existingData = cache.readQuery({ 
        query: GET_MY_FULFILLMENT_SERVICES_V2 
      }) as { myFulfillmentServices: FulfillmentService[] } | null
      
      if (existingData) {
        const updatedService = data.updateFulfillmentService.service
        
        cache.writeQuery({
          query: GET_MY_FULFILLMENT_SERVICES_V2,
          data: {
            myFulfillmentServices: existingData.myFulfillmentServices.map(service =>
              service.id === updatedService.id ? updatedService : service
            )
          }
        })
      }
    }
  }
})
```

---

## 🚀 ПРОИЗВОДИТЕЛЬНОСТЬ И ОПТИМИЗАЦИЯ

### ИНДЕКСЫ БД

```sql
-- ОСНОВНЫЕ ИНДЕКСЫ
CREATE INDEX "fulfillment_services_v2_fulfillmentId_isActive_idx" 
  ON "fulfillment_services_v2"("fulfillmentId", "isActive");

CREATE INDEX "fulfillment_consumables_v2_fulfillmentId_isAvailable_idx" 
  ON "fulfillment_consumables_v2"("fulfillmentId", "isAvailable");
  
CREATE INDEX "fulfillment_logistics_v2_fromLocation_toLocation_idx" 
  ON "fulfillment_logistics_v2"("fromLocation", "toLocation");
```

### APOLLO CLIENT ОПТИМИЗАЦИЯ

```typescript
// ОПТИМИЗИРОВАННЫЕ QUERY ПОЛИТИКИ
const { data } = useQuery(GET_MY_FULFILLMENT_SERVICES_V2, {
  fetchPolicy: 'cache-and-network',    // Быстрый отклик + актуальность
  errorPolicy: 'all',                  // Показ частичных данных при ошибках
  notifyOnNetworkStatusChange: true,   // UI обратная связь
})
```

### ПАГИНАЦИЯ (ГОТОВНОСТЬ)

```graphql
# РАСШИРЕННЫЕ QUERIES (для будущих версий)
type Query {
  fulfillmentServices(
    fulfillmentId: ID!
    first: Int
    after: String
    filter: FulfillmentServiceFilter
  ): FulfillmentServiceConnection!
}

type FulfillmentServiceConnection {
  edges: [FulfillmentServiceEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}
```

---

## 🔒 БЕЗОПАСНОСТЬ V2

### ДОМЕННАЯ ИЗОЛЯЦИЯ

```typescript
// ПРИНЦИП: Пользователь видит только свои данные
const services = await prisma.fulfillmentService.findMany({
  where: {
    fulfillmentId: user.organization.id, // ← КРИТИЧЕСКАЯ СТРОКА
    isActive: true,
  },
})
```

### ПРОВЕРКА ПРАВ ДОСТУПА

```typescript
// ДОПОЛНИТЕЛЬНАЯ ПРОВЕРКА ДЛЯ КРИТИЧЕСКИХ ОПЕРАЦИЙ
if (user.organization.type !== 'FULFILLMENT') {
  throw new Error('Доступ запрещен: требуется тип организации FULFILLMENT')
}
```

### ВАЛИДАЦИЯ ВХОДНЫХ ДАННЫХ

```typescript
// ЗАЩИТА ОТ НЕКОРРЕКТНЫХ ДАННЫХ
const validateServiceInput = (input: CreateFulfillmentServiceInput) => {
  if (!input.name?.trim()) {
    throw new Error('Название услуги обязательно')
  }
  
  if (input.name.length > 255) {
    throw new Error('Название слишком длинное')
  }
  
  if (input.price <= 0) {
    throw new Error('Цена должна быть положительной')
  }
  
  if (input.price > 1000000) {
    throw new Error('Цена слишком высокая')
  }
}
```

### САНИТИЗАЦИЯ ДАННЫХ

```typescript
// ОЧИСТКА ВХОДЯЩИХ ДАННЫХ  
const sanitizedInput = {
  ...input,
  name: input.name?.trim(),
  description: input.description?.trim() || null,
  price: parseFloat(input.price.toFixed(2)), // 2 знака после запятой
}
```

---

## 📈 МОНИТОРИНГ И ОТЛАДКА

### ЛОГИРОВАНИЕ ОПЕРАЦИЙ

```typescript  
// В резолверах
console.warn('V2 Service Operation:', {
  operation: 'createFulfillmentService',
  fulfillmentId: user.organization.id,
  serviceName: input.name,
  timestamp: new Date().toISOString(),
})
```

### ERROR HANDLING

```typescript
try {
  // Бизнес-логика
} catch (error) {
  // СТРУКТУРИРОВАННОЕ ЛОГИРОВАНИЕ ОШИБОК
  console.error('V2 Service Error:', {
    operation: 'createFulfillmentService',
    error: error.message,
    stack: error.stack,
    input: JSON.stringify(input),
    user: user.phone,
    timestamp: new Date().toISOString(),
  })
  
  // ПОЛЬЗОВАТЕЛЮ ПОКАЗЫВАЕМ БЕЗОПАСНОЕ СООБЩЕНИЕ
  return {
    success: false,
    message: 'Внутренняя ошибка сервера',
    service: null,
  }
}
```

### ПРОИЗВОДИТЕЛЬНОСТЬ МОНИТОРИНГ

```typescript
// ЗАМЕРЫ ВРЕМЕНИ ВЫПОЛНЕНИЯ
const startTime = Date.now()

const services = await prisma.fulfillmentService.findMany({
  where: { fulfillmentId: user.organization.id },
})

const endTime = Date.now()
console.warn('V2 Query Performance:', {
  operation: 'myFulfillmentServices',
  duration: `${endTime - startTime}ms`,
  resultCount: services.length,
})
```

---

## 🧪 ТЕСТИРОВАНИЕ V2

### UNIT ТЕСТЫ РЕЗОЛВЕРОВ

```typescript
// Пример теста резолвера
describe('FulfillmentServices V2 Resolvers', () => {
  describe('myFulfillmentServices', () => {
    it('should return only services for current fulfillment', async () => {
      const context = mockContext({ organizationId: 'fulfillment-1' })
      
      const result = await fulfillmentServicesQueries.myFulfillmentServices(
        {},
        {},
        context
      )
      
      expect(result).toHaveLength(2)
      result.forEach(service => {
        expect(service.fulfillmentId).toBe('fulfillment-1')
      })
    })
  })
})
```

### ИНТЕГРАЦИОННЫЕ ТЕСТЫ

```typescript
// Тест полного цикла CRUD
describe('Fulfillment Services Integration', () => {
  it('should create, read, update, delete service', async () => {
    // CREATE
    const createResult = await createFulfillmentService({
      name: 'Test Service',
      price: 100,
      unit: 'шт'
    })
    expect(createResult.success).toBe(true)
    
    // READ
    const services = await myFulfillmentServices()
    expect(services).toContainEqual(
      expect.objectContaining({ name: 'Test Service' })
    )
    
    // UPDATE
    const updateResult = await updateFulfillmentService({
      id: createResult.service.id,
      price: 150
    })
    expect(updateResult.success).toBe(true)
    
    // DELETE
    const deleteResult = await deleteFulfillmentService(createResult.service.id)
    expect(deleteResult).toBe(true)
  })
})
```

### E2E ТЕСТЫ КОМПОНЕНТОВ

```typescript
// Cypress тест UI
describe('Services Management V2', () => {
  it('should manage services through UI', () => {
    cy.visit('/fulfillment/services/services')
    
    // Создание услуги
    cy.get('[data-testid="add-service-btn"]').click()
    cy.get('[data-testid="service-name"]').type('Test Service')
    cy.get('[data-testid="service-price"]').type('100')
    cy.get('[data-testid="save-btn"]').click()
    
    // Проверка создания
    cy.contains('Test Service').should('exist')
    cy.contains('100 ₽').should('exist')
  })
})
```

---

## 📚 РУКОВОДСТВО ПО РАСШИРЕНИЮ V2

### ДОБАВЛЕНИЕ НОВОГО ПОЛЯ

#### 1. ОБНОВИТЬ PRISMA МОДЕЛЬ:
```prisma
model FulfillmentService {
  // ... существующие поля
  newField       String?      // Новое поле
}
```

#### 2. ДОБАВИТЬ В GRAPHQL СХЕМУ:
```graphql
type FulfillmentService {
  # ... существующие поля
  newField: String
}

input CreateFulfillmentServiceInput {
  # ... существующие поля  
  newField: String
}
```

#### 3. ОБНОВИТЬ РЕЗОЛВЕРЫ:
```typescript
createFulfillmentService: async (_, { input }, context) => {
  const service = await prisma.fulfillmentService.create({
    data: {
      ...input,
      newField: input.newField, // ← Добавить обработку
      fulfillmentId: user.organization.id,
    },
  })
}
```

#### 4. ОБНОВИТЬ UI КОМПОНЕНТЫ:
```typescript
// В forms
<Input 
  name="newField"
  value={formData.newField}
  onChange={handleInputChange}
/>

// В отображении
<span>{service.newField}</span>
```

### ДОБАВЛЕНИЕ НОВОГО ТИПА УСЛУГ

#### 1. СОЗДАТЬ НОВУЮ PRISMA МОДЕЛЬ:
```prisma
model FulfillmentNewType {
  id               String       @id @default(cuid())
  fulfillmentId    String
  // ... специфичные поля
  
  fulfillment      Organization @relation("FulfillmentNewTypeV2", fields: [fulfillmentId], references: [id])
  
  @@index([fulfillmentId])
  @@map("fulfillment_new_type_v2")
}
```

#### 2. СОЗДАТЬ РЕЗОЛВЕРЫ:
```typescript
// fulfillment-new-type-v2.ts
export const fulfillmentNewTypeQueries = {
  myFulfillmentNewType: async (_, __, context) => {
    // Логика запроса
  },
}

export const fulfillmentNewTypeMutations = {
  createFulfillmentNewType: async (_, { input }, context) => {
    // Логика создания
  },
}
```

#### 3. ПОДКЛЮЧИТЬ К ОСНОВНЫМ РЕЗОЛВЕРАМ:
```typescript
// В index.ts
import { fulfillmentNewTypeQueries, fulfillmentNewTypeMutations } from './fulfillment-new-type-v2'

const mergedResolvers = mergeResolvers(
  // ... существующие резолверы
  {
    Query: fulfillmentNewTypeQueries,
    Mutation: fulfillmentNewTypeMutations,
  },
)
```

#### 4. СОЗДАТЬ UI КОМПОНЕНТ:
```typescript
// new-type-tab.tsx
export function NewTypeTab() {
  const { data } = useQuery(GET_MY_FULFILLMENT_NEW_TYPE_V2)
  const newTypeItems = data?.myFulfillmentNewType || []
  
  return (
    <div className="new-type-management">
      {/* UI для управления новым типом */}
    </div>
  )
}
```

### ИНТЕГРАЦИЯ С ВНЕШНИМИ СИСТЕМАМИ

```typescript
// Пример: интеграция с внешним API
const createFulfillmentServiceWithExternalSync = async (input, context) => {
  // 1. Создать в локальной БД
  const service = await prisma.fulfillmentService.create({
    data: { ...input, fulfillmentId: context.user.organization.id }
  })
  
  // 2. Синхронизировать с внешней системой
  try {
    await ExternalAPI.syncService(service)
  } catch (error) {
    console.warn('External sync failed:', error)
    // Продолжаем работу без внешней синхронизации
  }
  
  return { success: true, service }
}
```

---

## 🎯 ЛУЧШИЕ ПРАКТИКИ V2

### 1. ИМЕНОВАНИЕ
- **Модели**: `FulfillmentServiceType` - ясное доменное имя
- **Резолверы**: `myFulfillmentServices` - владение + тип данных
- **Файлы**: `fulfillment-services-v2.ts` - домен + версия

### 2. СТРУКТУРА ДАННЫХ
- **Всегда включать `fulfillmentId`** для доменной изоляции  
- **Использовать `Decimal`** для денежных полей
- **Добавлять `createdAt/updatedAt`** для аудита
- **Использовать `isActive`** вместо жесткого удаления

### 3. БЕЗОПАСНОСТЬ
- **Фильтровать по `fulfillmentId`** во всех запросах
- **Валидировать входные данные** на уровне резолверов
- **Логировать критические операции**
- **Использовать `try/catch`** с корректной обработкой ошибок

### 4. ПРОИЗВОДИТЕЛЬНОСТЬ  
- **Добавлять индексы** на часто используемые поля
- **Использовать `include`** вместо отдельных запросов
- **Применять `cache-and-network`** политику Apollo
- **Мониторить время выполнения** запросов

### 5. ПОДДЕРЖИВАЕМОСТЬ
- **Документировать сложную логику**
- **Писать unit тесты** для резолверов
- **Использовать TypeScript** для типизации
- **Следовать принципу единой ответственности**

---

## 🔮 ROADMAP V2

### ЗАПЛАНИРОВАННЫЕ УЛУЧШЕНИЯ:

1. **Q4 2025**: Пагинация для больших списков
2. **Q1 2026**: Поиск и фильтрация в GraphQL
3. **Q2 2026**: Bulk операции (массовое создание/обновление)
4. **Q3 2026**: Интеграция с внешними системами учета
5. **Q4 2026**: Аналитика и отчеты по услугам

### ПОТЕНЦИАЛЬНЫЕ РАСШИРЕНИЯ:

- **Версионирование услуг** - отслеживание изменений цен
- **Категоризация услуг** - группировка по типам  
- **Шаблоны услуг** - быстрое создание похожих услуг
- **Интеграция с календарем** - планирование предоставления услуг
- **Система скидок** - гибкое ценообразование

---

## 📖 ЗАКЛЮЧЕНИЕ

V2 архитектура услуг SFERA представляет собой современное, масштабируемое и безопасное решение для управления услугами фулфилмента. Ключевые достижения:

**🎯 АРХИТЕКТУРНЫЕ ПРИНЦИПЫ:**
- Доменная специализация моделей данных
- Модульность резолверов и компонентов  
- Безопасность на уровне доменов
- Производительность через оптимизированные индексы

**🚀 ТЕХНИЧЕСКИЕ ПРЕИМУЩЕСТВА:**
- Полная типизация TypeScript
- Оптимизированные GraphQL запросы
- Эффективный Apollo Client кэш
- Comprehensive error handling

**🛡️ ГОТОВНОСТЬ К PRODUCTION:**
- Тщательное тестирование (Unit + Integration + E2E)
- Monitoring и logging
- Безопасная обработка ошибок
- Документированные API

Данная архитектура служит эталоном для будущих V2 систем SFERA и может быть адаптирована для других доменов платформы.

---

_Документ создан: 03.09.2025_  
_Версия: V2.0_  
_Статус: Production Ready_  
_Команда: SFERA Development Team_