feat(graphql): обновить GraphQL схему и resolvers для V2 системы
Обновления: - prisma/schema.prisma - обновлена схема БД для V2 расходников фулфилмента - src/graphql/typedefs.ts - новые типы для V2 FulfillmentInventoryItem - src/graphql/resolvers.ts - обновлены resolvers mySupplies и counterpartySupplies для V2 - src/graphql/resolvers/index.ts - подключены новые V2 resolvers - src/graphql/queries.ts - обновлены queries - src/graphql/mutations.ts - добавлена V2 мутация updateFulfillmentInventoryPrice - обновлен компонент fulfillment-consumables-orders-tab для V2 ESLint warnings исправим в отдельном коммите. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -35,6 +35,7 @@ import {
|
||||
GET_MY_EMPLOYEES,
|
||||
GET_LOGISTICS_PARTNERS,
|
||||
} from '@/graphql/queries'
|
||||
import { GET_INCOMING_SELLER_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
|
||||
interface SupplyOrder {
|
||||
@ -146,21 +147,31 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
console.error('LOGISTICS ERROR:', logisticsError)
|
||||
}
|
||||
|
||||
// Загружаем заказы поставок
|
||||
// Загружаем заказы поставок из старой системы
|
||||
const { data, loading, error, refetch } = useQuery(GET_SUPPLY_ORDERS)
|
||||
|
||||
// Загружаем селлерские поставки из новой системы
|
||||
const {
|
||||
data: sellerData,
|
||||
loading: sellerLoading,
|
||||
error: sellerError,
|
||||
refetch: refetchSellerSupplies,
|
||||
} = useQuery(GET_INCOMING_SELLER_SUPPLIES)
|
||||
|
||||
// Мутация для приемки поставки фулфилментом
|
||||
const [fulfillmentReceiveOrder, { loading: receiving }] = useMutation(FULFILLMENT_RECEIVE_ORDER, {
|
||||
onCompleted: (data) => {
|
||||
if (data.fulfillmentReceiveOrder.success) {
|
||||
toast.success(data.fulfillmentReceiveOrder.message)
|
||||
refetch() // Обновляем список заказов
|
||||
refetch() // Обновляем старые заказы поставок
|
||||
refetchSellerSupplies() // Обновляем селлерские поставки
|
||||
} else {
|
||||
toast.error(data.fulfillmentReceiveOrder.message)
|
||||
}
|
||||
},
|
||||
refetchQueries: [
|
||||
{ query: GET_SUPPLY_ORDERS }, // Обновляем заказы поставок
|
||||
{ query: GET_INCOMING_SELLER_SUPPLIES }, // Обновляем селлерские поставки
|
||||
{ query: GET_MY_SUPPLIES }, // Обновляем склад фулфилмента (расходники фулфилмента)
|
||||
{ query: GET_WAREHOUSE_PRODUCTS }, // Обновляем товары склада
|
||||
{ query: GET_PENDING_SUPPLIES_COUNT }, // Обновляем счетчики уведомлений
|
||||
@ -176,7 +187,8 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
onCompleted: (data) => {
|
||||
if (data.assignLogisticsToSupply.success) {
|
||||
toast.success('Логистика и ответственный назначены успешно')
|
||||
refetch() // Обновляем список заказов
|
||||
refetch() // Обновляем старые заказы поставок
|
||||
refetchSellerSupplies() // Обновляем селлерские поставки
|
||||
// Сбрасываем состояние назначения
|
||||
setAssigningOrders((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
@ -187,7 +199,10 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
toast.error(data.assignLogisticsToSupply.message || 'Ошибка при назначении логистики')
|
||||
}
|
||||
},
|
||||
refetchQueries: [{ query: GET_SUPPLY_ORDERS }],
|
||||
refetchQueries: [
|
||||
{ query: GET_SUPPLY_ORDERS },
|
||||
{ query: GET_INCOMING_SELLER_SUPPLIES },
|
||||
],
|
||||
onError: (error) => {
|
||||
console.error('Error assigning logistics:', error)
|
||||
toast.error('Ошибка при назначении логистики')
|
||||
@ -204,37 +219,93 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
setExpandedOrders(newExpanded)
|
||||
}
|
||||
|
||||
// Получаем данные заказов поставок
|
||||
// Получаем данные заказов поставок из старой системы
|
||||
const supplyOrders: SupplyOrder[] = data?.supplyOrders || []
|
||||
|
||||
// Получаем селлерские поставки и конвертируем их в формат SupplyOrder
|
||||
const sellerSupplies = sellerData?.incomingSellerSupplies || []
|
||||
const convertedSellerSupplies: SupplyOrder[] = sellerSupplies.map((supply: any) => ({
|
||||
id: supply.id,
|
||||
partnerId: supply.supplierId || supply.supplier?.id,
|
||||
deliveryDate: supply.requestedDeliveryDate,
|
||||
status: supply.status === 'DELIVERED' ? 'DELIVERED' :
|
||||
supply.status === 'SHIPPED' ? 'SHIPPED' :
|
||||
supply.status === 'APPROVED' ? 'SUPPLIER_APPROVED' :
|
||||
'PENDING',
|
||||
totalAmount: supply.totalCostWithDelivery || supply.items?.reduce((sum: number, item: any) =>
|
||||
sum + (item.unitPrice * item.requestedQuantity), 0) || 0,
|
||||
totalItems: supply.items?.reduce((sum: number, item: any) => sum + item.requestedQuantity, 0) || 0,
|
||||
createdAt: supply.createdAt,
|
||||
consumableType: 'SELLER_CONSUMABLES',
|
||||
fulfillmentCenter: supply.fulfillmentCenter,
|
||||
organization: supply.seller, // Селлер-создатель
|
||||
partner: supply.supplier, // Поставщик
|
||||
logisticsPartner: supply.logisticsPartner,
|
||||
items: supply.items?.map((item: any) => ({
|
||||
id: item.id,
|
||||
quantity: item.requestedQuantity,
|
||||
price: item.unitPrice,
|
||||
totalPrice: item.totalPrice || (item.unitPrice * item.requestedQuantity),
|
||||
product: {
|
||||
id: item.product?.id,
|
||||
name: item.product?.name || 'Товар',
|
||||
article: item.product?.article || '',
|
||||
description: item.product?.description,
|
||||
price: item.product?.price,
|
||||
quantity: item.product?.quantity,
|
||||
images: item.product?.images,
|
||||
mainImage: item.product?.mainImage,
|
||||
category: item.product?.category,
|
||||
},
|
||||
})) || [],
|
||||
}))
|
||||
|
||||
// Фильтруем заказы для фулфилмента (ТОЛЬКО расходники фулфилмента)
|
||||
// Объединяем старые поставки и селлерские поставки
|
||||
const allSupplyOrders = [...supplyOrders, ...convertedSellerSupplies]
|
||||
|
||||
// Фильтруем заказы для фулфилмента (расходники фулфилмента + селлеров)
|
||||
const fulfillmentOrders = supplyOrders.filter((order) => {
|
||||
// Показываем только заказы созданные САМИМ фулфилментом для своих расходников
|
||||
const isCreatedBySelf = order.organization?.id === user?.organization?.id
|
||||
// И получатель тоже мы (фулфилмент заказывает расходники для себя)
|
||||
// Получатель должен быть наш фулфилмент-центр
|
||||
const isRecipient = order.fulfillmentCenter?.id === user?.organization?.id
|
||||
// И статус не PENDING и не CANCELLED (одобренные поставщиком заявки)
|
||||
const isApproved = order.status !== 'CANCELLED' && order.status !== 'PENDING'
|
||||
// ✅ КРИТИЧНОЕ ИСПРАВЛЕНИЕ: Показывать только расходники ФУЛФИЛМЕНТА (НЕ селлеров и НЕ товары)
|
||||
|
||||
// ✅ ИСПРАВЛЕНИЕ: Показывать ОБА типа расходников - фулфилмент и селлер
|
||||
const isFulfillmentConsumables = order.consumableType === 'FULFILLMENT_CONSUMABLES'
|
||||
const isSellerConsumables = order.consumableType === 'SELLER_CONSUMABLES'
|
||||
const isAnyConsumables = isFulfillmentConsumables || isSellerConsumables
|
||||
|
||||
// Проверяем, что это НЕ товары (товары содержат услуги в рецептуре)
|
||||
const hasServices = order.items?.some(item => item.recipe?.services && item.recipe.services.length > 0)
|
||||
const isConsumablesOnly = isFulfillmentConsumables && !hasServices
|
||||
const isConsumablesOnly = isAnyConsumables && !hasServices
|
||||
|
||||
// Дополнительная проверка для селлерских поставок
|
||||
const isCreatedBySelf = order.organization?.id === user?.organization?.id
|
||||
const isFromSeller = order.organization?.type === 'SELLER'
|
||||
|
||||
// Логика фильтрации:
|
||||
// 1. Свои поставки фулфилмента (созданные нами)
|
||||
// 2. Поставки от селлеров (созданные селлерами для нас)
|
||||
const shouldShow = isRecipient && isApproved && isConsumablesOnly && (isCreatedBySelf || isFromSeller)
|
||||
|
||||
console.warn('🔍 Фильтрация расходников фулфилмента:', {
|
||||
console.warn('🔍 Фильтрация расходников фулфилмента + селлеров:', {
|
||||
orderId: order.id.slice(-8),
|
||||
isRecipient,
|
||||
isCreatedBySelf,
|
||||
isApproved,
|
||||
isFulfillmentConsumables,
|
||||
isSellerConsumables,
|
||||
isAnyConsumables,
|
||||
hasServices,
|
||||
isConsumablesOnly,
|
||||
isCreatedBySelf,
|
||||
isFromSeller,
|
||||
consumableType: order.consumableType,
|
||||
organizationType: order.organization?.type,
|
||||
itemsWithServices: order.items?.filter(item => item.recipe?.services && item.recipe.services.length > 0).length || 0,
|
||||
finalResult: isRecipient && isCreatedBySelf && isApproved && isConsumablesOnly,
|
||||
finalResult: shouldShow,
|
||||
})
|
||||
|
||||
return isRecipient && isCreatedBySelf && isApproved && isConsumablesOnly
|
||||
return shouldShow
|
||||
})
|
||||
|
||||
// Генерируем порядковые номера для заказов
|
||||
@ -422,7 +493,7 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Одобрено</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{fulfillmentOrders.filter((order) => order.status === 'SUPPLIER_APPROVED').length}
|
||||
{(fulfillmentOrders || []).filter((order) => order.status === 'SUPPLIER_APPROVED').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -436,7 +507,7 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Подтверждено</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{fulfillmentOrders.filter((order) => order.status === 'CONFIRMED').length}
|
||||
{(fulfillmentOrders || []).filter((order) => order.status === 'CONFIRMED').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -450,7 +521,7 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">В пути</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{fulfillmentOrders.filter((order) => order.status === 'IN_TRANSIT').length}
|
||||
{(fulfillmentOrders || []).filter((order) => order.status === 'IN_TRANSIT').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -464,7 +535,7 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Доставлено</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{fulfillmentOrders.filter((order) => order.status === 'DELIVERED').length}
|
||||
{(fulfillmentOrders || []).filter((order) => order.status === 'DELIVERED').length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -505,28 +576,30 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<span className="text-white font-semibold text-sm">{order.number}</span>
|
||||
</div>
|
||||
|
||||
{/* Селлер */}
|
||||
{/* Заказчик (селлер или фулфилмент) */}
|
||||
<div className="flex items-center space-x-2 min-w-0">
|
||||
<div className="flex flex-col items-center">
|
||||
<Store className="h-3 w-3 text-blue-400 mb-0.5" />
|
||||
<span className="text-blue-400 text-xs font-medium leading-none">Селлер</span>
|
||||
<span className="text-blue-400 text-xs font-medium leading-none">
|
||||
{order.consumableType === 'SELLER_CONSUMABLES' ? 'Селлер' : 'Заказчик'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Avatar className="w-7 h-7 flex-shrink-0">
|
||||
<AvatarFallback className="bg-blue-500 text-white text-xs">
|
||||
{getInitials(order.partner.name || order.partner.fullName)}
|
||||
{getInitials(order.organization?.name || order.organization?.fullName || 'Н/Д')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-white font-medium text-sm truncate max-w-[120px]">
|
||||
{order.partner.name || order.partner.fullName}
|
||||
{order.organization?.name || order.organization?.fullName || 'Не указано'}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs">{order.partner.inn}</p>
|
||||
<p className="text-white/60 text-xs">{order.organization?.type || 'Н/Д'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Поставщик (фулфилмент-центр) */}
|
||||
{/* Поставщик */}
|
||||
<div className="hidden xl:flex items-center space-x-2 min-w-0">
|
||||
<div className="flex flex-col items-center">
|
||||
<Building className="h-3 w-3 text-green-400 mb-0.5" />
|
||||
@ -535,14 +608,14 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Avatar className="w-7 h-7 flex-shrink-0">
|
||||
<AvatarFallback className="bg-green-500 text-white text-xs">
|
||||
{getInitials(user?.organization?.name || 'ФФ')}
|
||||
{getInitials(order.partner.name || order.partner.fullName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate max-w-[100px]">
|
||||
{user?.organization?.name || 'ФФ-центр'}
|
||||
{order.partner.name || order.partner.fullName}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs">Наш ФФ</p>
|
||||
<p className="text-white/60 text-xs">{order.partner.inn}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -596,12 +669,12 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Avatar className="w-6 h-6 flex-shrink-0">
|
||||
<AvatarFallback className="bg-green-500 text-white text-xs">
|
||||
{getInitials(user?.organization?.name || 'ФФ')}
|
||||
{getInitials(order.partner.name || order.partner.fullName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate">
|
||||
{user?.organization?.name || 'Фулфилмент-центр'}
|
||||
{order.partner.name || order.partner.fullName}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
@ -719,13 +792,41 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация о заказчике */}
|
||||
<div className="mb-3">
|
||||
<h4 className="text-white font-semibold mb-1.5 flex items-center text-sm">
|
||||
<Store className="h-4 w-4 mr-1.5 text-blue-400" />
|
||||
Информация о {order.consumableType === 'SELLER_CONSUMABLES' ? 'селлере' : 'заказчике'}
|
||||
</h4>
|
||||
<div className="bg-white/5 rounded p-2 space-y-1.5">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Building className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
<span className="text-white/80 text-sm font-medium">
|
||||
{order.organization?.name || order.organization?.fullName || 'Не указано'}
|
||||
</span>
|
||||
<span className="text-white/60 text-xs">
|
||||
({order.organization?.type || 'Н/Д'})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация о поставщике */}
|
||||
<div className="mb-3">
|
||||
<h4 className="text-white font-semibold mb-1.5 flex items-center text-sm">
|
||||
<Building className="h-4 w-4 mr-1.5 text-blue-400" />
|
||||
Информация о селлере
|
||||
<Building className="h-4 w-4 mr-1.5 text-green-400" />
|
||||
Информация о поставщике
|
||||
</h4>
|
||||
<div className="bg-white/5 rounded p-2 space-y-1.5">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Building className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
<span className="text-white/80 text-sm font-medium">
|
||||
{order.partner.name || order.partner.fullName}
|
||||
</span>
|
||||
<span className="text-white/60 text-xs">
|
||||
ИНН: {order.partner.inn}
|
||||
</span>
|
||||
</div>
|
||||
{order.partner.address && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<MapPin className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
@ -751,23 +852,23 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-1.5 flex items-center text-sm">
|
||||
<Package className="h-4 w-4 mr-1.5 text-green-400" />
|
||||
Товары ({order.items.length})
|
||||
Товары ({order.items?.length || 0})
|
||||
</h4>
|
||||
<div className="space-y-1.5">
|
||||
{order.items.map((item) => (
|
||||
{order.items?.map((item) => (
|
||||
<div key={item.id} className="bg-white/5 rounded p-2 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||
{item.product.mainImage && (
|
||||
{item.product?.mainImage && (
|
||||
<img
|
||||
src={item.product.mainImage}
|
||||
alt={item.product.name}
|
||||
alt={item.product?.name || 'Товар'}
|
||||
className="w-8 h-8 rounded object-cover flex-shrink-0"
|
||||
/>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<h5 className="text-white font-medium text-sm truncate">{item.product.name}</h5>
|
||||
<p className="text-white/60 text-xs">{item.product.article}</p>
|
||||
{item.product.category && (
|
||||
<h5 className="text-white font-medium text-sm truncate">{item.product?.name || 'Без названия'}</h5>
|
||||
<p className="text-white/60 text-xs">{item.product?.article || 'Без артикула'}</p>
|
||||
{item.product?.category?.name && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-blue-500/20 text-blue-300 text-xs mt-0.5 px-1.5 py-0.5"
|
||||
|
@ -1155,6 +1155,10 @@ export const GET_SUPPLY_ORDERS = gql`
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
images
|
||||
mainImage
|
||||
category {
|
||||
id
|
||||
name
|
||||
|
@ -3,12 +3,13 @@ import { JSONScalar, DateTimeScalar } from '../scalars'
|
||||
|
||||
import { authResolvers } from './auth'
|
||||
import { employeeResolvers } from './employees'
|
||||
import { fulfillmentConsumableV2Queries, fulfillmentConsumableV2Mutations } from './fulfillment-consumables-v2'
|
||||
import { logisticsResolvers } from './logistics'
|
||||
import { referralResolvers } from './referrals'
|
||||
import { integrateSecurityWithExistingResolvers } from './secure-integration'
|
||||
import { secureSuppliesResolvers } from './secure-supplies'
|
||||
import { sellerConsumableQueries, sellerConsumableMutations } from './seller-consumables'
|
||||
import { suppliesResolvers } from './supplies'
|
||||
import { fulfillmentConsumableV2Queries, fulfillmentConsumableV2Mutations } from './fulfillment-consumables-v2'
|
||||
|
||||
// Типы для резолверов
|
||||
interface ResolverObject {
|
||||
@ -111,6 +112,12 @@ const mergedResolvers = mergeResolvers(
|
||||
Query: fulfillmentConsumableV2Queries,
|
||||
Mutation: fulfillmentConsumableV2Mutations,
|
||||
},
|
||||
|
||||
// НОВЫЕ резолверы для системы поставок селлера
|
||||
{
|
||||
Query: sellerConsumableQueries,
|
||||
Mutation: sellerConsumableMutations,
|
||||
},
|
||||
)
|
||||
|
||||
// Применяем middleware безопасности ко всем резолверам
|
||||
|
Reference in New Issue
Block a user