feat: завершить полную миграцию V1→V2 с модульной архитектурой и документацией
- Завершить миграцию фулфилмента на 100% V2 (удалить legacy компонент) - Создать полную V2 систему для расходников селлера (SellerConsumableInventory) - Автоматическое пополнение инвентаря при статусе DELIVERED - Удалить весь код создания V1 Supply для расходников - Исправить фильтрацию: расходники селлера только на странице consumables - Исправить Organization.inn null ошибку с fallback значениями - Создать документацию V2 систем и отчет о миграции - Обновить import порядок для ESLint совместимости BREAKING CHANGES: V1 система поставок расходников полностью удалена
This commit is contained in:
@ -10,14 +10,13 @@ import { MarketplaceService } from '@/services/marketplace-service'
|
||||
import { SmsService } from '@/services/sms-service'
|
||||
import { WildberriesService } from '@/services/wildberries-service'
|
||||
|
||||
import '@/lib/seed-init' // Автоматическая инициализация БД
|
||||
// Импорт новых resolvers для системы поставок v2
|
||||
import { fulfillmentConsumableV2Queries, fulfillmentConsumableV2Mutations } from './resolvers/fulfillment-consumables-v2'
|
||||
import { fulfillmentConsumableV2Queries as fulfillmentConsumableV2QueriesRestored, fulfillmentConsumableV2Mutations as fulfillmentConsumableV2MutationsRestored } from './resolvers/fulfillment-consumables-v2-restored'
|
||||
import { fulfillmentInventoryV2Queries } from './resolvers/fulfillment-inventory-v2'
|
||||
import { fulfillmentConsumableV2Queries as fulfillmentConsumableV2QueriesRestored, fulfillmentConsumableV2Mutations as fulfillmentConsumableV2MutationsRestored } from './resolvers/fulfillment-consumables-v2-restored'
|
||||
import { logisticsConsumableV2Queries, logisticsConsumableV2Mutations } from './resolvers/logistics-consumables-v2'
|
||||
import { sellerInventoryV2Queries } from './resolvers/seller-inventory-v2'
|
||||
import { CommercialDataAudit } from './security/commercial-data-audit'
|
||||
import { createSecurityContext } from './security/index'
|
||||
import '@/lib/seed-init' // Автоматическая инициализация БД
|
||||
|
||||
// 🔒 HELPER: Создание безопасного контекста с организационными данными
|
||||
function createSecureContextWithOrgData(context: Context, currentUser: { organization: { id: string; type: string } }) {
|
||||
@ -1310,38 +1309,35 @@ export const resolvers = {
|
||||
`📊 FULFILLMENT SUPPLIES RECEIVED TODAY V2 (ПРИБЫЛО): ${fulfillmentSuppliesReceivedTodayV2.length} orders, ${fulfillmentSuppliesChangeToday} items`,
|
||||
)
|
||||
|
||||
// Расходники селлеров - получаем из таблицы Supply (актуальные остатки на складе фулфилмента)
|
||||
// ИСПРАВЛЕНО: считаем из Supply с типом SELLER_CONSUMABLES
|
||||
const sellerSuppliesFromWarehouse = await prisma.supply.findMany({
|
||||
// V2: Расходники селлеров - получаем из SellerConsumableInventory
|
||||
const sellerInventoryFromWarehouse = await prisma.sellerConsumableInventory.findMany({
|
||||
where: {
|
||||
organizationId: organizationId, // Склад фулфилмента
|
||||
type: 'SELLER_CONSUMABLES', // ТОЛЬКО расходники селлеров
|
||||
fulfillmentCenterId: organizationId, // Склад фулфилмента
|
||||
},
|
||||
})
|
||||
|
||||
const sellerSuppliesCount = sellerSuppliesFromWarehouse.reduce(
|
||||
(sum, supply) => sum + (supply.currentStock || 0),
|
||||
const sellerSuppliesCount = sellerInventoryFromWarehouse.reduce(
|
||||
(sum, item) => sum + (item.currentStock || 0),
|
||||
0,
|
||||
)
|
||||
|
||||
console.warn(`💼 SELLER SUPPLIES DEBUG: totalCount=${sellerSuppliesCount} (from Supply warehouse)`)
|
||||
console.warn(`💼 SELLER SUPPLIES V2 DEBUG: totalCount=${sellerSuppliesCount} (from SellerConsumableInventory)`)
|
||||
|
||||
// Изменения расходников селлеров за сутки - считаем из Supply записей, созданных за сутки
|
||||
const sellerSuppliesReceivedToday = await prisma.supply.findMany({
|
||||
// V2: Изменения расходников селлеров за сутки - считаем поступления за сутки
|
||||
const sellerSuppliesReceivedTodayV2 = await prisma.sellerConsumableInventory.findMany({
|
||||
where: {
|
||||
organizationId: organizationId, // Склад фулфилмента
|
||||
type: 'SELLER_CONSUMABLES', // ТОЛЬКО расходники селлеров
|
||||
createdAt: { gte: oneDayAgo }, // Созданы за последние сутки
|
||||
fulfillmentCenterId: organizationId,
|
||||
lastSupplyDate: { gte: oneDayAgo }, // Пополнены за последние сутки
|
||||
},
|
||||
})
|
||||
|
||||
const sellerSuppliesChangeToday = sellerSuppliesReceivedToday.reduce(
|
||||
(sum, supply) => sum + (supply.currentStock || 0),
|
||||
const sellerSuppliesChangeToday = sellerSuppliesReceivedTodayV2.reduce(
|
||||
(sum, item) => sum + (item.totalReceived || 0),
|
||||
0,
|
||||
)
|
||||
|
||||
console.warn(
|
||||
`📊 SELLER SUPPLIES RECEIVED TODAY: ${sellerSuppliesReceivedToday.length} supplies, ${sellerSuppliesChangeToday} items`,
|
||||
`📊 SELLER SUPPLIES RECEIVED TODAY V2: ${sellerSuppliesReceivedTodayV2.length} supplies, ${sellerSuppliesChangeToday} items`,
|
||||
)
|
||||
|
||||
// Вычисляем процентные изменения
|
||||
@ -1551,8 +1547,10 @@ export const resolvers = {
|
||||
})
|
||||
},
|
||||
|
||||
// Расходники селлеров на складе фулфилмента (новый resolver)
|
||||
// V2: Расходники селлеров на складе фулфилмента (обновлено на V2 систему)
|
||||
sellerSuppliesOnWarehouse: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.warn('🚀 V2 SELLER SUPPLIES ON WAREHOUSE RESOLVER CALLED')
|
||||
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
@ -1573,60 +1571,115 @@ export const resolvers = {
|
||||
throw new GraphQLError('Доступ разрешен только для фулфилмент-центров')
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕНО: Усиленная фильтрация расходников селлеров
|
||||
const sellerSupplies = await prisma.supply.findMany({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id, // На складе этого фулфилмента
|
||||
type: 'SELLER_CONSUMABLES' as const, // Только расходники селлеров
|
||||
sellerOwnerId: { not: null }, // ОБЯЗАТЕЛЬНО должен быть владелец-селлер
|
||||
},
|
||||
include: {
|
||||
organization: true, // Фулфилмент-центр (хранитель)
|
||||
sellerOwner: true, // Селлер-владелец расходников
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
try {
|
||||
// V2: Получаем данные из SellerConsumableInventory вместо старой Supply таблицы
|
||||
const sellerInventory = await prisma.sellerConsumableInventory.findMany({
|
||||
where: {
|
||||
fulfillmentCenterId: currentUser.organization.id,
|
||||
},
|
||||
include: {
|
||||
seller: true,
|
||||
fulfillmentCenter: true,
|
||||
product: {
|
||||
include: {
|
||||
organization: true, // Поставщик товара
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ seller: { name: 'asc' } }, // Группируем по селлерам
|
||||
{ updatedAt: 'desc' },
|
||||
],
|
||||
})
|
||||
|
||||
// Логирование для отладки
|
||||
console.warn('🔍 ИСПРАВЛЕНО: Запрос расходников селлеров на складе фулфилмента:', {
|
||||
fulfillmentId: currentUser.organization.id,
|
||||
fulfillmentName: currentUser.organization.name,
|
||||
totalSupplies: sellerSupplies.length,
|
||||
sellerSupplies: sellerSupplies.map((supply) => ({
|
||||
id: supply.id,
|
||||
name: supply.name,
|
||||
type: supply.type,
|
||||
sellerOwnerId: supply.sellerOwnerId,
|
||||
sellerOwnerName: supply.sellerOwner?.name || supply.sellerOwner?.fullName,
|
||||
currentStock: supply.currentStock,
|
||||
})),
|
||||
})
|
||||
console.warn('📊 V2 Seller Inventory loaded for warehouse:', {
|
||||
fulfillmentId: currentUser.organization.id,
|
||||
fulfillmentName: currentUser.organization.name,
|
||||
inventoryCount: sellerInventory.length,
|
||||
uniqueSellers: new Set(sellerInventory.map(item => item.sellerId)).size,
|
||||
})
|
||||
|
||||
// ДВОЙНАЯ ПРОВЕРКА: Фильтруем на уровне кода для гарантии
|
||||
const filteredSupplies = sellerSupplies.filter((supply) => {
|
||||
const isValid =
|
||||
supply.type === 'SELLER_CONSUMABLES' && supply.sellerOwnerId != null && supply.sellerOwner != null
|
||||
// Преобразуем V2 данные в формат Supply для совместимости с фронтендом
|
||||
const suppliesFormatted = sellerInventory.map((item) => {
|
||||
const status = item.currentStock > 0 ? 'На складе' : 'Недоступен'
|
||||
const supplier = item.product.organization?.name || 'Неизвестен'
|
||||
|
||||
if (!isValid) {
|
||||
console.warn('⚠️ ОТФИЛЬТРОВАН некорректный расходник:', {
|
||||
id: supply.id,
|
||||
name: supply.name,
|
||||
type: supply.type,
|
||||
sellerOwnerId: supply.sellerOwnerId,
|
||||
hasSellerOwner: !!supply.sellerOwner,
|
||||
})
|
||||
}
|
||||
// Дополнительная проверка на null значения
|
||||
if (!item.seller?.inn) {
|
||||
console.error('❌ КРИТИЧЕСКАЯ ОШИБКА: seller.inn is null/undefined', {
|
||||
sellerId: item.sellerId,
|
||||
sellerName: item.seller?.name,
|
||||
itemId: item.id,
|
||||
})
|
||||
}
|
||||
|
||||
return isValid
|
||||
})
|
||||
return {
|
||||
// === ИДЕНТИФИКАЦИЯ ===
|
||||
id: item.id,
|
||||
productId: item.product.id,
|
||||
|
||||
// === ОСНОВНЫЕ ДАННЫЕ ===
|
||||
name: item.product.name,
|
||||
article: item.product.article,
|
||||
description: item.product.description || '',
|
||||
unit: item.product.unit || 'шт',
|
||||
category: item.product.category || 'Расходники',
|
||||
imageUrl: item.product.imageUrl,
|
||||
|
||||
// === СКЛАДСКИЕ ДАННЫЕ ===
|
||||
currentStock: item.currentStock,
|
||||
minStock: item.minStock,
|
||||
usedStock: item.totalUsed || 0,
|
||||
quantity: item.totalReceived,
|
||||
reservedStock: item.reservedStock,
|
||||
|
||||
// === ЦЕНЫ ===
|
||||
price: parseFloat(item.averageCost.toString()),
|
||||
pricePerUnit: item.usagePrice ? parseFloat(item.usagePrice.toString()) : null,
|
||||
|
||||
// === СТАТУС И МЕТАДАННЫЕ ===
|
||||
status,
|
||||
isAvailable: item.currentStock > 0,
|
||||
supplier,
|
||||
type: 'SELLER_CONSUMABLES', // Для совместимости с фронтендом
|
||||
date: item.lastSupplyDate?.toISOString() || item.createdAt.toISOString(),
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
updatedAt: item.updatedAt.toISOString(),
|
||||
|
||||
// === СВЯЗИ ===
|
||||
organization: {
|
||||
id: item.fulfillmentCenter.id,
|
||||
name: item.fulfillmentCenter.name,
|
||||
fullName: item.fulfillmentCenter.fullName,
|
||||
type: item.fulfillmentCenter.type,
|
||||
},
|
||||
sellerOwner: {
|
||||
id: item.seller.id,
|
||||
name: item.seller.name || 'Неизвестно',
|
||||
fullName: item.seller.fullName || item.seller.name || 'Неизвестно',
|
||||
inn: item.seller.inn || 'НЕ_УКАЗАН',
|
||||
type: item.seller.type,
|
||||
},
|
||||
sellerOwnerId: item.sellerId, // Для совместимости
|
||||
|
||||
// === ДОПОЛНИТЕЛЬНЫЕ ПОЛЯ ===
|
||||
notes: item.notes,
|
||||
actualQuantity: item.currentStock,
|
||||
}
|
||||
})
|
||||
|
||||
console.warn('✅ ФИНАЛЬНЫЙ РЕЗУЛЬТАТ после фильтрации:', {
|
||||
originalCount: sellerSupplies.length,
|
||||
filteredCount: filteredSupplies.length,
|
||||
removedCount: sellerSupplies.length - filteredSupplies.length,
|
||||
})
|
||||
console.warn('✅ V2 Seller Supplies formatted for frontend:', {
|
||||
count: suppliesFormatted.length,
|
||||
totalStock: suppliesFormatted.reduce((sum, item) => sum + item.currentStock, 0),
|
||||
lowStockItems: suppliesFormatted.filter(item => item.currentStock <= item.minStock).length,
|
||||
})
|
||||
|
||||
return filteredSupplies
|
||||
return suppliesFormatted
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error in V2 seller supplies on warehouse resolver:', error)
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
// Мои товары и расходники (для поставщиков)
|
||||
@ -2857,6 +2910,9 @@ export const resolvers = {
|
||||
|
||||
// Новая система складских остатков V2 (заменяет старый myFulfillmentSupplies)
|
||||
...fulfillmentInventoryV2Queries,
|
||||
|
||||
// V2 система складских остатков расходников селлера
|
||||
...sellerInventoryV2Queries,
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
@ -5513,46 +5569,8 @@ export const resolvers = {
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем расходники на основе заказанных товаров
|
||||
// Расходники создаются в организации получателя (фулфилмент-центре)
|
||||
// Определяем тип расходников на основе consumableType
|
||||
const supplyType =
|
||||
args.input.consumableType === 'SELLER_CONSUMABLES' ? 'SELLER_CONSUMABLES' : 'FULFILLMENT_CONSUMABLES'
|
||||
|
||||
// Определяем sellerOwnerId для расходников селлеров
|
||||
const sellerOwnerId = supplyType === 'SELLER_CONSUMABLES' ? currentUser.organization!.id : null
|
||||
|
||||
const suppliesData = args.input.items.map((item) => {
|
||||
const product = products.find((p) => p.id === item.productId)!
|
||||
const productWithCategory = supplyOrder.items.find(
|
||||
(orderItem: { productId: string; product: { category?: { name: string } | null } }) =>
|
||||
orderItem.productId === item.productId,
|
||||
)?.product
|
||||
|
||||
return {
|
||||
name: product.name,
|
||||
article: product.article, // ИСПРАВЛЕНО: Добавляем артикул товара для уникальности
|
||||
description: product.description || `Заказано у ${partner.name}`,
|
||||
price: product.price, // Цена закупки у поставщика
|
||||
quantity: item.quantity,
|
||||
unit: 'шт',
|
||||
category: productWithCategory?.category?.name || 'Расходники',
|
||||
status: 'planned', // Статус "запланировано" (ожидает одобрения поставщиком)
|
||||
date: new Date(args.input.deliveryDate),
|
||||
supplier: partner.name || partner.fullName || 'Не указан',
|
||||
minStock: Math.round(item.quantity * 0.1), // 10% от заказанного как минимальный остаток
|
||||
currentStock: 0, // Пока товар не пришел
|
||||
type: supplyType, // ИСПРАВЛЕНО: Добавляем тип расходников
|
||||
sellerOwnerId: sellerOwnerId, // ИСПРАВЛЕНО: Добавляем владельца для расходников селлеров
|
||||
// Расходники создаются в организации получателя (фулфилмент-центре)
|
||||
organizationId: fulfillmentCenterId || currentUser.organization!.id,
|
||||
}
|
||||
})
|
||||
|
||||
// Создаем расходники
|
||||
await prisma.supply.createMany({
|
||||
data: suppliesData,
|
||||
})
|
||||
// V2 СИСТЕМА: Расходники будут автоматически созданы при подтверждении заказа
|
||||
console.warn('📦 V2 система: расходники будут созданы автоматически при доставке через соответствующие резолверы')
|
||||
|
||||
// 🔔 ОТПРАВЛЯЕМ УВЕДОМЛЕНИЕ ПОСТАВЩИКУ О НОВОМ ЗАКАЗЕ
|
||||
try {
|
||||
@ -7299,113 +7317,8 @@ export const resolvers = {
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем расходники
|
||||
for (const item of existingOrder.items) {
|
||||
console.warn('📦 Обрабатываем товар:', {
|
||||
productName: item.product.name,
|
||||
quantity: item.quantity,
|
||||
targetOrganizationId,
|
||||
consumableType: existingOrder.consumableType,
|
||||
})
|
||||
|
||||
// ИСПРАВЛЕНИЕ: Определяем правильный тип расходников
|
||||
const isSellerSupply = existingOrder.consumableType === 'SELLER_CONSUMABLES'
|
||||
const supplyType = isSellerSupply ? 'SELLER_CONSUMABLES' : 'FULFILLMENT_CONSUMABLES'
|
||||
const sellerOwnerId = isSellerSupply ? existingOrder.organizationId : null
|
||||
|
||||
console.warn('🔍 Определен тип расходников:', {
|
||||
isSellerSupply,
|
||||
supplyType,
|
||||
sellerOwnerId,
|
||||
})
|
||||
|
||||
// ИСПРАВЛЕНИЕ: Ищем по Артикул СФ для уникальности вместо имени
|
||||
const whereCondition = isSellerSupply
|
||||
? {
|
||||
organizationId: targetOrganizationId,
|
||||
article: item.product.article, // ИЗМЕНЕНО: поиск по article вместо name
|
||||
type: 'SELLER_CONSUMABLES' as const,
|
||||
sellerOwnerId: existingOrder.organizationId,
|
||||
}
|
||||
: {
|
||||
organizationId: targetOrganizationId,
|
||||
article: item.product.article, // ИЗМЕНЕНО: поиск по article вместо name
|
||||
type: 'FULFILLMENT_CONSUMABLES' as const,
|
||||
sellerOwnerId: null, // Для фулфилмента sellerOwnerId должен быть null
|
||||
}
|
||||
|
||||
console.warn('🔍 Ищем существующий расходник с условиями:', whereCondition)
|
||||
|
||||
const existingSupply = await prisma.supply.findFirst({
|
||||
where: whereCondition,
|
||||
})
|
||||
|
||||
if (existingSupply) {
|
||||
console.warn('📈 ОБНОВЛЯЕМ существующий расходник:', {
|
||||
id: existingSupply.id,
|
||||
oldStock: existingSupply.currentStock,
|
||||
oldQuantity: existingSupply.quantity,
|
||||
addingQuantity: item.quantity,
|
||||
})
|
||||
|
||||
// ОБНОВЛЯЕМ существующий расходник
|
||||
const updatedSupply = await prisma.supply.update({
|
||||
where: { id: existingSupply.id },
|
||||
data: {
|
||||
currentStock: existingSupply.currentStock + item.quantity,
|
||||
// ❌ ИСПРАВЛЕНО: НЕ обновляем quantity - это изначальное количество заказа!
|
||||
// quantity остается как было изначально заказано
|
||||
status: 'in-stock', // Меняем статус на "на складе"
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
console.warn('✅ Расходник ОБНОВЛЕН (НЕ создан дубликат):', {
|
||||
id: updatedSupply.id,
|
||||
name: updatedSupply.name,
|
||||
newCurrentStock: updatedSupply.currentStock,
|
||||
newTotalQuantity: updatedSupply.quantity,
|
||||
type: updatedSupply.type,
|
||||
})
|
||||
} else {
|
||||
console.warn('➕ СОЗДАЕМ новый расходник (не найден существующий):', {
|
||||
name: item.product.name,
|
||||
quantity: item.quantity,
|
||||
organizationId: targetOrganizationId,
|
||||
type: supplyType,
|
||||
sellerOwnerId: sellerOwnerId,
|
||||
})
|
||||
|
||||
// СОЗДАЕМ новый расходник
|
||||
const newSupply = await prisma.supply.create({
|
||||
data: {
|
||||
name: item.product.name,
|
||||
article: item.product.article, // ДОБАВЛЕНО: Артикул СФ для уникальности
|
||||
description: item.product.description || `Поставка от ${existingOrder.partner.name}`,
|
||||
price: item.price, // Цена закупки у поставщика
|
||||
quantity: item.quantity,
|
||||
unit: 'шт',
|
||||
category: item.product.category?.name || 'Расходники',
|
||||
status: 'in-stock',
|
||||
date: new Date(),
|
||||
supplier: existingOrder.partner.name || existingOrder.partner.fullName || 'Не указан',
|
||||
minStock: Math.round(item.quantity * 0.1),
|
||||
currentStock: item.quantity,
|
||||
organizationId: targetOrganizationId,
|
||||
type: supplyType as 'SELLER_CONSUMABLES' | 'FULFILLMENT_CONSUMABLES',
|
||||
sellerOwnerId: sellerOwnerId,
|
||||
},
|
||||
})
|
||||
|
||||
console.warn('✅ Новый расходник СОЗДАН:', {
|
||||
id: newSupply.id,
|
||||
name: newSupply.name,
|
||||
currentStock: newSupply.currentStock,
|
||||
type: newSupply.type,
|
||||
sellerOwnerId: newSupply.sellerOwnerId,
|
||||
})
|
||||
}
|
||||
}
|
||||
// V2 СИСТЕМА: Расходники автоматически обрабатываются в seller-consumables.ts и fulfillment-consumables.ts
|
||||
console.warn('📦 V2 система автоматически обработает инвентарь через специализированные резолверы')
|
||||
|
||||
console.warn('🎉 Склад организации успешно обновлен!')
|
||||
}
|
||||
@ -8412,54 +8325,8 @@ export const resolvers = {
|
||||
},
|
||||
})
|
||||
|
||||
// Добавляем расходники в склад фулфилмента как 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} единиц`,
|
||||
)
|
||||
}
|
||||
}
|
||||
// V2 СИСТЕМА: Инвентарь селлера автоматически обновляется через SellerConsumableInventory
|
||||
console.warn('📦 V2 система автоматически обновит SellerConsumableInventory через processSellerConsumableSupplyReceipt')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@ -8565,81 +8432,8 @@ export const resolvers = {
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем склад фулфилмента с учетом типа расходников
|
||||
console.warn('📦 Обновляем склад фулфилмента...')
|
||||
console.warn(`🏷️ Тип поставки: ${existingOrder.consumableType || 'FULFILLMENT_CONSUMABLES'}`)
|
||||
|
||||
for (const item of existingOrder.items) {
|
||||
// Определяем тип расходников и владельца
|
||||
const isSellerSupply = existingOrder.consumableType === 'SELLER_CONSUMABLES'
|
||||
const supplyType = isSellerSupply ? 'SELLER_CONSUMABLES' : 'FULFILLMENT_CONSUMABLES'
|
||||
const sellerOwnerId = isSellerSupply ? updatedOrder.organization?.id : null
|
||||
|
||||
// Для расходников селлеров ищем по Артикул СФ И по владельцу
|
||||
const whereCondition = isSellerSupply
|
||||
? {
|
||||
organizationId: currentUser.organization.id,
|
||||
article: item.product.article, // ИЗМЕНЕНО: поиск по article вместо name
|
||||
type: 'SELLER_CONSUMABLES' as const,
|
||||
sellerOwnerId: sellerOwnerId,
|
||||
}
|
||||
: {
|
||||
organizationId: currentUser.organization.id,
|
||||
article: item.product.article, // ИЗМЕНЕНО: поиск по article вместо name
|
||||
type: 'FULFILLMENT_CONSUMABLES' as const,
|
||||
}
|
||||
|
||||
const existingSupply = await prisma.supply.findFirst({
|
||||
where: whereCondition,
|
||||
})
|
||||
|
||||
if (existingSupply) {
|
||||
await prisma.supply.update({
|
||||
where: { id: existingSupply.id },
|
||||
data: {
|
||||
currentStock: existingSupply.currentStock + item.quantity,
|
||||
// ❌ ИСПРАВЛЕНО: НЕ обновляем quantity - это изначальное количество заказа!
|
||||
status: 'in-stock',
|
||||
},
|
||||
})
|
||||
console.warn(
|
||||
`📈 Обновлен существующий ${
|
||||
isSellerSupply ? 'расходник селлера' : 'расходник фулфилмента'
|
||||
} "${item.product.name}" ${
|
||||
isSellerSupply ? `(владелец: ${updatedOrder.organization?.name})` : ''
|
||||
}: ${existingSupply.currentStock} -> ${existingSupply.currentStock + item.quantity}`,
|
||||
)
|
||||
} else {
|
||||
await prisma.supply.create({
|
||||
data: {
|
||||
name: item.product.name,
|
||||
article: item.product.article, // ДОБАВЛЕНО: Артикул СФ для уникальности
|
||||
description: isSellerSupply
|
||||
? `Расходники селлера ${updatedOrder.organization?.name || updatedOrder.organization?.fullName}`
|
||||
: item.product.description || `Расходники от ${updatedOrder.partner.name}`,
|
||||
price: item.price, // Цена закупки у поставщика
|
||||
quantity: item.quantity,
|
||||
actualQuantity: item.quantity, // НОВОЕ: Фактически поставленное количество
|
||||
currentStock: item.quantity,
|
||||
usedStock: 0,
|
||||
unit: 'шт',
|
||||
category: item.product.category?.name || 'Расходники',
|
||||
status: 'in-stock',
|
||||
supplier: updatedOrder.partner.name || updatedOrder.partner.fullName || 'Поставщик',
|
||||
type: supplyType as 'SELLER_CONSUMABLES' | 'FULFILLMENT_CONSUMABLES',
|
||||
sellerOwnerId: sellerOwnerId,
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
})
|
||||
console.warn(
|
||||
`➕ Создан новый ${
|
||||
isSellerSupply ? 'расходник селлера' : 'расходник фулфилмента'
|
||||
} "${item.product.name}" ${
|
||||
isSellerSupply ? `(владелец: ${updatedOrder.organization?.name})` : ''
|
||||
}: ${item.quantity} единиц`,
|
||||
)
|
||||
}
|
||||
}
|
||||
// V2 СИСТЕМА: Инвентарь автоматически обновляется через специализированные резолверы
|
||||
console.warn('📦 V2 система: склад обновится автоматически через FulfillmentConsumableInventory и SellerConsumableInventory')
|
||||
|
||||
console.warn('🎉 Синхронизация склада завершена успешно!')
|
||||
|
||||
|
Reference in New Issue
Block a user