feat(v2-inventory): мигрировать систему расходников на V2 архитектуру
Переход от старой таблицы Supply к новой FulfillmentConsumableInventory: - Обновлен mySupplies resolver для чтения из V2 таблицы с корректными остатками - Добавлена V2 мутация updateFulfillmentInventoryPrice для обновления цен - Исправлен counterpartySupplies для показа актуальных V2 цен в рецептурах - Frontend использует новую мутацию UPDATE_FULFILLMENT_INVENTORY_PRICE - Цены расходников корректно сохраняются и отображаются после перезагрузки - Селлеры видят правильные цены при создании поставок товаров 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -10,7 +10,7 @@ import { toast } from 'sonner'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card } from '@/components/ui/card'
|
import { Card } from '@/components/ui/card'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { UPDATE_SUPPLY_PRICE } from '@/graphql/mutations'
|
import { UPDATE_FULFILLMENT_INVENTORY_PRICE } from '@/graphql/mutations'
|
||||||
import { GET_MY_SUPPLIES } from '@/graphql/queries'
|
import { GET_MY_SUPPLIES } from '@/graphql/queries'
|
||||||
import { useAuth } from '@/hooks/useAuth'
|
import { useAuth } from '@/hooks/useAuth'
|
||||||
|
|
||||||
@ -55,16 +55,16 @@ export function SuppliesTab() {
|
|||||||
const [isInitialized, setIsInitialized] = useState(false)
|
const [isInitialized, setIsInitialized] = useState(false)
|
||||||
|
|
||||||
// Debug информация
|
// Debug информация
|
||||||
console.log('SuppliesTab - User:', user?.phone, 'Type:', user?.organization?.type)
|
console.warn('SuppliesTab - User:', user?.phone, 'Type:', user?.organization?.type)
|
||||||
|
|
||||||
// GraphQL запросы и мутации
|
// GraphQL запросы и мутации
|
||||||
const { data, loading, error, refetch } = useQuery(GET_MY_SUPPLIES, {
|
const { data, loading, error, refetch } = useQuery(GET_MY_SUPPLIES, {
|
||||||
skip: !user || user?.organization?.type !== 'FULFILLMENT',
|
skip: !user || user?.organization?.type !== 'FULFILLMENT',
|
||||||
})
|
})
|
||||||
const [updateSupplyPrice] = useMutation(UPDATE_SUPPLY_PRICE)
|
const [updateFulfillmentInventoryPrice] = useMutation(UPDATE_FULFILLMENT_INVENTORY_PRICE)
|
||||||
|
|
||||||
// Debug GraphQL запроса
|
// Debug GraphQL запроса
|
||||||
console.log('SuppliesTab - Query:', {
|
console.warn('SuppliesTab - Query:', {
|
||||||
skip: !user || user?.organization?.type !== 'FULFILLMENT',
|
skip: !user || user?.organization?.type !== 'FULFILLMENT',
|
||||||
loading,
|
loading,
|
||||||
error: error?.message,
|
error: error?.message,
|
||||||
@ -167,7 +167,7 @@ export function SuppliesTab() {
|
|||||||
// Проверяем валидность цены (может быть пустой)
|
// Проверяем валидность цены (может быть пустой)
|
||||||
const pricePerUnit = supply.pricePerUnit.trim() ? parseFloat(supply.pricePerUnit) : null
|
const pricePerUnit = supply.pricePerUnit.trim() ? parseFloat(supply.pricePerUnit) : null
|
||||||
|
|
||||||
if (supply.pricePerUnit.trim() && (isNaN(pricePerUnit!) || pricePerUnit! <= 0)) {
|
if (supply.pricePerUnit.trim() && (pricePerUnit === null || isNaN(pricePerUnit) || pricePerUnit <= 0)) {
|
||||||
toast.error('Введите корректную цену')
|
toast.error('Введите корректную цену')
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
return
|
return
|
||||||
@ -177,17 +177,18 @@ export function SuppliesTab() {
|
|||||||
pricePerUnit: pricePerUnit,
|
pricePerUnit: pricePerUnit,
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateSupplyPrice({
|
await updateFulfillmentInventoryPrice({
|
||||||
variables: { id: supply.id, input },
|
variables: { id: supply.id, input },
|
||||||
update: (cache, { data }) => {
|
update: (cache, { data }) => {
|
||||||
if (data?.updateSupplyPrice?.supply) {
|
if (data?.updateFulfillmentInventoryPrice?.item) {
|
||||||
const existingData = cache.readQuery({ query: GET_MY_SUPPLIES }) as { mySupplies: Supply[] } | null
|
const existingData = cache.readQuery({ query: GET_MY_SUPPLIES }) as { mySupplies: Supply[] } | null
|
||||||
if (existingData) {
|
if (existingData) {
|
||||||
|
const updatedItem = data.updateFulfillmentInventoryPrice.item
|
||||||
cache.writeQuery({
|
cache.writeQuery({
|
||||||
query: GET_MY_SUPPLIES,
|
query: GET_MY_SUPPLIES,
|
||||||
data: {
|
data: {
|
||||||
mySupplies: existingData.mySupplies.map((s: Supply) =>
|
mySupplies: existingData.mySupplies.map((s: Supply) =>
|
||||||
s.id === data.updateSupplyPrice.supply.id ? data.updateSupplyPrice.supply : s,
|
s.id === updatedItem.id ? updatedItem : s,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -643,7 +643,34 @@ export const DELETE_SERVICE = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// Мутации для расходников - только обновление цены разрешено
|
// V2 мутация для обновления цены расходников в инвентаре фулфилмента
|
||||||
|
export const UPDATE_FULFILLMENT_INVENTORY_PRICE = gql`
|
||||||
|
mutation UpdateFulfillmentInventoryPrice($id: ID!, $input: UpdateSupplyPriceInput!) {
|
||||||
|
updateFulfillmentInventoryPrice(id: $id, input: $input) {
|
||||||
|
success
|
||||||
|
message
|
||||||
|
item {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
pricePerUnit
|
||||||
|
unit
|
||||||
|
imageUrl
|
||||||
|
warehouseStock
|
||||||
|
isAvailable
|
||||||
|
warehouseConsumableId
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
organization {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// DEPRECATED: Мутации для расходников - только обновление цены разрешено
|
||||||
export const UPDATE_SUPPLY_PRICE = gql`
|
export const UPDATE_SUPPLY_PRICE = gql`
|
||||||
mutation UpdateSupplyPrice($id: ID!, $input: UpdateSupplyPriceInput!) {
|
mutation UpdateSupplyPrice($id: ID!, $input: UpdateSupplyPriceInput!) {
|
||||||
updateSupplyPrice(id: $id, input: $input) {
|
updateSupplyPrice(id: $id, input: $input) {
|
||||||
|
@ -11,16 +11,14 @@ import { SmsService } from '@/services/sms-service'
|
|||||||
import { WildberriesService } from '@/services/wildberries-service'
|
import { WildberriesService } from '@/services/wildberries-service'
|
||||||
|
|
||||||
import '@/lib/seed-init' // Автоматическая инициализация БД
|
import '@/lib/seed-init' // Автоматическая инициализация БД
|
||||||
|
|
||||||
// Импорт новых resolvers для системы поставок v2
|
// Импорт новых resolvers для системы поставок v2
|
||||||
import { fulfillmentConsumableV2Queries, fulfillmentConsumableV2Mutations } from './resolvers/fulfillment-consumables-v2'
|
import { fulfillmentConsumableV2Queries, fulfillmentConsumableV2Mutations } from './resolvers/fulfillment-consumables-v2'
|
||||||
|
import { fulfillmentInventoryV2Queries } from './resolvers/fulfillment-inventory-v2'
|
||||||
// 🔒 СИСТЕМА БЕЗОПАСНОСТИ - импорты
|
|
||||||
import { CommercialDataAudit } from './security/commercial-data-audit'
|
import { CommercialDataAudit } from './security/commercial-data-audit'
|
||||||
import { createSecurityContext } from './security/index'
|
import { createSecurityContext } from './security/index'
|
||||||
|
|
||||||
// 🔒 HELPER: Создание безопасного контекста с организационными данными
|
// 🔒 HELPER: Создание безопасного контекста с организационными данными
|
||||||
function createSecureContextWithOrgData(context: Context, currentUser: any) {
|
function createSecureContextWithOrgData(context: Context, currentUser: { organization: { id: string; type: string } }) {
|
||||||
return {
|
return {
|
||||||
...context,
|
...context,
|
||||||
user: {
|
user: {
|
||||||
@ -793,27 +791,30 @@ export const resolvers = {
|
|||||||
return [] // Только фулфилменты имеют расходники
|
return [] // Только фулфилменты имеют расходники
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем ВСЕ расходники из таблицы supply для фулфилмента
|
// Получаем расходники из V2 инвентаря фулфилмента
|
||||||
const allSupplies = await prisma.supply.findMany({
|
const inventoryItems = await prisma.fulfillmentConsumableInventory.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
where: { fulfillmentCenterId: currentUser.organization.id },
|
||||||
include: { organization: true },
|
include: {
|
||||||
orderBy: { createdAt: 'desc' },
|
product: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
|
},
|
||||||
|
orderBy: { lastSupplyDate: 'desc' },
|
||||||
})
|
})
|
||||||
|
|
||||||
// Преобразуем старую структуру в новую согласно GraphQL схеме
|
// Преобразуем V2 структуру в формат для services/supplies
|
||||||
const transformedSupplies = allSupplies.map((supply) => ({
|
const transformedSupplies = inventoryItems.map((item) => ({
|
||||||
id: supply.id,
|
id: item.id,
|
||||||
name: supply.name,
|
name: item.product.name,
|
||||||
description: supply.description,
|
description: item.product.description || '',
|
||||||
pricePerUnit: supply.price ? parseFloat(supply.price.toString()) : null, // Конвертируем Decimal в Number
|
pricePerUnit: item.resalePrice ? parseFloat(item.resalePrice.toString()) : null, // Цена перепродажи
|
||||||
unit: supply.unit || 'шт', // Единица измерения
|
unit: 'шт', // TODO: добавить unit в Product модель
|
||||||
imageUrl: supply.imageUrl,
|
imageUrl: item.product.mainImage,
|
||||||
warehouseStock: supply.currentStock || 0, // Остаток на складе
|
warehouseStock: item.currentStock, // Текущий остаток V2
|
||||||
isAvailable: (supply.currentStock || 0) > 0, // Есть ли в наличии
|
isAvailable: item.currentStock > 0, // Есть ли в наличии
|
||||||
warehouseConsumableId: supply.id, // Связь со складом (пока используем тот же ID)
|
warehouseConsumableId: item.id, // ID из V2 инвентаря
|
||||||
createdAt: supply.createdAt,
|
createdAt: item.createdAt,
|
||||||
updatedAt: supply.updatedAt,
|
updatedAt: item.updatedAt,
|
||||||
organization: supply.organization,
|
organization: item.fulfillmentCenter,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
console.warn('🔥 SUPPLIES RESOLVER - NEW FORMAT:', {
|
console.warn('🔥 SUPPLIES RESOLVER - NEW FORMAT:', {
|
||||||
@ -898,39 +899,68 @@ export const resolvers = {
|
|||||||
throw new GraphQLError('Доступ только для фулфилмент центров')
|
throw new GraphQLError('Доступ только для фулфилмент центров')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем расходники фулфилмента из таблицы Supply
|
// Получаем расходники фулфилмента из V2 таблицы FulfillmentConsumableInventory
|
||||||
const supplies = await prisma.supply.findMany({
|
const inventoryItems = await prisma.fulfillmentConsumableInventory.findMany({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
fulfillmentCenterId: currentUser.organization.id,
|
||||||
type: 'FULFILLMENT_CONSUMABLES', // Только расходники фулфилмента
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true,
|
product: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { lastSupplyDate: 'desc' },
|
||||||
})
|
})
|
||||||
|
|
||||||
// Логирование для отладки
|
// Логирование для отладки
|
||||||
console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED (NEW ARCHITECTURE) 🔥🔥🔥')
|
console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED (V2 ARCHITECTURE) 🔥🔥🔥')
|
||||||
console.warn('📊 Расходники фулфилмента из склада:', {
|
console.warn('📊 Расходники фулфилмента из V2 инвентаря:', {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
organizationType: currentUser.organization.type,
|
organizationType: currentUser.organization.type,
|
||||||
suppliesCount: supplies.length,
|
inventoryItemsCount: inventoryItems.length,
|
||||||
supplies: supplies.map((s) => ({
|
inventoryItems: inventoryItems.map((item) => ({
|
||||||
id: s.id,
|
id: item.id,
|
||||||
name: s.name,
|
productName: item.product.name,
|
||||||
type: s.type,
|
currentStock: item.currentStock,
|
||||||
status: s.status,
|
totalReceived: item.totalReceived,
|
||||||
currentStock: s.currentStock,
|
totalShipped: item.totalShipped,
|
||||||
quantity: s.quantity,
|
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Преобразуем в формат для фронтенда
|
// Преобразуем V2 инвентарь в V1 формат для совместимости с фронтом
|
||||||
return supplies.map((supply) => ({
|
return inventoryItems.map((item) => ({
|
||||||
...supply,
|
// === ОСНОВНЫЕ ПОЛЯ ===
|
||||||
price: supply.price ? parseFloat(supply.price.toString()) : 0,
|
id: item.id,
|
||||||
shippedQuantity: 0, // Добавляем для совместимости
|
name: item.product.name,
|
||||||
|
article: item.product.article,
|
||||||
|
description: item.product.description || '',
|
||||||
|
category: item.product.category?.name || 'Расходники',
|
||||||
|
unit: 'шт', // TODO: добавить в Product модель
|
||||||
|
|
||||||
|
// === СКЛАДСКИЕ ДАННЫЕ ===
|
||||||
|
currentStock: item.currentStock,
|
||||||
|
minStock: item.minStock,
|
||||||
|
usedStock: item.totalShipped, // V2: всего отгружено = использовано
|
||||||
|
quantity: item.totalReceived, // V2: всего получено = количество
|
||||||
|
shippedQuantity: item.totalShipped, // Для совместимости
|
||||||
|
|
||||||
|
// === ЦЕНЫ ===
|
||||||
|
price: parseFloat(item.averageCost.toString()),
|
||||||
|
|
||||||
|
// === СТАТУС ===
|
||||||
|
status: item.currentStock > 0 ? 'in-stock' : 'out-of-stock',
|
||||||
|
|
||||||
|
// === ДАТЫ ===
|
||||||
|
date: item.lastSupplyDate?.toISOString() || item.createdAt.toISOString(),
|
||||||
|
createdAt: item.createdAt.toISOString(),
|
||||||
|
updatedAt: item.updatedAt.toISOString(),
|
||||||
|
|
||||||
|
// === ПОСТАВЩИК ===
|
||||||
|
supplier: 'Различные поставщики', // V2 инвентарь агрегирует данные от разных поставщиков
|
||||||
|
|
||||||
|
// === ДОПОЛНИТЕЛЬНО ===
|
||||||
|
imageUrl: item.product.mainImage,
|
||||||
|
type: 'FULFILLMENT_CONSUMABLES', // Для совместимости
|
||||||
|
organizationId: item.fulfillmentCenterId,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -970,28 +1000,15 @@ export const resolvers = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
partner: {
|
partner: true,
|
||||||
include: {
|
organization: true,
|
||||||
users: true,
|
fulfillmentCenter: true,
|
||||||
},
|
|
||||||
},
|
|
||||||
organization: {
|
|
||||||
include: {
|
|
||||||
users: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fulfillmentCenter: {
|
|
||||||
include: {
|
|
||||||
users: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logisticsPartner: true,
|
logisticsPartner: true,
|
||||||
items: {
|
items: {
|
||||||
include: {
|
include: {
|
||||||
product: {
|
product: {
|
||||||
include: {
|
include: {
|
||||||
category: true,
|
category: true,
|
||||||
organization: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -2093,11 +2110,41 @@ export const resolvers = {
|
|||||||
throw new GraphQLError('Расходники доступны только у фулфилмент центров')
|
throw new GraphQLError('Расходники доступны только у фулфилмент центров')
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.supply.findMany({
|
// Получаем расходники из V2 инвентаря фулфилмента с правильными ценами
|
||||||
where: { organizationId: args.organizationId },
|
const inventoryItems = await prisma.fulfillmentConsumableInventory.findMany({
|
||||||
include: { organization: true },
|
where: {
|
||||||
orderBy: { createdAt: 'desc' },
|
fulfillmentCenterId: args.organizationId,
|
||||||
|
currentStock: { gt: 0 }, // Только те, что есть в наличии
|
||||||
|
resalePrice: { not: null }, // Только те, у которых установлена цена
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
|
},
|
||||||
|
orderBy: { lastSupplyDate: 'desc' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.warn('🔥 COUNTERPARTY SUPPLIES - V2 FORMAT:', {
|
||||||
|
organizationId: args.organizationId,
|
||||||
|
itemsCount: inventoryItems.length,
|
||||||
|
itemsWithPrices: inventoryItems.filter(item => item.resalePrice).length,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Преобразуем V2 формат в формат старого Supply для обратной совместимости
|
||||||
|
return inventoryItems.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.product.name,
|
||||||
|
description: item.product.description || '',
|
||||||
|
price: item.resalePrice ? parseFloat(item.resalePrice.toString()) : 0, // Цена перепродажи из V2
|
||||||
|
quantity: item.currentStock, // Текущий остаток
|
||||||
|
unit: 'шт', // TODO: добавить unit в Product модель
|
||||||
|
category: 'CONSUMABLE',
|
||||||
|
status: 'AVAILABLE',
|
||||||
|
imageUrl: item.product.mainImage,
|
||||||
|
createdAt: item.createdAt,
|
||||||
|
updatedAt: item.updatedAt,
|
||||||
|
organization: item.fulfillmentCenter,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
// Корзина пользователя
|
// Корзина пользователя
|
||||||
@ -2799,6 +2846,9 @@ export const resolvers = {
|
|||||||
|
|
||||||
// Новая система поставок v2
|
// Новая система поставок v2
|
||||||
...fulfillmentConsumableV2Queries,
|
...fulfillmentConsumableV2Queries,
|
||||||
|
|
||||||
|
// Новая система складских остатков V2 (заменяет старый myFulfillmentSupplies)
|
||||||
|
...fulfillmentInventoryV2Queries,
|
||||||
},
|
},
|
||||||
|
|
||||||
Mutation: {
|
Mutation: {
|
||||||
@ -4760,47 +4810,54 @@ export const resolvers = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Находим и обновляем расходник
|
// Находим и обновляем расходник в V2 таблице FulfillmentConsumableInventory
|
||||||
const existingSupply = await prisma.supply.findFirst({
|
const existingInventoryItem = await prisma.fulfillmentConsumableInventory.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id,
|
fulfillmentCenterId: currentUser.organization.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!existingSupply) {
|
if (!existingInventoryItem) {
|
||||||
throw new GraphQLError('Расходник не найден')
|
throw new GraphQLError('Расходник не найден в инвентаре')
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedSupply = await prisma.supply.update({
|
const updatedInventoryItem = await prisma.fulfillmentConsumableInventory.update({
|
||||||
where: { id: args.id },
|
where: { id: args.id },
|
||||||
data: {
|
data: {
|
||||||
pricePerUnit: args.input.pricePerUnit, // Обновляем цену продажи, НЕ цену закупки
|
resalePrice: args.input.pricePerUnit, // Обновляем цену перепродажи в V2
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
include: { organization: true },
|
include: {
|
||||||
|
product: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Преобразуем в новый формат для GraphQL
|
// Преобразуем V2 данные в формат для GraphQL (аналогично mySupplies resolver)
|
||||||
const transformedSupply = {
|
const transformedSupply = {
|
||||||
id: updatedSupply.id,
|
id: updatedInventoryItem.id,
|
||||||
name: updatedSupply.name,
|
name: updatedInventoryItem.product.name,
|
||||||
description: updatedSupply.description,
|
description: updatedInventoryItem.product.description || '',
|
||||||
pricePerUnit: updatedSupply.price ? parseFloat(updatedSupply.price.toString()) : null, // Конвертируем Decimal в Number
|
pricePerUnit: updatedInventoryItem.resalePrice ? parseFloat(updatedInventoryItem.resalePrice.toString()) : null,
|
||||||
unit: updatedSupply.unit || 'шт',
|
unit: 'шт', // TODO: добавить unit в Product модель
|
||||||
imageUrl: updatedSupply.imageUrl,
|
imageUrl: updatedInventoryItem.product.mainImage,
|
||||||
warehouseStock: updatedSupply.currentStock || 0,
|
warehouseStock: updatedInventoryItem.currentStock,
|
||||||
isAvailable: (updatedSupply.currentStock || 0) > 0,
|
isAvailable: updatedInventoryItem.currentStock > 0,
|
||||||
warehouseConsumableId: updatedSupply.id,
|
warehouseConsumableId: updatedInventoryItem.id,
|
||||||
createdAt: updatedSupply.createdAt,
|
createdAt: updatedInventoryItem.createdAt,
|
||||||
updatedAt: updatedSupply.updatedAt,
|
updatedAt: updatedInventoryItem.updatedAt,
|
||||||
organization: updatedSupply.organization,
|
organization: updatedInventoryItem.fulfillmentCenter,
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('🔥 SUPPLY PRICE UPDATED:', {
|
console.warn('🔥 V2 SUPPLY PRICE UPDATED:', {
|
||||||
id: transformedSupply.id,
|
id: transformedSupply.id,
|
||||||
name: transformedSupply.name,
|
name: transformedSupply.name,
|
||||||
oldPrice: existingSupply.price,
|
oldPrice: existingInventoryItem.resalePrice,
|
||||||
newPrice: transformedSupply.pricePerUnit,
|
newPrice: transformedSupply.pricePerUnit,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -4818,6 +4875,88 @@ export const resolvers = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// V2 мутация для обновления цены в инвентаре фулфилмента
|
||||||
|
updateFulfillmentInventoryPrice: async (
|
||||||
|
_: unknown,
|
||||||
|
args: {
|
||||||
|
id: string
|
||||||
|
input: {
|
||||||
|
pricePerUnit?: number | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: Context,
|
||||||
|
) => {
|
||||||
|
if (!context.user) {
|
||||||
|
throw new GraphQLError('Требуется авторизация', {
|
||||||
|
extensions: { code: 'UNAUTHENTICATED' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = await prisma.user.findUnique({
|
||||||
|
where: { id: context.user.id },
|
||||||
|
include: { organization: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!currentUser?.organization) {
|
||||||
|
throw new GraphQLError('У пользователя нет организации')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||||
|
throw new GraphQLError('Обновление цен расходников доступно только для фулфилмент центров')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedInventoryItem = await prisma.fulfillmentConsumableInventory.update({
|
||||||
|
where: {
|
||||||
|
id: args.id,
|
||||||
|
fulfillmentCenterId: currentUser.organization.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
resalePrice: args.input.pricePerUnit,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const transformedItem = {
|
||||||
|
id: updatedInventoryItem.id,
|
||||||
|
name: updatedInventoryItem.product.name,
|
||||||
|
description: updatedInventoryItem.product.description || '',
|
||||||
|
pricePerUnit: updatedInventoryItem.resalePrice ? parseFloat(updatedInventoryItem.resalePrice.toString()) : null,
|
||||||
|
unit: 'шт',
|
||||||
|
imageUrl: updatedInventoryItem.product.mainImage,
|
||||||
|
warehouseStock: updatedInventoryItem.currentStock,
|
||||||
|
isAvailable: updatedInventoryItem.currentStock > 0,
|
||||||
|
warehouseConsumableId: updatedInventoryItem.id,
|
||||||
|
createdAt: updatedInventoryItem.createdAt,
|
||||||
|
updatedAt: updatedInventoryItem.updatedAt,
|
||||||
|
organization: updatedInventoryItem.fulfillmentCenter,
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('🔥 V2 FULFILLMENT INVENTORY PRICE UPDATED:', {
|
||||||
|
id: transformedItem.id,
|
||||||
|
name: transformedItem.name,
|
||||||
|
newPrice: transformedItem.pricePerUnit,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Цена расходника успешно обновлена',
|
||||||
|
item: transformedItem,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating fulfillment inventory price:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Ошибка при обновлении цены расходника',
|
||||||
|
item: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Использовать расходники фулфилмента
|
// Использовать расходники фулфилмента
|
||||||
useFulfillmentSupplies: async (
|
useFulfillmentSupplies: async (
|
||||||
_: unknown,
|
_: unknown,
|
||||||
@ -8217,6 +8356,120 @@ export const resolvers = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Сначала пытаемся найти селлерскую поставку
|
||||||
|
const sellerSupply = await prisma.sellerConsumableSupplyOrder.findFirst({
|
||||||
|
where: {
|
||||||
|
id: args.id,
|
||||||
|
fulfillmentCenterId: currentUser.organization.id,
|
||||||
|
status: 'SHIPPED',
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
items: {
|
||||||
|
include: {
|
||||||
|
product: {
|
||||||
|
include: {
|
||||||
|
category: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seller: true, // Селлер-создатель заказа
|
||||||
|
supplier: true, // Поставщик
|
||||||
|
fulfillmentCenter: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Если нашли селлерскую поставку, обрабатываем её
|
||||||
|
if (sellerSupply) {
|
||||||
|
// Обновляем статус селлерской поставки
|
||||||
|
const updatedSellerOrder = await prisma.sellerConsumableSupplyOrder.update({
|
||||||
|
where: { id: args.id },
|
||||||
|
data: {
|
||||||
|
status: 'DELIVERED',
|
||||||
|
deliveredAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
items: {
|
||||||
|
include: {
|
||||||
|
product: {
|
||||||
|
include: {
|
||||||
|
category: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seller: true,
|
||||||
|
supplier: true,
|
||||||
|
fulfillmentCenter: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Добавляем расходники в склад фулфилмента как SELLER_CONSUMABLES
|
||||||
|
console.warn('📦 Обновляем склад фулфилмента для селлерской поставки...')
|
||||||
|
for (const item of sellerSupply.items) {
|
||||||
|
const existingSupply = await prisma.supply.findFirst({
|
||||||
|
where: {
|
||||||
|
organizationId: currentUser.organization.id,
|
||||||
|
article: item.product.article,
|
||||||
|
type: 'SELLER_CONSUMABLES',
|
||||||
|
sellerOwnerId: sellerSupply.sellerId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existingSupply) {
|
||||||
|
await prisma.supply.update({
|
||||||
|
where: { id: existingSupply.id },
|
||||||
|
data: {
|
||||||
|
currentStock: existingSupply.currentStock + item.requestedQuantity,
|
||||||
|
status: 'in-stock',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.warn(
|
||||||
|
`📈 Обновлен расходник селлера "${item.product.name}" (владелец: ${sellerSupply.seller.name}): ${existingSupply.currentStock} -> ${existingSupply.currentStock + item.requestedQuantity}`,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await prisma.supply.create({
|
||||||
|
data: {
|
||||||
|
name: item.product.name,
|
||||||
|
article: item.product.article,
|
||||||
|
description: `Расходники селлера ${sellerSupply.seller.name || sellerSupply.seller.fullName}`,
|
||||||
|
price: item.unitPrice,
|
||||||
|
quantity: item.requestedQuantity,
|
||||||
|
actualQuantity: item.requestedQuantity,
|
||||||
|
currentStock: item.requestedQuantity,
|
||||||
|
usedStock: 0,
|
||||||
|
unit: 'шт',
|
||||||
|
category: item.product.category?.name || 'Расходники',
|
||||||
|
status: 'in-stock',
|
||||||
|
supplier: sellerSupply.supplier?.name || sellerSupply.supplier?.fullName || 'Поставщик',
|
||||||
|
type: 'SELLER_CONSUMABLES',
|
||||||
|
sellerOwnerId: sellerSupply.sellerId,
|
||||||
|
organizationId: currentUser.organization.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.warn(
|
||||||
|
`➕ Создан новый расходник селлера "${item.product.name}" (владелец: ${sellerSupply.seller.name}): ${item.requestedQuantity} единиц`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Селлерская поставка принята фулфилментом. Расходники добавлены на склад.',
|
||||||
|
order: {
|
||||||
|
id: updatedSellerOrder.id,
|
||||||
|
status: updatedSellerOrder.status,
|
||||||
|
deliveryDate: updatedSellerOrder.requestedDeliveryDate,
|
||||||
|
totalAmount: updatedSellerOrder.items.reduce((sum, item) => sum + (item.unitPrice * item.requestedQuantity), 0),
|
||||||
|
totalItems: updatedSellerOrder.items.reduce((sum, item) => sum + item.requestedQuantity, 0),
|
||||||
|
partner: updatedSellerOrder.supplier,
|
||||||
|
organization: updatedSellerOrder.seller,
|
||||||
|
fulfillmentCenter: updatedSellerOrder.fulfillmentCenter,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если селлерской поставки нет, ищем старую поставку
|
||||||
const existingOrder = await prisma.supplyOrder.findFirst({
|
const existingOrder = await prisma.supplyOrder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
@ -10239,7 +10492,7 @@ resolvers.Mutation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Добавляем v2 mutations через spread
|
// Добавляем v2 mutations через spread
|
||||||
...fulfillmentConsumableV2Mutations
|
...fulfillmentConsumableV2Mutations,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* // Резолвер для парсинга JSON рецептуры в SupplyOrderItem
|
/* // Резолвер для парсинга JSON рецептуры в SupplyOrderItem
|
||||||
|
@ -203,7 +203,9 @@ export const typeDefs = gql`
|
|||||||
deleteService(id: ID!): Boolean!
|
deleteService(id: ID!): Boolean!
|
||||||
|
|
||||||
# Работа с расходниками (только обновление цены разрешено)
|
# Работа с расходниками (только обновление цены разрешено)
|
||||||
|
# DEPRECATED: используйте updateFulfillmentInventoryPrice
|
||||||
updateSupplyPrice(id: ID!, input: UpdateSupplyPriceInput!): SupplyResponse!
|
updateSupplyPrice(id: ID!, input: UpdateSupplyPriceInput!): SupplyResponse!
|
||||||
|
updateFulfillmentInventoryPrice(id: ID!, input: UpdateSupplyPriceInput!): FulfillmentInventoryResponse!
|
||||||
|
|
||||||
# Использование расходников фулфилмента
|
# Использование расходников фулфилмента
|
||||||
useFulfillmentSupplies(input: UseFulfillmentSuppliesInput!): SupplyResponse!
|
useFulfillmentSupplies(input: UseFulfillmentSuppliesInput!): SupplyResponse!
|
||||||
@ -653,6 +655,28 @@ export const typeDefs = gql`
|
|||||||
pricePerUnit: Float # Может быть null (цена не установлена)
|
pricePerUnit: Float # Может быть null (цена не установлена)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# V2 типы для инвентаря фулфилмента
|
||||||
|
type FulfillmentInventoryItem {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
pricePerUnit: Float # Цена перепродажи
|
||||||
|
unit: String!
|
||||||
|
imageUrl: String
|
||||||
|
warehouseStock: Int!
|
||||||
|
isAvailable: Boolean!
|
||||||
|
warehouseConsumableId: ID!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
organization: Organization!
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillmentInventoryResponse {
|
||||||
|
success: Boolean!
|
||||||
|
message: String!
|
||||||
|
item: FulfillmentInventoryItem
|
||||||
|
}
|
||||||
|
|
||||||
input UseFulfillmentSuppliesInput {
|
input UseFulfillmentSuppliesInput {
|
||||||
supplyId: ID!
|
supplyId: ID!
|
||||||
quantityUsed: Int!
|
quantityUsed: Int!
|
||||||
@ -1734,6 +1758,7 @@ export const typeDefs = gql`
|
|||||||
# Input типы для создания поставок
|
# Input типы для создания поставок
|
||||||
input CreateFulfillmentConsumableSupplyInput {
|
input CreateFulfillmentConsumableSupplyInput {
|
||||||
supplierId: ID!
|
supplierId: ID!
|
||||||
|
logisticsPartnerId: ID # Логистический партнер (опционально)
|
||||||
requestedDeliveryDate: DateTime!
|
requestedDeliveryDate: DateTime!
|
||||||
items: [FulfillmentConsumableSupplyItemInput!]!
|
items: [FulfillmentConsumableSupplyItemInput!]!
|
||||||
notes: String
|
notes: String
|
||||||
@ -1744,6 +1769,19 @@ export const typeDefs = gql`
|
|||||||
requestedQuantity: Int!
|
requestedQuantity: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Input для приемки поставки
|
||||||
|
input ReceiveFulfillmentConsumableSupplyInput {
|
||||||
|
supplyOrderId: ID!
|
||||||
|
items: [ReceiveFulfillmentConsumableSupplyItemInput!]!
|
||||||
|
notes: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ReceiveFulfillmentConsumableSupplyItemInput {
|
||||||
|
productId: ID!
|
||||||
|
receivedQuantity: Int!
|
||||||
|
defectQuantity: Int
|
||||||
|
}
|
||||||
|
|
||||||
# Response типы
|
# Response типы
|
||||||
type CreateFulfillmentConsumableSupplyResult {
|
type CreateFulfillmentConsumableSupplyResult {
|
||||||
success: Boolean!
|
success: Boolean!
|
||||||
@ -1751,11 +1789,18 @@ export const typeDefs = gql`
|
|||||||
supplyOrder: FulfillmentConsumableSupplyOrder
|
supplyOrder: FulfillmentConsumableSupplyOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SupplierConsumableSupplyResponse {
|
||||||
|
success: Boolean!
|
||||||
|
message: String!
|
||||||
|
order: FulfillmentConsumableSupplyOrder
|
||||||
|
}
|
||||||
|
|
||||||
# Расширяем Query и Mutation для новой системы
|
# Расширяем Query и Mutation для новой системы
|
||||||
extend type Query {
|
extend type Query {
|
||||||
# Новые запросы для системы поставок v2
|
# Новые запросы для системы поставок v2
|
||||||
myFulfillmentConsumableSupplies: [FulfillmentConsumableSupplyOrder!]!
|
myFulfillmentConsumableSupplies: [FulfillmentConsumableSupplyOrder!]!
|
||||||
mySupplierConsumableSupplies: [FulfillmentConsumableSupplyOrder!]!
|
mySupplierConsumableSupplies: [FulfillmentConsumableSupplyOrder!]!
|
||||||
|
myLogisticsConsumableSupplies: [FulfillmentConsumableSupplyOrder!]!
|
||||||
fulfillmentConsumableSupply(id: ID!): FulfillmentConsumableSupplyOrder
|
fulfillmentConsumableSupply(id: ID!): FulfillmentConsumableSupplyOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1764,5 +1809,162 @@ export const typeDefs = gql`
|
|||||||
createFulfillmentConsumableSupply(
|
createFulfillmentConsumableSupply(
|
||||||
input: CreateFulfillmentConsumableSupplyInput!
|
input: CreateFulfillmentConsumableSupplyInput!
|
||||||
): CreateFulfillmentConsumableSupplyResult!
|
): CreateFulfillmentConsumableSupplyResult!
|
||||||
|
|
||||||
|
# Приемка поставки с автоматическим обновлением инвентаря
|
||||||
|
receiveFulfillmentConsumableSupply(
|
||||||
|
input: ReceiveFulfillmentConsumableSupplyInput!
|
||||||
|
): CreateFulfillmentConsumableSupplyResult!
|
||||||
|
|
||||||
|
# Мутации поставщика для V2 расходников фулфилмента
|
||||||
|
supplierApproveConsumableSupply(id: ID!): SupplierConsumableSupplyResponse!
|
||||||
|
supplierRejectConsumableSupply(id: ID!, reason: String): SupplierConsumableSupplyResponse!
|
||||||
|
supplierShipConsumableSupply(id: ID!): SupplierConsumableSupplyResponse!
|
||||||
|
|
||||||
|
# Мутации логистики для V2 расходников фулфилмента
|
||||||
|
logisticsConfirmConsumableSupply(id: ID!): SupplierConsumableSupplyResponse!
|
||||||
|
logisticsRejectConsumableSupply(id: ID!, reason: String): SupplierConsumableSupplyResponse!
|
||||||
|
|
||||||
|
# Мутация фулфилмента для приемки V2 расходников
|
||||||
|
fulfillmentReceiveConsumableSupply(
|
||||||
|
id: ID!
|
||||||
|
items: [ReceiveFulfillmentConsumableSupplyItemInput!]!
|
||||||
|
notes: String
|
||||||
|
): SupplierConsumableSupplyResponse!
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 📦 СИСТЕМА ПОСТАВОК РАСХОДНИКОВ СЕЛЛЕРА
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# 5-статусная система для поставок селлера
|
||||||
|
enum SellerSupplyOrderStatus {
|
||||||
|
PENDING # Ожидает одобрения поставщика
|
||||||
|
APPROVED # Одобрено поставщиком
|
||||||
|
SHIPPED # Отгружено
|
||||||
|
DELIVERED # Доставлено
|
||||||
|
COMPLETED # Завершено
|
||||||
|
CANCELLED # Отменено
|
||||||
|
}
|
||||||
|
|
||||||
|
# Основной тип для поставки расходников селлера
|
||||||
|
type SellerConsumableSupplyOrder {
|
||||||
|
id: ID!
|
||||||
|
status: SellerSupplyOrderStatus!
|
||||||
|
|
||||||
|
# Данные селлера (создатель)
|
||||||
|
sellerId: ID!
|
||||||
|
seller: Organization!
|
||||||
|
fulfillmentCenterId: ID!
|
||||||
|
fulfillmentCenter: Organization!
|
||||||
|
requestedDeliveryDate: DateTime!
|
||||||
|
notes: String
|
||||||
|
|
||||||
|
# Данные поставщика
|
||||||
|
supplierId: ID
|
||||||
|
supplier: Organization
|
||||||
|
supplierApprovedAt: DateTime
|
||||||
|
packagesCount: Int
|
||||||
|
estimatedVolume: Float
|
||||||
|
supplierContractId: String
|
||||||
|
supplierNotes: String
|
||||||
|
|
||||||
|
# Данные логистики
|
||||||
|
logisticsPartnerId: ID
|
||||||
|
logisticsPartner: Organization
|
||||||
|
estimatedDeliveryDate: DateTime
|
||||||
|
routeId: ID
|
||||||
|
logisticsCost: Float
|
||||||
|
logisticsNotes: String
|
||||||
|
|
||||||
|
# Данные отгрузки
|
||||||
|
shippedAt: DateTime
|
||||||
|
trackingNumber: String
|
||||||
|
|
||||||
|
# Данные приемки
|
||||||
|
receivedAt: DateTime
|
||||||
|
receivedById: ID
|
||||||
|
receivedBy: User
|
||||||
|
actualQuantity: Int
|
||||||
|
defectQuantity: Int
|
||||||
|
receiptNotes: String
|
||||||
|
|
||||||
|
# Экономика (для будущего раздела экономики)
|
||||||
|
totalCostWithDelivery: Float
|
||||||
|
estimatedStorageCost: Float
|
||||||
|
|
||||||
|
items: [SellerConsumableSupplyItem!]!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
# Позиция в поставке селлера
|
||||||
|
type SellerConsumableSupplyItem {
|
||||||
|
id: ID!
|
||||||
|
productId: ID!
|
||||||
|
product: Product!
|
||||||
|
requestedQuantity: Int!
|
||||||
|
approvedQuantity: Int
|
||||||
|
shippedQuantity: Int
|
||||||
|
receivedQuantity: Int
|
||||||
|
defectQuantity: Int
|
||||||
|
unitPrice: Float!
|
||||||
|
totalPrice: Float!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
# Input типы для создания поставок селлера
|
||||||
|
input CreateSellerConsumableSupplyInput {
|
||||||
|
fulfillmentCenterId: ID! # куда доставлять (FULFILLMENT партнер)
|
||||||
|
supplierId: ID! # от кого заказывать (WHOLESALE партнер)
|
||||||
|
logisticsPartnerId: ID # кто везет (LOGIST партнер, опционально)
|
||||||
|
requestedDeliveryDate: DateTime! # когда нужно
|
||||||
|
items: [SellerConsumableSupplyItemInput!]!
|
||||||
|
notes: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input SellerConsumableSupplyItemInput {
|
||||||
|
productId: ID! # какой расходник заказываем
|
||||||
|
requestedQuantity: Int! # сколько нужно
|
||||||
|
}
|
||||||
|
|
||||||
|
# Response типы для селлера
|
||||||
|
type CreateSellerConsumableSupplyResult {
|
||||||
|
success: Boolean!
|
||||||
|
message: String!
|
||||||
|
supplyOrder: SellerConsumableSupplyOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
# Расширяем Query для селлерских поставок
|
||||||
|
extend type Query {
|
||||||
|
# Поставки селлера (мои заказы)
|
||||||
|
mySellerConsumableSupplies: [SellerConsumableSupplyOrder!]!
|
||||||
|
|
||||||
|
# Входящие заказы от селлеров (для фулфилмента)
|
||||||
|
incomingSellerSupplies: [SellerConsumableSupplyOrder!]!
|
||||||
|
|
||||||
|
# Поставки селлеров для поставщиков
|
||||||
|
mySellerSupplyRequests: [SellerConsumableSupplyOrder!]!
|
||||||
|
|
||||||
|
# Конкретная поставка селлера
|
||||||
|
sellerConsumableSupply(id: ID!): SellerConsumableSupplyOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
# Расширяем Mutation для селлерских поставок
|
||||||
|
extend type Mutation {
|
||||||
|
# Создание поставки расходников селлера
|
||||||
|
createSellerConsumableSupply(
|
||||||
|
input: CreateSellerConsumableSupplyInput!
|
||||||
|
): CreateSellerConsumableSupplyResult!
|
||||||
|
|
||||||
|
# Обновление статуса поставки (для поставщиков и фулфилмента)
|
||||||
|
updateSellerSupplyStatus(
|
||||||
|
id: ID!
|
||||||
|
status: SellerSupplyOrderStatus!
|
||||||
|
notes: String
|
||||||
|
): SellerConsumableSupplyOrder!
|
||||||
|
|
||||||
|
# Отмена поставки селлером (только PENDING/APPROVED)
|
||||||
|
cancelSellerSupply(id: ID!): SellerConsumableSupplyOrder!
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
Reference in New Issue
Block a user