Обновлены модели и компоненты для управления поставками и расходниками. Добавлены новые поля в модели SupplyOrder и соответствующие резолверы для поддержки логистики. Реализованы компоненты уведомлений для отображения статуса логистических заявок и поставок. Оптимизирован интерфейс для улучшения пользовательского опыта, добавлены логи для диагностики запросов. Обновлены GraphQL схемы и мутации для поддержки новых функциональных возможностей.

This commit is contained in:
Veronika Smirnova
2025-08-03 17:04:29 +03:00
parent a33adda9d7
commit 8407ca397c
34 changed files with 5382 additions and 1795 deletions

View File

@ -0,0 +1,6 @@
import { Context } from "../context";
export const authResolvers = {
Query: {},
Mutation: {},
};

View File

@ -0,0 +1,6 @@
import { Context } from "../context";
export const employeeResolvers = {
Query: {},
Mutation: {},
};

View File

@ -0,0 +1,84 @@
import { JSONScalar, DateTimeScalar } from "../scalars";
import { authResolvers } from "./auth";
import { employeeResolvers } from "./employees";
import { logisticsResolvers } from "./logistics";
import { suppliesResolvers } from "./supplies";
// Функция для объединения резолверов
const mergeResolvers = (...resolvers: any[]) => {
const result: any = {
Query: {},
Mutation: {},
};
for (const resolver of resolvers) {
if (resolver.Query) {
Object.assign(result.Query, resolver.Query);
}
if (resolver.Mutation) {
Object.assign(result.Mutation, resolver.Mutation);
}
// Объединяем другие типы резолверов (например, Employee, Organization и т.д.)
for (const [key, value] of Object.entries(resolver)) {
if (key !== "Query" && key !== "Mutation") {
if (!result[key]) {
result[key] = {};
}
Object.assign(result[key], value);
}
}
}
return result;
};
// Временно импортируем старые резолверы для частей, которые еще не вынесены
// TODO: Постепенно убрать это после полного рефакторинга
import { resolvers as oldResolvers } from "../resolvers";
// Объединяем новые модульные резолверы с остальными старыми
export const resolvers = mergeResolvers(
// Скалярные типы
{
JSON: JSONScalar,
DateTime: DateTimeScalar,
},
// Новые модульные резолверы
authResolvers,
employeeResolvers,
logisticsResolvers,
suppliesResolvers,
// Временно добавляем старые резолверы, исключая уже вынесенные
{
Query: {
...oldResolvers.Query,
// Исключаем уже вынесенные Query
myEmployees: undefined,
logisticsPartners: undefined,
pendingSuppliesCount: undefined,
},
Mutation: {
...oldResolvers.Mutation,
// Исключаем уже вынесенные Mutation
sendSmsCode: undefined,
verifySmsCode: undefined,
verifyInn: undefined,
registerFulfillmentOrganization: undefined,
createEmployee: undefined,
updateEmployee: undefined,
deleteEmployee: undefined,
assignLogisticsToSupply: undefined,
logisticsConfirmOrder: undefined,
logisticsRejectOrder: undefined,
},
// Остальные типы пока оставляем из старых резолверов
User: oldResolvers.User,
Organization: oldResolvers.Organization,
Product: oldResolvers.Product,
// SupplyOrder: oldResolvers.SupplyOrder, // Удалено: отсутствует в старых резолверах
// Employee берем из нового модуля
Employee: undefined,
}
);

View File

@ -0,0 +1,285 @@
import { GraphQLError } from "graphql";
import { Context } from "../context";
import { prisma } from "../../lib/prisma";
export const logisticsResolvers = {
Query: {
// Получить логистические компании-партнеры
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
});
},
},
Mutation: {
// Назначить логистику на поставку (используется фулфилментом)
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
: "Ошибка при назначении логистики",
};
}
},
// Подтвердить заказ логистической компанией
logisticsConfirmOrder: async (
_: unknown,
args: { id: 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("У пользователя нет организации");
}
try {
const existingOrder = await prisma.supplyOrder.findFirst({
where: {
id: args.id,
logisticsPartnerId: currentUser.organization.id,
OR: [{ status: "SUPPLIER_APPROVED" }, { status: "CONFIRMED" }],
},
});
if (!existingOrder) {
return {
success: false,
message:
"Заказ не найден или недоступен для подтверждения логистикой",
};
}
const updatedOrder = await prisma.supplyOrder.update({
where: { id: args.id },
data: { status: "LOGISTICS_CONFIRMED" },
include: {
partner: true,
organization: true,
fulfillmentCenter: true,
logisticsPartner: true,
items: {
include: {
product: {
include: {
category: true,
organization: true,
},
},
},
},
},
});
return {
success: true,
message: "Заказ подтвержден логистической компанией",
order: updatedOrder,
};
} catch (error) {
console.error("Error confirming supply order:", error);
return {
success: false,
message: "Ошибка при подтверждении заказа",
};
}
},
// Отклонить заказ логистической компанией
logisticsRejectOrder: async (
_: unknown,
args: { id: 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("У пользователя нет организации");
}
try {
const existingOrder = await prisma.supplyOrder.findFirst({
where: {
id: args.id,
logisticsPartnerId: currentUser.organization.id,
OR: [{ status: "SUPPLIER_APPROVED" }, { status: "CONFIRMED" }],
},
});
if (!existingOrder) {
return {
success: false,
message: "Заказ не найден или недоступен для отклонения логистикой",
};
}
const updatedOrder = await prisma.supplyOrder.update({
where: { id: args.id },
data: {
status: "CANCELLED",
logisticsPartnerId: null, // Убираем назначенную логистику
},
include: {
partner: true,
organization: true,
fulfillmentCenter: true,
logisticsPartner: true,
items: {
include: {
product: {
include: {
category: true,
organization: true,
},
},
},
},
},
});
return {
success: true,
message: "Заказ отклонен логистической компанией",
order: updatedOrder,
};
} catch (error) {
console.error("Error rejecting supply order:", error);
return {
success: false,
message: "Ошибка при отклонении заказа",
};
}
},
},
};

View File

@ -0,0 +1,6 @@
import { Context } from "../context";
export const suppliesResolvers = {
Query: {},
Mutation: {},
};