Обновлены модели и компоненты для управления поставками и расходниками. Добавлены новые поля в модели SupplyOrder и соответствующие резолверы для поддержки логистики. Реализованы компоненты уведомлений для отображения статуса логистических заявок и поставок. Оптимизирован интерфейс для улучшения пользовательского опыта, добавлены логи для диагностики запросов. Обновлены GraphQL схемы и мутации для поддержки новых функциональных возможностей.
This commit is contained in:
6
src/graphql/resolvers/auth.ts
Normal file
6
src/graphql/resolvers/auth.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Context } from "../context";
|
||||
|
||||
export const authResolvers = {
|
||||
Query: {},
|
||||
Mutation: {},
|
||||
};
|
6
src/graphql/resolvers/employees.ts
Normal file
6
src/graphql/resolvers/employees.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Context } from "../context";
|
||||
|
||||
export const employeeResolvers = {
|
||||
Query: {},
|
||||
Mutation: {},
|
||||
};
|
84
src/graphql/resolvers/index.ts
Normal file
84
src/graphql/resolvers/index.ts
Normal 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,
|
||||
}
|
||||
);
|
285
src/graphql/resolvers/logistics.ts
Normal file
285
src/graphql/resolvers/logistics.ts
Normal 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: "Ошибка при отклонении заказа",
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
6
src/graphql/resolvers/supplies.ts
Normal file
6
src/graphql/resolvers/supplies.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Context } from "../context";
|
||||
|
||||
export const suppliesResolvers = {
|
||||
Query: {},
|
||||
Mutation: {},
|
||||
};
|
Reference in New Issue
Block a user