# GRAPHQL API ДОКУМЕНТАЦИЯ ## 🎯 ОБЗОР API SFERA GraphQL API предоставляет единую точку входа для всех операций системы. API использует строгую типизацию, контекстную аутентификацию через JWT токены и поддерживает real-time подписки для мгновенных обновлений. ### Основной endpoint ``` POST /api/graphql ``` ### Заголовки аутентификации ```http Authorization: Bearer Content-Type: application/json ``` ## 📊 ОСНОВНЫЕ ТИПЫ (TYPES) ### User ```graphql type User { id: ID! # CUID уникальный идентификатор phone: String! # Телефон для входа avatar: String # URL аватара managerName: String # Имя менеджера organization: Organization # Связанная организация createdAt: DateTime! # Дата регистрации updatedAt: DateTime! # Дата обновления } ``` ### Organization ```graphql type Organization { id: ID! inn: String! # ИНН организации (уникальный) kpp: String # КПП name: String # Краткое название fullName: String # Полное юридическое название ogrn: String # ОГРН type: OrganizationType! # Тип организации # Контактная информация address: String phones: [Phone!] emails: [Email!] # Финансовая информация revenue: Float # Годовая выручка employeeCount: Int # Количество сотрудников taxSystem: String # Система налогообложения # Реферальная система referralCode: String # Уникальный реф. код referralPoints: Int # Накопленные баллы # Связи users: [User!] apiKeys: [ApiKey!] products: [Product!] employees: [Employee!] services: [Service!] createdAt: DateTime! updatedAt: DateTime! } ``` ### Product (Товар) ```graphql type Product { id: ID! name: String! # Название товара article: String! # Артикул (уникальный в рамках организации) description: String price: Float! # Цена за единицу pricePerSet: Float # Цена за комплект # Остатки и резервы quantity: Int! # Доступно для заказа ordered: Int # Зарезервировано в заказах inTransit: Int # В пути stock: Int # Физический остаток на складе sold: Int # Продано всего # Характеристики brand: String color: String size: String weight: Float # Вес в кг dimensions: String # Габариты material: String # Материал # Медиа images: [String!] # Массив URL изображений mainImage: String # Основное изображение # Метаданные category: Category # Категория товара organization: Organization! # Организация-поставщик isActive: Boolean # Активность товара createdAt: DateTime! updatedAt: DateTime! } ``` ### Supply (Расходные материалы) ```graphql type Supply { id: ID! name: String! # Название расходника article: String! # Артикул СФ для уникальности description: String # Цены и количество price: Float! # Общая цена pricePerUnit: Float # Цена за единицу quantity: Int! # Общее количество unit: String! # Единица измерения (шт, кг, м) # Остатки minStock: Int # Минимальный остаток currentStock: Int # Текущий остаток usedStock: Int # Использовано # Классификация category: String # Категория (Расходники, Материалы) supplier: String # Поставщик type: SupplyType! # FULFILLMENT_CONSUMABLES | SELLER_CONSUMABLES # Владение (для селлерских расходников) sellerOwnerId: ID # ID селлера-владельца sellerOwner: Organization # Организация-владелец shopLocation: String # Расположение магазина imageUrl: String status: String # planned, ordered, delivered date: DateTime # Дата поставки organization: Organization! # Организация-владелец createdAt: DateTime! updatedAt: DateTime! } ``` ### SupplyOrder (Заказ поставки) ```graphql type SupplyOrder { id: ID! organizationId: ID! # Организация-заказчик partnerId: ID! # Организация-поставщик partner: Organization! deliveryDate: DateTime! # Дата доставки status: SupplyOrderStatus! # Статус заказа # Суммарная информация totalAmount: Float! # Общая сумма totalItems: Int! # Общее количество товаров # Многоуровневая система packagesCount: Int # Количество грузовых мест volume: Float # Объём в м³ responsibleEmployee: String # ID ответственного сотрудника notes: String # Примечания # Логистика fulfillmentCenterId: ID # ID фулфилмент-центра fulfillmentCenter: Organization logisticsPartnerId: ID # ID логистической компании logisticsPartner: Organization # Позиции заказа items: [SupplyOrderItem!]! routes: [SupplyRoute!] # Маршруты доставки createdAt: DateTime! updatedAt: DateTime! # Информация о процессе processInfo: SupplyOrderProcessInfo } ``` ### Message (Сообщение) ```graphql type Message { id: ID! content: String # Текст сообщения type: MessageType! # TEXT | VOICE | IMAGE | FILE # Голосовые сообщения voiceUrl: String # URL аудиофайла voiceDuration: Int # Длительность в секундах # Файловые вложения fileUrl: String # URL файла fileName: String # Оригинальное название fileSize: Int # Размер в байтах fileType: String # MIME тип # Участники senderId: ID! sender: User! senderOrganizationId: ID! senderOrganization: Organization! receiverOrganizationId: ID! receiverOrganization: Organization! isRead: Boolean! # Статус прочтения createdAt: DateTime! updatedAt: DateTime! } ``` ### Employee (Сотрудник) ```graphql type Employee { id: ID! firstName: String! lastName: String! middleName: String birthDate: DateTime # Документы avatar: String # Фото сотрудника passportPhoto: String # Фото паспорта passportSeries: String # Серия паспорта passportNumber: String # Номер паспорта passportIssued: String # Кем выдан passportDate: DateTime # Дата выдачи address: String # Адрес регистрации # Рабочая информация position: String! # Должность department: String # Отдел hireDate: DateTime! # Дата приема salary: Float # Зарплата status: EmployeeStatus! # ACTIVE | VACATION | SICK | FIRED # Контакты phone: String! email: String telegram: String whatsapp: String emergencyContact: String # Экстренный контакт emergencyPhone: String # Телефон экстренного контакта # Связи organization: Organization! scheduleRecords: [EmployeeSchedule!]! createdAt: DateTime! updatedAt: DateTime! } ``` ## 🔍 QUERIES (ЗАПРОСЫ) ### Аутентификация и профиль ```graphql # Текущий пользователь query Me { me { id phone avatar managerName organization { id inn name type } } } # Данные организации query GetOrganization($id: ID!) { organization(id: $id) { id inn kpp name fullName type address phones { value label } emails { value label } users { id phone managerName } } } ``` ### Контрагенты и партнеры ```graphql # Поиск организаций для партнерства query SearchOrganizations($type: OrganizationType, $search: String) { searchOrganizations(type: $type, search: $search) { id inn name fullName type } } # Мои контрагенты query MyCounterparties { myCounterparties { id inn name fullName type phones { value } emails { value } } } # Входящие заявки на партнерство query IncomingRequests { incomingRequests { id status message sender { id name inn type } createdAt } } ``` ### Сообщения и чаты ```graphql # Список бесед query GetConversations { conversations { id counterparty { id name fullName type avatar } lastMessage { id content type senderId isRead createdAt } unreadCount updatedAt } } # История сообщений query GetMessages($counterpartyId: ID!, $limit: Int = 50, $offset: Int = 0) { messages(counterpartyId: $counterpartyId, limit: $limit, offset: $offset) { id content type voiceUrl voiceDuration fileUrl fileName fileSize fileType isRead senderId senderOrganizationId createdAt sender { id phone avatar } senderOrganization { id name fullName type } } } ``` ### Товары и каталог ```graphql # Мои товары (для поставщика) query MyProducts { myProducts { id name article description price quantity ordered inTransit stock brand images mainImage category { id name } isActive } } # Все товары для маркета query AllProducts($search: String, $category: String) { allProducts(search: $search, category: $category) { id name article price quantity images mainImage organization { id name inn type } category { id name } } } # Категории товаров query GetCategories { categories { id name createdAt updatedAt } } ``` ### Корзина и избранное ```graphql # Моя корзина query GetMyCart { myCart { id totalPrice totalItems items { id quantity totalPrice isAvailable availableQuantity product { id name article price quantity images mainImage organization { id name fullName inn } } } } } # Избранные товары query GetMyFavorites { myFavorites { id name article price quantity images mainImage isActive organization { id name fullName inn type } category { id name } } } ``` ### Расходные материалы ```graphql # Расходники фулфилмента query MyFulfillmentSupplies { myFulfillmentSupplies { id name article description price pricePerUnit quantity unit category minStock currentStock usedStock supplier type imageUrl status } } # Расходники селлеров на складе query SellerSuppliesOnWarehouse { sellerSuppliesOnWarehouse { id name article quantity unit currentStock sellerOwnerId sellerOwner { id name fullName } shopLocation } } # Доступные расходники для рецептур query GetAvailableSuppliesForRecipe { getAvailableSuppliesForRecipe { id name pricePerUnit unit imageUrl quantity } } ``` ### Заказы поставок ```graphql # Мои заказы поставок (многоуровневая таблица) query MySupplyOrders { mySupplyOrders { id partnerId partner { id name fullName type } deliveryDate status totalAmount totalItems packagesCount volume responsibleEmployee notes fulfillmentCenter { id name } logisticsPartner { id name } items { id productId product { id name article } quantity price totalPrice } routes { id fromLocation toLocation fromAddress toAddress distance estimatedTime price status } processInfo { role supplier fulfillmentCenter logistics status } } } # Счетчик ожидающих поставок query PendingSuppliesCount { pendingSuppliesCount { pendingOrders supplierPending logisticsOrders incomingRequests total } } ``` ### Сотрудники ```graphql # Список сотрудников query MyEmployees { myEmployees { id firstName lastName middleName position department status phone email avatar hireDate salary } } # Табель сотрудника query EmployeeSchedule($employeeId: ID!, $year: Int!, $month: Int!) { employeeSchedule(employeeId: $employeeId, year: $year, month: $month) { id date status hoursWorked overtimeHours notes } } ``` ### Логистика ```graphql # Мои логистические маршруты query MyLogistics { myLogistics { id fromLocation toLocation priceUnder1m3 priceOver1m3 description } } # Партнеры-логисты query LogisticsPartners { logisticsPartners { id inn name fullName address phones { value } } } ``` ### Склад (3-уровневая иерархия) ```graphql # Данные склада с вложенной структурой query WarehouseData { warehouseData { entries { id partner { id name fullName type } products { id productName productQuantity productPlace variants { id variantName variantQuantity variantPlace } } totalProducts totalQuantity totalValue lastUpdated } statistics { totalPartners totalProducts totalQuantity totalValue movements { arrived { value change percentChange } departed { value change percentChange } } } } } ``` ## ✏️ MUTATIONS (МУТАЦИИ) ### Аутентификация ```graphql # Отправка SMS кода mutation SendSmsCode($phone: String!) { sendSmsCode(phone: $phone) { success message } } # Верификация SMS кода mutation VerifySmsCode($phone: String!, $code: String!) { verifySmsCode(phone: $phone, code: $code) { success message token user { id phone organization { id type } } } } # Выход из системы mutation Logout { logout } ``` ### Регистрация организаций ```graphql # Регистрация фулфилмент-центра mutation RegisterFulfillment($input: FulfillmentRegistrationInput!) { registerFulfillmentOrganization(input: $input) { success message token user { id phone organization { id inn name type } } } } # Input для регистрации фулфилмента input FulfillmentRegistrationInput { inn: String! serviceType: String! managerName: String! referralCode: String } # Регистрация селлера mutation RegisterSeller($input: SellerRegistrationInput!) { registerSellerOrganization(input: $input) { success message token user { id phone organization { id inn name type } } } } # Input для регистрации селлера input SellerRegistrationInput { inn: String! hasOwnWarehouse: Boolean! managerName: String! referralCode: String } ``` ### Управление профилем ```graphql # Обновление профиля пользователя mutation UpdateUserProfile($input: UpdateUserProfileInput!) { updateUserProfile(input: $input) { success message user { id avatar managerName } } } input UpdateUserProfileInput { avatar: String managerName: String } # Обновление данных организации mutation UpdateOrganizationByInn($inn: String!) { updateOrganizationByInn(inn: $inn) { success message organization { id inn kpp name fullName ogrn address } } } ``` ### Управление контрагентами ```graphql # Отправка заявки на партнерство mutation SendCounterpartyRequest($organizationId: ID!, $message: String) { sendCounterpartyRequest(organizationId: $organizationId, message: $message) { success message request { id status message } } } # Ответ на заявку mutation RespondToRequest($requestId: ID!, $accept: Boolean!) { respondToCounterpartyRequest(requestId: $requestId, accept: $accept) { success message request { id status } } } # Удаление контрагента mutation RemoveCounterparty($organizationId: ID!) { removeCounterparty(organizationId: $organizationId) } ``` ### Сообщения ```graphql # Отправка текстового сообщения mutation SendMessage($receiverOrganizationId: ID!, $content: String!) { sendMessage(receiverOrganizationId: $receiverOrganizationId, content: $content) { success message messageData { id content type createdAt isRead } } } # Отправка голосового сообщения mutation SendVoiceMessage($receiverOrganizationId: ID!, $voiceUrl: String!, $voiceDuration: Int!) { sendVoiceMessage( receiverOrganizationId: $receiverOrganizationId voiceUrl: $voiceUrl voiceDuration: $voiceDuration ) { success message messageData { id voiceUrl voiceDuration type createdAt } } } # Отправка файла mutation SendFileMessage( $receiverOrganizationId: ID! $fileUrl: String! $fileName: String! $fileSize: Int! $fileType: String! ) { sendFileMessage( receiverOrganizationId: $receiverOrganizationId fileUrl: $fileUrl fileName: $fileName fileSize: $fileSize fileType: $fileType ) { success message messageData { id fileUrl fileName fileSize fileType type createdAt } } } # Отметить сообщения как прочитанные mutation MarkMessagesAsRead($conversationId: ID!) { markMessagesAsRead(conversationId: $conversationId) } ``` ### Управление товарами ```graphql # Создание товара mutation CreateProduct($input: ProductInput!) { createProduct(input: $input) { success message product { id name article price quantity } } } input ProductInput { name: String! article: String! description: String price: Float! pricePerSet: Float quantity: Int! setQuantity: Int categoryId: ID brand: String color: String size: String weight: Float dimensions: String material: String images: [String!] mainImage: String isActive: Boolean } # Обновление товара mutation UpdateProduct($id: ID!, $input: ProductInput!) { updateProduct(id: $id, input: $input) { success message product { id name article price quantity } } } # Проверка уникальности артикула mutation CheckArticleUniqueness($article: String!, $excludeId: ID) { checkArticleUniqueness(article: $article, excludeId: $excludeId) { isUnique existingProduct { id name article } } } # Управление резервами товара mutation ReserveProductStock($productId: ID!, $quantity: Int!) { reserveProductStock(productId: $productId, quantity: $quantity) { success message product { id quantity ordered } } } ``` ### Корзина и избранное ```graphql # Добавление в корзину mutation AddToCart($productId: ID!, $quantity: Int = 1) { addToCart(productId: $productId, quantity: $quantity) { success message cartItem { id quantity totalPrice product { id name price } } } } # Обновление количества в корзине mutation UpdateCartItem($productId: ID!, $quantity: Int!) { updateCartItem(productId: $productId, quantity: $quantity) { success message cartItem { id quantity totalPrice } } } # Удаление из корзины mutation RemoveFromCart($productId: ID!) { removeFromCart(productId: $productId) { success message } } # Очистка корзины mutation ClearCart { clearCart } # Добавление в избранное mutation AddToFavorites($productId: ID!) { addToFavorites(productId: $productId) { success message favorite { id productId organizationId createdAt } } } # Удаление из избранного mutation RemoveFromFavorites($productId: ID!) { removeFromFavorites(productId: $productId) { success message } } ``` ### Заказы поставок ```graphql # Создание заказа поставки mutation CreateSupplyOrder($input: SupplyOrderInput!) { createSupplyOrder(input: $input) { success message order { id status totalAmount totalItems deliveryDate partner { id name } } processInfo { role supplier fulfillmentCenter logistics status } } } input SupplyOrderInput { partnerId: ID! deliveryDate: DateTime! consumableType: String items: [SupplyOrderItemInput!]! routes: [SupplyRouteInput!] } input SupplyOrderItemInput { productId: ID! quantity: Int! price: Float! recipe: ProductRecipeInput } input ProductRecipeInput { services: [ID!] fulfillmentConsumables: [ID!] sellerConsumables: [ID!] marketplaceCardId: String } # Действия поставщика mutation SupplierApproveOrder($id: ID!) { supplierApproveOrder(id: $id) { success message order { id status } } } # Поставщик одобряет с упаковкой mutation SupplierApproveWithPackaging($id: ID!, $packagesCount: Int, $volume: Float) { supplierApproveOrderWithPackaging(id: $id, packagesCount: $packagesCount, volume: $volume) { success message order { id status packagesCount volume } } } # Действия логиста mutation LogisticsConfirmOrder($id: ID!) { logisticsConfirmOrder(id: $id) { success message order { id status } } } # Действия фулфилмента mutation FulfillmentReceiveOrder($id: ID!) { fulfillmentReceiveOrder(id: $id) { success message order { id status } } } # Назначение ответственного сотрудника mutation FulfillmentAssignEmployee($supplyOrderId: ID!, $employeeId: ID!) { fulfillmentAssignEmployee(supplyOrderId: $supplyOrderId, employeeId: $employeeId) { success message order { id responsibleEmployee } } } ``` ### Сотрудники ```graphql # Создание сотрудника mutation CreateEmployee($input: CreateEmployeeInput!) { createEmployee(input: $input) { success message employee { id firstName lastName position status } } } input CreateEmployeeInput { firstName: String! lastName: String! middleName: String birthDate: DateTime passportSeries: String passportNumber: String passportIssued: String passportDate: DateTime address: String position: String! department: String hireDate: DateTime! salary: Float phone: String! email: String telegram: String whatsapp: String emergencyContact: String emergencyPhone: String } # Обновление расписания сотрудника mutation UpdateEmployeeSchedule($input: UpdateScheduleInput!) { updateEmployeeSchedule(input: $input) } input UpdateScheduleInput { employeeId: ID! date: DateTime! status: ScheduleStatus! hoursWorked: Float overtimeHours: Float notes: String } ``` ### Расходные материалы ```graphql # Обновление цены расходника mutation UpdateSupplyPrice($id: ID!, $input: UpdateSupplyPriceInput!) { updateSupplyPrice(id: $id, input: $input) { success message supply { id price pricePerUnit } } } input UpdateSupplyPriceInput { price: Float! pricePerUnit: Float } # Использование расходников фулфилмента mutation UseFulfillmentSupplies($input: UseFulfillmentSuppliesInput!) { useFulfillmentSupplies(input: $input) { success message supply { id currentStock usedStock } } } input UseFulfillmentSuppliesInput { supplyId: ID! quantityUsed: Int! notes: String } ``` ## 🔤 ENUMS (ПЕРЕЧИСЛЕНИЯ) ### OrganizationType ```graphql enum OrganizationType { FULFILLMENT # Фулфилмент-центр SELLER # Продавец/Селлер LOGIST # Логистическая компания WHOLESALE # Оптовый поставщик } ``` ### MarketplaceType ```graphql enum MarketplaceType { WILDBERRIES # Wildberries OZON # Ozon } ``` ### MessageType ```graphql enum MessageType { TEXT # Текстовое сообщение VOICE # Голосовое сообщение IMAGE # Изображение FILE # Файл } ``` ### SupplyType ```graphql enum SupplyType { FULFILLMENT_CONSUMABLES # Расходники фулфилмента SELLER_CONSUMABLES # Расходники селлеров } ``` ### SupplyOrderStatus ```graphql enum SupplyOrderStatus { PENDING # Ожидает одобрения поставщика SUPPLIER_APPROVED # Поставщик одобрил LOGISTICS_CONFIRMED # Логистика подтверждена SHIPPED # Отправлено DELIVERED # Доставлено CANCELLED # Отменено } ``` ### EmployeeStatus ```graphql enum EmployeeStatus { ACTIVE # Активный сотрудник VACATION # В отпуске SICK # На больничном FIRED # Уволен } ``` ### ScheduleStatus ```graphql enum ScheduleStatus { WORK # Рабочий день WEEKEND # Выходной VACATION # Отпуск SICK # Больничный ABSENT # Отсутствие } ``` ### CounterpartyRequestStatus ```graphql enum CounterpartyRequestStatus { PENDING # Ожидает ответа ACCEPTED # Принята REJECTED # Отклонена CANCELLED # Отменена } ``` ### ReferralTransactionType ```graphql enum ReferralTransactionType { REGISTRATION # Регистрация по реф. ссылке AUTO_PARTNERSHIP # Автоматическое партнерство FIRST_ORDER # Первый заказ реферала MONTHLY_BONUS # Ежемесячный бонус } ``` ## 🛡️ АУТЕНТИФИКАЦИЯ И АВТОРИЗАЦИЯ ### JWT Token Structure ```json { "userId": "cuid_string", "organizationId": "cuid_string", "organizationType": "FULFILLMENT", "iat": 1234567890, "exp": 1234567890 } ``` ### Context в резолверах ```typescript interface Context { user?: { id: string organizationId: string organizationType: OrganizationType } isAdmin?: boolean } ``` ### Проверка прав доступа ```typescript // Пример резолвера с проверкой авторизации const resolvers = { Query: { myProducts: async (parent, args, context) => { // Проверка аутентификации if (!context.user) { throw new GraphQLError('Необходима авторизация') } // Проверка типа организации if (context.user.organizationType !== 'WHOLESALE') { throw new GraphQLError('Доступно только для поставщиков') } // Логика запроса... }, }, } ``` ## 🔄 ПОДПИСКИ (SUBSCRIPTIONS) > **Примечание**: Подписки находятся в разработке и будут доступны в следующих версиях API. ### Планируемые подписки ```graphql # Новые сообщения subscription OnNewMessage($organizationId: ID!) { messageReceived(organizationId: $organizationId) { id content type senderId senderOrganization { id name } createdAt } } # Обновления статуса заказа subscription OnOrderStatusChange($organizationId: ID!) { orderStatusChanged(organizationId: $organizationId) { id status updatedAt } } # Новые заявки на партнерство subscription OnNewCounterpartyRequest($organizationId: ID!) { counterpartyRequestReceived(organizationId: $organizationId) { id status message sender { id name inn } } } ``` ## 📝 ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ ### Полный флоу авторизации ```typescript // 1. Отправка SMS кода const sendSms = await apolloClient.mutate({ mutation: SEND_SMS_CODE, variables: { phone: '+79001234567' }, }) // 2. Верификация кода const verify = await apolloClient.mutate({ mutation: VERIFY_SMS_CODE, variables: { phone: '+79001234567', code: '1234', }, }) // 3. Сохранение токена localStorage.setItem('authToken', verify.data.verifySmsCode.token) // 4. Получение профиля const profile = await apolloClient.query({ query: GET_ME, }) ``` ### Создание заказа поставки с рецептурой ```typescript const createOrder = await apolloClient.mutate({ mutation: CREATE_SUPPLY_ORDER, variables: { input: { partnerId: 'partner_id', deliveryDate: '2024-01-15', items: [ { productId: 'product_id', quantity: 100, price: 50.0, recipe: { services: ['service_id_1', 'service_id_2'], fulfillmentConsumables: ['consumable_id_1'], sellerConsumables: ['consumable_id_2'], marketplaceCardId: 'wb_card_123', }, }, ], }, }, }) ``` ### Отправка сообщения с файлом ```typescript // 1. Загрузка файла на сервер const formData = new FormData() formData.append('file', fileBlob) const uploadResponse = await fetch('/api/upload-file', { method: 'POST', body: formData, headers: { Authorization: `Bearer ${token}`, }, }) const { fileUrl } = await uploadResponse.json() // 2. Отправка сообщения с файлом const sendFile = await apolloClient.mutate({ mutation: SEND_FILE_MESSAGE, variables: { receiverOrganizationId: 'org_id', fileUrl, fileName: 'document.pdf', fileSize: 1024000, fileType: 'application/pdf', }, }) ``` ## 🔍 ERROR HANDLING ### Стандартные коды ошибок ```typescript // Ошибки аутентификации { "code": "UNAUTHENTICATED", "message": "Необходима авторизация" } // Ошибки авторизации { "code": "FORBIDDEN", "message": "Недостаточно прав доступа" } // Ошибки валидации { "code": "BAD_USER_INPUT", "message": "Неверный формат данных" } // Бизнес-логика ошибки { "code": "BUSINESS_RULE_VIOLATION", "message": "Недостаточно товара на складе" } ``` ### Обработка ошибок на клиенте ```typescript try { const result = await apolloClient.mutate({ mutation: CREATE_PRODUCT, variables: { input: productData }, }) } catch (error) { if (error.graphQLErrors?.length > 0) { // GraphQL ошибки const message = error.graphQLErrors[0].message toast.error(message) } else if (error.networkError) { // Сетевые ошибки toast.error('Ошибка соединения') } } ``` ## 📊 ПАГИНАЦИЯ И ФИЛЬТРАЦИЯ ### Стандартные параметры пагинации ```graphql # limit - количество записей (по умолчанию 20) # offset - смещение от начала query GetProducts($limit: Int = 20, $offset: Int = 0) { allProducts(limit: $limit, offset: $offset) { id name # ... } } ``` ### Фильтрация и поиск ```graphql # search - текстовый поиск # category - фильтр по категории # type - фильтр по типу query SearchProducts($search: String, $category: String, $type: String, $limit: Int = 20) { organizationProducts(search: $search, category: $category, type: $type, limit: $limit) { id name article # ... } } ``` ## 🚀 BEST PRACTICES ### 1. Используйте фрагменты для переиспользования ```graphql fragment ProductBasicInfo on Product { id name article price quantity } query GetProducts { myProducts { ...ProductBasicInfo description images } } ``` ### 2. Минимизируйте количество запросов ```graphql # Плохо - несколько запросов query GetUser { me { id } } query GetOrg { organization(id: $id) { name } } # Хорошо - один запрос query GetProfile { me { id organization { id name } } } ``` ### 3. Используйте переменные для динамических значений ```graphql # Плохо - конкатенация строк query { product(id: "123") { name } } # Хорошо - переменные query GetProduct($id: ID!) { product(id: $id) { name } } ``` ### 4. Обрабатывайте loading и error состояния ```typescript const { data, loading, error } = useQuery(GET_PRODUCTS) if (loading) return if (error) return return ``` --- _GraphQL API документация обновлена на основе анализа src/graphql/typedefs.ts_ _Версия API: 1.0.0_ _Последнее обновление: 2025-08-21_