feat(fulfillment-supplies): миграция формы создания поставок расходников на v2 систему

- Обновлена форма создания поставок расходников фулфилмента для использования v2 GraphQL API
- Заменена мутация CREATE_SUPPLY_ORDER на CREATE_FULFILLMENT_CONSUMABLE_SUPPLY
- Обновлена структура input данных под новый формат v2
- Сделано поле логистики опциональным
- Добавлено поле notes для комментариев к поставке
- Обновлены refetchQueries на новые v2 запросы
- Исправлены TypeScript ошибки в интерфейсах
- Удалена дублирующая страница consumables-v2
- Сохранен оригинальный богатый UI интерфейс формы (819 строк)
- Подтверждена работа с новой таблицей FulfillmentConsumableSupplyOrder

Технические изменения:
- src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx - основная форма
- src/components/fulfillment-supplies/fulfillment-supplies-layout.tsx - обновлена навигация
- Добавлены недостающие поля quantity и ordered в интерфейсы продуктов
- Исправлены импорты и зависимости

Результат: форма полностью интегрирована с v2 системой поставок, которая использует отдельные таблицы для каждого типа поставок согласно новой архитектуре.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-25 07:52:46 +03:00
parent d05f0a6a93
commit 0e3ffc179c
34 changed files with 5795 additions and 565 deletions

View File

@ -0,0 +1,268 @@
import { GraphQLError } from 'graphql'
import { Context } from '../context'
import { prisma } from '@/lib/prisma'
import { notifyOrganization } from '@/lib/realtime'
export const fulfillmentConsumableV2Queries = {
myFulfillmentConsumableSupplies: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
try {
const user = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true },
})
if (!user?.organization || user.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент-центров')
}
const supplies = await prisma.fulfillmentConsumableSupplyOrder.findMany({
where: {
fulfillmentCenterId: user.organizationId!,
},
include: {
fulfillmentCenter: true,
supplier: true,
logisticsPartner: true,
receivedBy: true,
items: {
include: {
product: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
})
return supplies
} catch (error) {
console.error('Error fetching fulfillment consumable supplies:', error)
return [] // Возвращаем пустой массив вместо throw
}
},
fulfillmentConsumableSupply: async (_: unknown, args: { id: string }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
try {
const user = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true },
})
if (!user?.organization) {
throw new GraphQLError('Организация не найдена')
}
const supply = await prisma.fulfillmentConsumableSupplyOrder.findUnique({
where: { id: args.id },
include: {
fulfillmentCenter: true,
supplier: true,
logisticsPartner: true,
receivedBy: true,
items: {
include: {
product: true,
},
},
},
})
if (!supply) {
throw new GraphQLError('Поставка не найдена')
}
// Проверка доступа
if (
user.organization.type === 'FULFILLMENT' &&
supply.fulfillmentCenterId !== user.organizationId
) {
throw new GraphQLError('Нет доступа к этой поставке')
}
if (
user.organization.type === 'WHOLESALE' &&
supply.supplierId !== user.organizationId
) {
throw new GraphQLError('Нет доступа к этой поставке')
}
return supply
} catch (error) {
console.error('Error fetching fulfillment consumable supply:', error)
throw new GraphQLError('Ошибка получения поставки')
}
},
// Заявки на поставки для поставщиков (новая система v2)
mySupplierConsumableSupplies: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
try {
const user = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true },
})
if (!user?.organization || user.organization.type !== 'WHOLESALE') {
return []
}
const supplies = await prisma.fulfillmentConsumableSupplyOrder.findMany({
where: {
supplierId: user.organizationId!,
},
include: {
fulfillmentCenter: true,
supplier: true,
logisticsPartner: true,
receivedBy: true,
items: {
include: {
product: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
})
return supplies
} catch (error) {
console.error('Error fetching supplier consumable supplies:', error)
return []
}
},
}
export const fulfillmentConsumableV2Mutations = {
createFulfillmentConsumableSupply: async (
_: unknown,
args: {
input: {
supplierId: string
requestedDeliveryDate: string
items: Array<{
productId: string
requestedQuantity: number
}>
notes?: string
}
},
context: Context
) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
try {
const user = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true },
})
if (!user?.organization || user.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Только фулфилмент-центры могут создавать поставки расходников')
}
// Проверяем что поставщик существует и является WHOLESALE
const supplier = await prisma.organization.findUnique({
where: { id: args.input.supplierId },
})
if (!supplier || supplier.type !== 'WHOLESALE') {
throw new GraphQLError('Поставщик не найден или не является оптовиком')
}
// Проверяем что все товары существуют и принадлежат поставщику
const productIds = args.input.items.map(item => item.productId)
const products = await prisma.product.findMany({
where: {
id: { in: productIds },
organizationId: supplier.id,
type: 'CONSUMABLE',
},
})
if (products.length !== productIds.length) {
throw new GraphQLError('Некоторые товары не найдены или не принадлежат поставщику')
}
// Создаем поставку с items
const supplyOrder = await prisma.fulfillmentConsumableSupplyOrder.create({
data: {
fulfillmentCenterId: user.organizationId!,
supplierId: supplier.id,
requestedDeliveryDate: new Date(args.input.requestedDeliveryDate),
notes: args.input.notes,
items: {
create: args.input.items.map(item => {
const product = products.find(p => p.id === item.productId)!
return {
productId: item.productId,
requestedQuantity: item.requestedQuantity,
unitPrice: product.price,
totalPrice: product.price.mul(item.requestedQuantity),
}
}),
},
},
include: {
fulfillmentCenter: true,
supplier: true,
items: {
include: {
product: true,
},
},
},
})
// Отправляем уведомление поставщику о новой заявке
await notifyOrganization(supplier.id, {
type: 'supply-order:new',
title: 'Новая заявка на поставку расходников',
message: `Фулфилмент-центр "${user.organization.name}" создал заявку на поставку расходников`,
data: {
supplyOrderId: supplyOrder.id,
supplyOrderType: 'FULFILLMENT_CONSUMABLES_V2',
fulfillmentCenterName: user.organization.name,
itemsCount: args.input.items.length,
requestedDeliveryDate: args.input.requestedDeliveryDate,
},
})
return {
success: true,
message: 'Поставка расходников создана успешно',
supplyOrder,
}
} catch (error) {
console.error('Error creating fulfillment consumable supply:', error)
return {
success: false,
message: error instanceof Error ? error.message : 'Ошибка создания поставки',
supplyOrder: null,
}
}
},
}