Обновлены модели и компоненты для управления поставками и расходниками. Добавлены новые поля в модели SupplyOrder и соответствующие резолверы для поддержки логистики. Реализованы компоненты уведомлений для отображения статуса логистических заявок и поставок. Оптимизирован интерфейс для улучшения пользовательского опыта, добавлены логи для диагностики запросов. Обновлены GraphQL схемы и мутации для поддержки новых функциональных возможностей.
This commit is contained in:
@ -900,7 +900,7 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
// Возвращаем заказы где текущая организация является заказчиком, поставщиком, получателем или логистическим партнером
|
||||
return await prisma.supplyOrder.findMany({
|
||||
const orders = await prisma.supplyOrder.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ organizationId: currentUser.organization.id }, // Заказы созданные организацией
|
||||
@ -939,6 +939,8 @@ export const resolvers = {
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return orders;
|
||||
},
|
||||
|
||||
// Счетчик поставок, требующих одобрения
|
||||
@ -969,12 +971,17 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
|
||||
// Расходники селлеров (созданные другими для нас) - требуют подтверждения получения
|
||||
// Расходники селлеров (созданные другими для нас) - требуют действий фулфилмента
|
||||
const sellerSupplyOrders = await prisma.supplyOrder.count({
|
||||
where: {
|
||||
fulfillmentCenterId: currentUser.organization.id, // Получатель - мы
|
||||
organizationId: { not: currentUser.organization.id }, // Создали НЕ мы
|
||||
status: "IN_TRANSIT", // В пути - нужно подтвердить получение
|
||||
status: {
|
||||
in: [
|
||||
"SUPPLIER_APPROVED", // Поставщик подтвердил - нужно назначить логистику
|
||||
"IN_TRANSIT", // В пути - нужно подтвердить получение
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -986,9 +993,30 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
|
||||
// Общий счетчик поставок
|
||||
const pendingSupplyOrders =
|
||||
ourSupplyOrders + sellerSupplyOrders + incomingSupplierOrders;
|
||||
// 🚚 ЛОГИСТИЧЕСКИЕ ЗАЯВКИ ДЛЯ ЛОГИСТИКИ (LOGIST) - требуют действий логистики
|
||||
const logisticsOrders = await prisma.supplyOrder.count({
|
||||
where: {
|
||||
logisticsPartnerId: currentUser.organization.id, // Мы - назначенная логистика
|
||||
status: {
|
||||
in: [
|
||||
"CONFIRMED", // Подтверждено фулфилментом - нужно подтвердить логистикой
|
||||
"LOGISTICS_CONFIRMED", // Подтверждено логистикой - нужно забрать товар у поставщика
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Общий счетчик поставок в зависимости от типа организации
|
||||
let pendingSupplyOrders = 0;
|
||||
if (currentUser.organization.type === "FULFILLMENT") {
|
||||
pendingSupplyOrders = ourSupplyOrders + sellerSupplyOrders;
|
||||
} else if (currentUser.organization.type === "WHOLESALE") {
|
||||
pendingSupplyOrders = incomingSupplierOrders;
|
||||
} else if (currentUser.organization.type === "LOGIST") {
|
||||
pendingSupplyOrders = logisticsOrders;
|
||||
} else if (currentUser.organization.type === "SELLER") {
|
||||
pendingSupplyOrders = 0; // Селлеры не подтверждают поставки, только отслеживают
|
||||
}
|
||||
|
||||
// Считаем входящие заявки на партнерство со статусом PENDING
|
||||
const pendingIncomingRequests = await prisma.counterpartyRequest.count({
|
||||
@ -1003,6 +1031,7 @@ export const resolvers = {
|
||||
ourSupplyOrders: ourSupplyOrders, // Расходники фулфилмента
|
||||
sellerSupplyOrders: sellerSupplyOrders, // Расходники селлеров
|
||||
incomingSupplierOrders: incomingSupplierOrders, // 🔔 Входящие заказы для поставщиков
|
||||
logisticsOrders: logisticsOrders, // 🚚 Логистические заявки для логистики
|
||||
incomingRequests: pendingIncomingRequests,
|
||||
total: pendingSupplyOrders + pendingIncomingRequests,
|
||||
};
|
||||
@ -1146,9 +1175,11 @@ export const resolvers = {
|
||||
);
|
||||
|
||||
// Подсчитываем количество из таблицы Supply (актуальные остатки на складе фулфилмента)
|
||||
// ИСПРАВЛЕНО: считаем только расходники фулфилмента, исключаем расходники селлеров
|
||||
const fulfillmentSuppliesFromWarehouse = await prisma.supply.findMany({
|
||||
where: {
|
||||
organizationId: organizationId, // Склад фулфилмента
|
||||
type: "FULFILLMENT_CONSUMABLES", // ТОЛЬКО расходники фулфилмента
|
||||
},
|
||||
});
|
||||
|
||||
@ -1203,39 +1234,40 @@ export const resolvers = {
|
||||
`📊 FULFILLMENT SUPPLIES RECEIVED TODAY (ПРИБЫЛО): ${fulfillmentSuppliesReceivedToday.length} orders, ${fulfillmentSuppliesChangeToday} items`
|
||||
);
|
||||
|
||||
// Расходники селлеров - получаем из заказов от селлеров (расходники = CONSUMABLE)
|
||||
// Согласно правилам: селлеры заказывают расходники у поставщиков и доставляют на склад фулфилмента
|
||||
const sellerSuppliesCount = sellerDeliveredOrders.reduce(
|
||||
(sum, order) =>
|
||||
sum +
|
||||
order.items.reduce(
|
||||
(itemSum, item) =>
|
||||
itemSum +
|
||||
(item.product.type === "CONSUMABLE" ? item.quantity : 0),
|
||||
0
|
||||
),
|
||||
// Расходники селлеров - получаем из таблицы Supply (актуальные остатки на складе фулфилмента)
|
||||
// ИСПРАВЛЕНО: считаем из Supply с типом SELLER_CONSUMABLES
|
||||
const sellerSuppliesFromWarehouse = await prisma.supply.findMany({
|
||||
where: {
|
||||
organizationId: organizationId, // Склад фулфилмента
|
||||
type: "SELLER_CONSUMABLES", // ТОЛЬКО расходники селлеров
|
||||
},
|
||||
});
|
||||
|
||||
const sellerSuppliesCount = sellerSuppliesFromWarehouse.reduce(
|
||||
(sum, supply) => sum + (supply.currentStock || 0),
|
||||
0
|
||||
);
|
||||
|
||||
console.log(
|
||||
`💼 SELLER SUPPLIES DEBUG: totalCount=${sellerSuppliesCount} (from delivered orders)`
|
||||
`💼 SELLER SUPPLIES DEBUG: totalCount=${sellerSuppliesCount} (from Supply warehouse)`
|
||||
);
|
||||
|
||||
// Изменения расходников селлеров за сутки - используем уже полученные данные
|
||||
const sellerSuppliesChangeToday = recentSellerDeliveredOrders.reduce(
|
||||
(sum, order) =>
|
||||
sum +
|
||||
order.items.reduce(
|
||||
(itemSum, item) =>
|
||||
itemSum +
|
||||
(item.product.type === "CONSUMABLE" ? item.quantity : 0),
|
||||
0
|
||||
),
|
||||
// Изменения расходников селлеров за сутки - считаем из Supply записей, созданных за сутки
|
||||
const sellerSuppliesReceivedToday = await prisma.supply.findMany({
|
||||
where: {
|
||||
organizationId: organizationId, // Склад фулфилмента
|
||||
type: "SELLER_CONSUMABLES", // ТОЛЬКО расходники селлеров
|
||||
createdAt: { gte: oneDayAgo }, // Созданы за последние сутки
|
||||
},
|
||||
});
|
||||
|
||||
const sellerSuppliesChangeToday = sellerSuppliesReceivedToday.reduce(
|
||||
(sum, supply) => sum + (supply.currentStock || 0),
|
||||
0
|
||||
);
|
||||
|
||||
console.log(
|
||||
`📊 SELLER SUPPLIES RECEIVED TODAY: ${recentSellerDeliveredOrders.length} orders, ${sellerSuppliesChangeToday} items`
|
||||
`📊 SELLER SUPPLIES RECEIVED TODAY: ${sellerSuppliesReceivedToday.length} supplies, ${sellerSuppliesChangeToday} items`
|
||||
);
|
||||
|
||||
// Вычисляем процентные изменения
|
||||
@ -1327,6 +1359,24 @@ export const resolvers = {
|
||||
});
|
||||
},
|
||||
|
||||
// Логистические партнеры (организации-логисты)
|
||||
logisticsPartners: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
// Получаем все организации типа LOGIST
|
||||
return await prisma.organization.findMany({
|
||||
where: {
|
||||
type: "LOGIST",
|
||||
// Убираем фильтр по статусу пока не определим правильные значения
|
||||
},
|
||||
orderBy: { createdAt: "desc" }, // Сортируем по дате создания вместо name
|
||||
});
|
||||
},
|
||||
|
||||
// Мои поставки Wildberries
|
||||
myWildberriesSupplies: async (
|
||||
_: unknown,
|
||||
@ -1358,6 +1408,94 @@ export const resolvers = {
|
||||
});
|
||||
},
|
||||
|
||||
// Расходники селлеров на складе фулфилмента (новый resolver)
|
||||
sellerSuppliesOnWarehouse: async (
|
||||
_: unknown,
|
||||
__: unknown,
|
||||
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("Доступ разрешен только для фулфилмент-центров");
|
||||
}
|
||||
|
||||
// ИСПРАВЛЕНО: Усиленная фильтрация расходников селлеров
|
||||
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" },
|
||||
});
|
||||
|
||||
// Логирование для отладки
|
||||
console.log(
|
||||
"🔍 ИСПРАВЛЕНО: Запрос расходников селлеров на складе фулфилмента:",
|
||||
{
|
||||
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,
|
||||
})),
|
||||
}
|
||||
);
|
||||
|
||||
// ДВОЙНАЯ ПРОВЕРКА: Фильтруем на уровне кода для гарантии
|
||||
const filteredSupplies = sellerSupplies.filter((supply) => {
|
||||
const isValid =
|
||||
supply.type === "SELLER_CONSUMABLES" &&
|
||||
supply.sellerOwnerId != null &&
|
||||
supply.sellerOwner != null;
|
||||
|
||||
if (!isValid) {
|
||||
console.warn("⚠️ ОТФИЛЬТРОВАН некорректный расходник:", {
|
||||
id: supply.id,
|
||||
name: supply.name,
|
||||
type: supply.type,
|
||||
sellerOwnerId: supply.sellerOwnerId,
|
||||
hasSellerOwner: !!supply.sellerOwner,
|
||||
});
|
||||
}
|
||||
|
||||
return isValid;
|
||||
});
|
||||
|
||||
console.log("✅ ФИНАЛЬНЫЙ РЕЗУЛЬТАТ после фильтрации:", {
|
||||
originalCount: sellerSupplies.length,
|
||||
filteredCount: filteredSupplies.length,
|
||||
removedCount: sellerSupplies.length - filteredSupplies.length,
|
||||
});
|
||||
|
||||
return filteredSupplies;
|
||||
},
|
||||
|
||||
// Мои товары и расходники (для поставщиков)
|
||||
myProducts: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log("🔍 MY_PRODUCTS RESOLVER - ВЫЗВАН:", {
|
||||
@ -1830,34 +1968,52 @@ export const resolvers = {
|
||||
|
||||
// Сотрудники организации
|
||||
myEmployees: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log("🔍 myEmployees resolver called");
|
||||
|
||||
if (!context.user) {
|
||||
console.log("❌ No user in context for myEmployees");
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true },
|
||||
});
|
||||
console.log("✅ User authenticated for myEmployees:", context.user.id);
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
try {
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
console.log("❌ User has no organization");
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
console.log(
|
||||
"📊 User organization type:",
|
||||
currentUser.organization.type
|
||||
);
|
||||
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
console.log("❌ Not a fulfillment center");
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
console.log("👥 Found employees:", employees.length);
|
||||
return employees;
|
||||
} catch (error) {
|
||||
console.error("❌ Error in myEmployees resolver:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return employees;
|
||||
},
|
||||
|
||||
// Получение сотрудника по ID
|
||||
@ -3937,7 +4093,19 @@ export const resolvers = {
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
console.log("🔍 Проверка пользователя:", {
|
||||
userId: context.user.id,
|
||||
userFound: !!currentUser,
|
||||
organizationFound: !!currentUser?.organization,
|
||||
organizationType: currentUser?.organization?.type,
|
||||
organizationId: currentUser?.organization?.id,
|
||||
});
|
||||
|
||||
if (!currentUser) {
|
||||
throw new GraphQLError("Пользователь не найден");
|
||||
}
|
||||
|
||||
if (!currentUser.organization) {
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
@ -4067,21 +4235,34 @@ export const resolvers = {
|
||||
initialStatus = "CONFIRMED"; // Логист может сразу подтверждать заказы
|
||||
}
|
||||
|
||||
const supplyOrder = await prisma.supplyOrder.create({
|
||||
data: {
|
||||
partnerId: args.input.partnerId,
|
||||
deliveryDate: new Date(args.input.deliveryDate),
|
||||
totalAmount: new Prisma.Decimal(totalAmount),
|
||||
totalItems: totalItems,
|
||||
organizationId: currentUser.organization.id,
|
||||
fulfillmentCenterId: fulfillmentCenterId,
|
||||
logisticsPartnerId: args.input.logisticsPartnerId,
|
||||
consumableType: args.input.consumableType, // Классификация расходников
|
||||
status: initialStatus,
|
||||
items: {
|
||||
create: orderItems,
|
||||
},
|
||||
// Подготавливаем данные для создания заказа
|
||||
const createData: any = {
|
||||
partnerId: args.input.partnerId,
|
||||
deliveryDate: new Date(args.input.deliveryDate),
|
||||
totalAmount: new Prisma.Decimal(totalAmount),
|
||||
totalItems: totalItems,
|
||||
organizationId: currentUser.organization.id,
|
||||
fulfillmentCenterId: fulfillmentCenterId,
|
||||
consumableType: args.input.consumableType,
|
||||
status: initialStatus,
|
||||
items: {
|
||||
create: orderItems,
|
||||
},
|
||||
};
|
||||
|
||||
// 🔄 ЛОГИСТИКА ОПЦИОНАЛЬНА: добавляем только если передана
|
||||
if (args.input.logisticsPartnerId) {
|
||||
createData.logisticsPartnerId = args.input.logisticsPartnerId;
|
||||
}
|
||||
|
||||
console.log("🔍 Создаем SupplyOrder с данными:", {
|
||||
hasLogistics: !!args.input.logisticsPartnerId,
|
||||
logisticsId: args.input.logisticsPartnerId,
|
||||
createData: createData,
|
||||
});
|
||||
|
||||
const supplyOrder = await prisma.supplyOrder.create({
|
||||
data: createData,
|
||||
include: {
|
||||
partner: {
|
||||
include: {
|
||||
@ -5961,17 +6142,13 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
if (product) {
|
||||
// Согласно правилам: Основные значения = Предыдущие остатки + Прибыло - Убыло
|
||||
const currentStock = product.stock || product.quantity || 0;
|
||||
const newStock = Math.max(currentStock - item.quantity, 0);
|
||||
|
||||
// ИСПРАВЛЕНО: НЕ списываем повторно, только переводим из inTransit в sold
|
||||
// Остаток уже был уменьшен при создании/одобрении заказа
|
||||
await prisma.product.update({
|
||||
where: { id: item.product.id },
|
||||
data: {
|
||||
// Обновляем основные остатки (УБЫЛО)
|
||||
stock: newStock,
|
||||
quantity: newStock, // Синхронизируем оба поля для совместимости
|
||||
// Обновляем дополнительные значения
|
||||
// НЕ ТРОГАЕМ stock - он уже правильно уменьшен при заказе
|
||||
// Только переводим из inTransit в sold
|
||||
inTransit: Math.max(
|
||||
(product.inTransit || 0) - item.quantity,
|
||||
0
|
||||
@ -5980,7 +6157,11 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
console.log(
|
||||
`✅ Товар поставщика "${product.name}" обновлен: доставлено ${item.quantity} единиц (остаток: ${currentStock} -> ${newStock})`
|
||||
`✅ Товар поставщика "${product.name}" обновлен: доставлено ${
|
||||
item.quantity
|
||||
} единиц (остаток НЕ ИЗМЕНЕН: ${
|
||||
product.stock || product.quantity || 0
|
||||
})`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6073,6 +6254,117 @@ export const resolvers = {
|
||||
}
|
||||
},
|
||||
|
||||
// Назначение логистики фулфилментом на заказ селлера
|
||||
assignLogisticsToSupply: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
supplyOrderId: string;
|
||||
logisticsPartnerId: string;
|
||||
responsibleId?: string;
|
||||
},
|
||||
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 existingOrder = await prisma.supplyOrder.findUnique({
|
||||
where: { id: args.supplyOrderId },
|
||||
include: {
|
||||
partner: true,
|
||||
fulfillmentCenter: true,
|
||||
logisticsPartner: true,
|
||||
items: {
|
||||
include: { product: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingOrder) {
|
||||
throw new GraphQLError("Заказ поставки не найден");
|
||||
}
|
||||
|
||||
// Проверяем, что это заказ для нашего фулфилмент-центра
|
||||
if (existingOrder.fulfillmentCenterId !== currentUser.organization.id) {
|
||||
throw new GraphQLError("Нет доступа к этому заказу");
|
||||
}
|
||||
|
||||
// Проверяем, что статус позволяет назначить логистику
|
||||
if (existingOrder.status !== "SUPPLIER_APPROVED") {
|
||||
throw new GraphQLError(
|
||||
`Нельзя назначить логистику для заказа со статусом ${existingOrder.status}`
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем, что логистическая компания существует
|
||||
const logisticsPartner = await prisma.organization.findUnique({
|
||||
where: { id: args.logisticsPartnerId },
|
||||
});
|
||||
|
||||
if (!logisticsPartner || logisticsPartner.type !== "LOGIST") {
|
||||
throw new GraphQLError("Логистическая компания не найдена");
|
||||
}
|
||||
|
||||
// Обновляем заказ
|
||||
const updatedOrder = await prisma.supplyOrder.update({
|
||||
where: { id: args.supplyOrderId },
|
||||
data: {
|
||||
logisticsPartner: {
|
||||
connect: { id: args.logisticsPartnerId },
|
||||
},
|
||||
status: "CONFIRMED", // Переводим в статус "подтвержден фулфилментом"
|
||||
},
|
||||
include: {
|
||||
partner: true,
|
||||
fulfillmentCenter: true,
|
||||
logisticsPartner: true,
|
||||
items: {
|
||||
include: { product: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Логистика назначена на заказ ${args.supplyOrderId}:`, {
|
||||
logisticsPartner: logisticsPartner.name,
|
||||
responsible: args.responsibleId,
|
||||
newStatus: "CONFIRMED",
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Логистика успешно назначена",
|
||||
order: updatedOrder,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ Ошибка при назначении логистики:", error);
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Ошибка при назначении логистики",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Резолверы для новых действий с заказами поставок
|
||||
supplierApproveOrder: async (
|
||||
_: unknown,
|
||||
@ -6458,7 +6750,7 @@ export const resolvers = {
|
||||
where: {
|
||||
id: args.id,
|
||||
logisticsPartnerId: currentUser.organization.id,
|
||||
status: "SUPPLIER_APPROVED",
|
||||
OR: [{ status: "SUPPLIER_APPROVED" }, { status: "CONFIRMED" }],
|
||||
},
|
||||
});
|
||||
|
||||
@ -6530,7 +6822,7 @@ export const resolvers = {
|
||||
where: {
|
||||
id: args.id,
|
||||
logisticsPartnerId: currentUser.organization.id,
|
||||
status: "SUPPLIER_APPROVED",
|
||||
OR: [{ status: "SUPPLIER_APPROVED" }, { status: "CONFIRMED" }],
|
||||
},
|
||||
});
|
||||
|
||||
@ -6608,9 +6900,15 @@ export const resolvers = {
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: true,
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organization: true, // Селлер-создатель заказа
|
||||
partner: true, // Поставщик
|
||||
},
|
||||
});
|
||||
|
||||
@ -6651,17 +6949,13 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
if (product) {
|
||||
// Согласно правилам: Основные значения = Предыдущие остатки + Прибыло - Убыло
|
||||
const currentStock = product.stock || product.quantity || 0;
|
||||
const newStock = Math.max(currentStock - item.quantity, 0);
|
||||
|
||||
// ИСПРАВЛЕНО: НЕ списываем повторно, только переводим из inTransit в sold
|
||||
// Остаток уже был уменьшен при создании/одобрении заказа
|
||||
await prisma.product.update({
|
||||
where: { id: item.product.id },
|
||||
data: {
|
||||
// Обновляем основные остатки (УБЫЛО)
|
||||
stock: newStock,
|
||||
quantity: newStock, // Синхронизируем оба поля для совместимости
|
||||
// Обновляем дополнительные значения
|
||||
// НЕ ТРОГАЕМ stock - он уже правильно уменьшен при заказе
|
||||
// Только переводим из inTransit в sold
|
||||
inTransit: Math.max(
|
||||
(product.inTransit || 0) - item.quantity,
|
||||
0
|
||||
@ -6670,33 +6964,62 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
console.log(
|
||||
`✅ Товар поставщика "${product.name}" обновлен: доставлено ${item.quantity} единиц`
|
||||
`✅ Товар поставщика "${product.name}" обновлен: получено ${item.quantity} единиц`
|
||||
);
|
||||
console.log(
|
||||
` 📊 Остатки: ${currentStock} -> ${newStock} (УБЫЛО: ${item.quantity})`
|
||||
` 📊 Остаток: ${
|
||||
product.stock || product.quantity || 0
|
||||
} (НЕ ИЗМЕНЕН - уже списан при заказе)`
|
||||
);
|
||||
console.log(
|
||||
` 🚚 В пути: ${product.inTransit || 0} -> ${Math.max(
|
||||
(product.inTransit || 0) - item.quantity,
|
||||
0
|
||||
)}`
|
||||
)} (УБЫЛО: ${item.quantity})`
|
||||
);
|
||||
console.log(
|
||||
` 💰 Продано: ${product.sold || 0} -> ${
|
||||
(product.sold || 0) + item.quantity
|
||||
}`
|
||||
} (ПРИБЫЛО: ${item.quantity})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем склад фулфилмента
|
||||
// Обновляем склад фулфилмента с учетом типа расходников
|
||||
console.log("📦 Обновляем склад фулфилмента...");
|
||||
console.log(
|
||||
`🏷️ Тип поставки: ${
|
||||
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,
|
||||
name: item.product.name,
|
||||
type: "SELLER_CONSUMABLES" as const,
|
||||
sellerOwnerId: sellerOwnerId,
|
||||
}
|
||||
: {
|
||||
organizationId: currentUser.organization.id,
|
||||
name: item.product.name,
|
||||
type: "FULFILLMENT_CONSUMABLES" as const,
|
||||
};
|
||||
|
||||
const existingSupply = await prisma.supply.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
name: item.product.name,
|
||||
},
|
||||
where: whereCondition,
|
||||
});
|
||||
|
||||
if (existingSupply) {
|
||||
@ -6709,9 +7032,13 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
console.log(
|
||||
`📈 Обновлен существующий расходник фулфилмента "${
|
||||
item.product.name
|
||||
}": ${existingSupply.currentStock} -> ${
|
||||
`📈 Обновлен существующий ${
|
||||
isSellerSupply ? "расходник селлера" : "расходник фулфилмента"
|
||||
} "${item.product.name}" ${
|
||||
isSellerSupply
|
||||
? `(владелец: ${updatedOrder.organization?.name})`
|
||||
: ""
|
||||
}: ${existingSupply.currentStock} -> ${
|
||||
existingSupply.currentStock + item.quantity
|
||||
}`
|
||||
);
|
||||
@ -6719,9 +7046,13 @@ export const resolvers = {
|
||||
await prisma.supply.create({
|
||||
data: {
|
||||
name: item.product.name,
|
||||
description:
|
||||
item.product.description ||
|
||||
`Расходники от ${updatedOrder.partner.name}`,
|
||||
description: isSellerSupply
|
||||
? `Расходники селлера ${
|
||||
updatedOrder.organization?.name ||
|
||||
updatedOrder.organization?.fullName
|
||||
}`
|
||||
: item.product.description ||
|
||||
`Расходники от ${updatedOrder.partner.name}`,
|
||||
price: item.price,
|
||||
quantity: item.quantity,
|
||||
currentStock: item.quantity,
|
||||
@ -6733,11 +7064,21 @@ export const resolvers = {
|
||||
updatedOrder.partner.name ||
|
||||
updatedOrder.partner.fullName ||
|
||||
"Поставщик",
|
||||
type: supplyType as
|
||||
| "SELLER_CONSUMABLES"
|
||||
| "FULFILLMENT_CONSUMABLES",
|
||||
sellerOwnerId: sellerOwnerId,
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
console.log(
|
||||
`➕ Создан новый расходник фулфилмента "${item.product.name}": ${item.quantity} единиц`
|
||||
`➕ Создан новый ${
|
||||
isSellerSupply ? "расходник селлера" : "расходник фулфилмента"
|
||||
} "${item.product.name}" ${
|
||||
isSellerSupply
|
||||
? `(владелец: ${updatedOrder.organization?.name})`
|
||||
: ""
|
||||
}: ${item.quantity} единиц`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6849,7 +7190,10 @@ export const resolvers = {
|
||||
// Иначе загружаем отдельно
|
||||
return await prisma.supply.findMany({
|
||||
where: { organizationId: parent.id },
|
||||
include: { organization: true },
|
||||
include: {
|
||||
organization: true,
|
||||
sellerOwner: true, // Включаем информацию о селлере-владельце
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
@ -6949,6 +7293,20 @@ export const resolvers = {
|
||||
},
|
||||
|
||||
Employee: {
|
||||
fullName: (parent: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
middleName?: string;
|
||||
}) => {
|
||||
const parts = [parent.lastName, parent.firstName];
|
||||
if (parent.middleName) {
|
||||
parts.push(parent.middleName);
|
||||
}
|
||||
return parts.join(" ");
|
||||
},
|
||||
name: (parent: { firstName: string; lastName: string }) => {
|
||||
return `${parent.firstName} ${parent.lastName}`;
|
||||
},
|
||||
birthDate: (parent: { birthDate?: Date | string | null }) => {
|
||||
if (!parent.birthDate) return null;
|
||||
if (parent.birthDate instanceof Date) {
|
||||
|
Reference in New Issue
Block a user