Добавлен новый функционал для обработки заказов поставок расходников в компоненте CreateConsumablesSupplyPage. Реализован выбор фулфилмент-центра, улучшена логика создания заказа с учетом нового поля fulfillmentCenterId. Обновлен компонент SuppliesConsumablesTab для отображения заказов поставок с новыми данными. Оптимизированы стили и структура кода для повышения удобства использования.
This commit is contained in:
@ -753,4 +753,49 @@ export const ALL_USERS = gql`
|
||||
hasMore
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
export const GET_SUPPLY_ORDERS = gql`
|
||||
query GetSupplyOrders {
|
||||
supplyOrders {
|
||||
id
|
||||
deliveryDate
|
||||
status
|
||||
totalAmount
|
||||
totalItems
|
||||
createdAt
|
||||
updatedAt
|
||||
partner {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
inn
|
||||
address
|
||||
phones
|
||||
emails
|
||||
}
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
type
|
||||
}
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
price
|
||||
totalPrice
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@ -97,7 +97,7 @@ const generateToken = (payload: AuthTokenPayload): string => {
|
||||
const verifyToken = (token: string): AuthTokenPayload => {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (error) {
|
||||
throw new GraphQLError("Недействительный токен", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
@ -169,7 +169,7 @@ function parseLiteral(ast: unknown): unknown {
|
||||
fields?: unknown[];
|
||||
values?: unknown[];
|
||||
};
|
||||
|
||||
|
||||
switch (astNode.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
@ -291,7 +291,7 @@ export const resolvers = {
|
||||
|
||||
// Получаем исходящие заявки для добавления флага hasOutgoingRequest
|
||||
const outgoingRequests = await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
senderId: currentUser.organization.id,
|
||||
status: "PENDING",
|
||||
},
|
||||
@ -302,7 +302,7 @@ export const resolvers = {
|
||||
|
||||
// Получаем входящие заявки для добавления флага hasIncomingRequest
|
||||
const incomingRequests = await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
receiverId: currentUser.organization.id,
|
||||
status: "PENDING",
|
||||
},
|
||||
@ -366,7 +366,7 @@ export const resolvers = {
|
||||
|
||||
const counterparties = await prisma.counterparty.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
include: {
|
||||
counterparty: {
|
||||
include: {
|
||||
users: true,
|
||||
@ -397,7 +397,7 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
return await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
receiverId: currentUser.organization.id,
|
||||
status: "PENDING",
|
||||
},
|
||||
@ -437,7 +437,7 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
return await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
senderId: currentUser.organization.id,
|
||||
status: { in: ["PENDING", "REJECTED"] },
|
||||
},
|
||||
@ -506,7 +506,7 @@ export const resolvers = {
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "asc" },
|
||||
@ -537,7 +537,7 @@ export const resolvers = {
|
||||
// Получаем всех контрагентов
|
||||
const counterparties = await prisma.counterparty.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
include: {
|
||||
counterparty: {
|
||||
include: {
|
||||
users: true,
|
||||
@ -550,7 +550,7 @@ export const resolvers = {
|
||||
const conversations = await Promise.all(
|
||||
counterparties.map(async (cp) => {
|
||||
const counterpartyId = cp.counterparty.id;
|
||||
|
||||
|
||||
// Последнее сообщение с этим контрагентом
|
||||
const lastMessage = await prisma.message.findFirst({
|
||||
where: {
|
||||
@ -608,7 +608,10 @@ export const resolvers = {
|
||||
// Фильтруем null значения и сортируем по времени последнего сообщения
|
||||
return conversations
|
||||
.filter((conv) => conv !== null)
|
||||
.sort((a, b) => new Date(b!.updatedAt).getTime() - new Date(a!.updatedAt).getTime());
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b!.updatedAt).getTime() - new Date(a!.updatedAt).getTime()
|
||||
);
|
||||
},
|
||||
|
||||
// Мои услуги
|
||||
@ -664,6 +667,57 @@ export const resolvers = {
|
||||
});
|
||||
},
|
||||
|
||||
// Заказы поставок расходников
|
||||
supplyOrders: 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("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Возвращаем заказы где текущая организация является заказчиком или поставщиком
|
||||
return await prisma.supplyOrder.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ organizationId: currentUser.organization.id }, // Заказы созданные организацией
|
||||
{ partnerId: currentUser.organization.id }, // Заказы где организация - поставщик
|
||||
],
|
||||
},
|
||||
include: {
|
||||
partner: {
|
||||
include: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
organization: {
|
||||
include: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Логистика организации
|
||||
myLogistics: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
@ -743,7 +797,7 @@ export const resolvers = {
|
||||
|
||||
return await prisma.product.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true,
|
||||
},
|
||||
@ -785,7 +839,7 @@ export const resolvers = {
|
||||
|
||||
return await prisma.product.findMany({
|
||||
where,
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
@ -899,7 +953,9 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
if (!targetOrganization || targetOrganization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Расходники доступны только у фулфилмент центров");
|
||||
throw new GraphQLError(
|
||||
"Расходники доступны только у фулфилмент центров"
|
||||
);
|
||||
}
|
||||
|
||||
return await prisma.supply.findMany({
|
||||
@ -1068,7 +1124,7 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
const employee = await prisma.employee.findFirst({
|
||||
where: {
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
@ -1155,7 +1211,7 @@ export const resolvers = {
|
||||
args.phone,
|
||||
args.code
|
||||
);
|
||||
|
||||
|
||||
if (!verificationResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
@ -1214,7 +1270,7 @@ export const resolvers = {
|
||||
};
|
||||
|
||||
console.log("verifySmsCode - Returning result:", {
|
||||
success: result.success,
|
||||
success: result.success,
|
||||
hasToken: !!result.token,
|
||||
hasUser: !!result.user,
|
||||
message: result.message,
|
||||
@ -1318,28 +1374,28 @@ export const resolvers = {
|
||||
addressFull: organizationData.addressFull,
|
||||
ogrn: organizationData.ogrn,
|
||||
ogrnDate: organizationData.ogrnDate,
|
||||
|
||||
|
||||
// Статус организации
|
||||
status: organizationData.status,
|
||||
actualityDate: organizationData.actualityDate,
|
||||
registrationDate: organizationData.registrationDate,
|
||||
liquidationDate: organizationData.liquidationDate,
|
||||
|
||||
|
||||
// Руководитель
|
||||
managementName: organizationData.managementName,
|
||||
managementPost: organizationData.managementPost,
|
||||
|
||||
|
||||
// ОПФ
|
||||
opfCode: organizationData.opfCode,
|
||||
opfFull: organizationData.opfFull,
|
||||
opfShort: organizationData.opfShort,
|
||||
|
||||
|
||||
// Коды статистики
|
||||
okato: organizationData.okato,
|
||||
oktmo: organizationData.oktmo,
|
||||
okpo: organizationData.okpo,
|
||||
okved: organizationData.okved,
|
||||
|
||||
|
||||
// Контакты
|
||||
phones: organizationData.phones
|
||||
? JSON.parse(JSON.stringify(organizationData.phones))
|
||||
@ -1347,12 +1403,12 @@ export const resolvers = {
|
||||
emails: organizationData.emails
|
||||
? JSON.parse(JSON.stringify(organizationData.emails))
|
||||
: null,
|
||||
|
||||
|
||||
// Финансовые данные
|
||||
employeeCount: organizationData.employeeCount,
|
||||
revenue: organizationData.revenue,
|
||||
taxSystem: organizationData.taxSystem,
|
||||
|
||||
|
||||
type: type,
|
||||
dadataData: JSON.parse(JSON.stringify(organizationData.rawData)),
|
||||
},
|
||||
@ -1455,7 +1511,7 @@ export const resolvers = {
|
||||
const tradeMark = validationResults[0]?.data?.tradeMark;
|
||||
const sellerName = validationResults[0]?.data?.sellerName;
|
||||
const shopName = tradeMark || sellerName || "Магазин";
|
||||
|
||||
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
inn:
|
||||
@ -1598,7 +1654,7 @@ export const resolvers = {
|
||||
where: { id: existingKey.id },
|
||||
data: {
|
||||
apiKey,
|
||||
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
@ -1624,7 +1680,7 @@ export const resolvers = {
|
||||
message: "API ключ успешно добавлен",
|
||||
apiKey: newKey,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error adding marketplace API key:", error);
|
||||
return {
|
||||
@ -1697,7 +1753,7 @@ export const resolvers = {
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
@ -1712,7 +1768,7 @@ export const resolvers = {
|
||||
|
||||
try {
|
||||
const { input } = args;
|
||||
|
||||
|
||||
// Обновляем данные пользователя (аватар, имя управляющего)
|
||||
const userUpdateData: { avatar?: string; managerName?: string } = {};
|
||||
if (input.avatar) {
|
||||
@ -1721,14 +1777,14 @@ export const resolvers = {
|
||||
if (input.managerName) {
|
||||
userUpdateData.managerName = input.managerName;
|
||||
}
|
||||
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) {
|
||||
await prisma.user.update({
|
||||
where: { id: context.user.id },
|
||||
data: userUpdateData,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Подготавливаем данные для обновления организации
|
||||
const updateData: {
|
||||
phones?: object;
|
||||
@ -1736,20 +1792,20 @@ export const resolvers = {
|
||||
managementName?: string;
|
||||
managementPost?: string;
|
||||
} = {};
|
||||
|
||||
|
||||
// Название организации больше не обновляется через профиль
|
||||
// Для селлеров устанавливается при регистрации, для остальных - при смене ИНН
|
||||
|
||||
|
||||
// Обновляем контактные данные в JSON поле phones
|
||||
if (input.orgPhone) {
|
||||
updateData.phones = [{ value: input.orgPhone, type: "main" }];
|
||||
}
|
||||
|
||||
// Обновляем email в JSON поле emails
|
||||
|
||||
// Обновляем email в JSON поле emails
|
||||
if (input.email) {
|
||||
updateData.emails = [{ value: input.email, type: "main" }];
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем дополнительные контакты в custom полях
|
||||
// Пока добавим их как дополнительные JSON поля
|
||||
const customContacts: {
|
||||
@ -1763,13 +1819,13 @@ export const resolvers = {
|
||||
corrAccount?: string;
|
||||
};
|
||||
} = {};
|
||||
|
||||
|
||||
// managerName теперь сохраняется в поле пользователя, а не в JSON
|
||||
|
||||
|
||||
if (input.telegram) {
|
||||
customContacts.telegram = input.telegram;
|
||||
}
|
||||
|
||||
|
||||
if (input.whatsapp) {
|
||||
customContacts.whatsapp = input.whatsapp;
|
||||
}
|
||||
@ -1787,7 +1843,7 @@ export const resolvers = {
|
||||
corrAccount: input.corrAccount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Если есть дополнительные контакты, сохраним их в поле managementPost временно
|
||||
// В идеале нужно добавить отдельную таблицу для контактов
|
||||
if (Object.keys(customContacts).length > 0) {
|
||||
@ -1806,7 +1862,7 @@ export const resolvers = {
|
||||
// Получаем обновленного пользователя
|
||||
const updatedUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
@ -1842,7 +1898,7 @@ export const resolvers = {
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
@ -1940,7 +1996,7 @@ export const resolvers = {
|
||||
// Получаем обновленного пользователя
|
||||
const updatedUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
@ -2591,8 +2647,8 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
// conversationId имеет формат "currentOrgId-counterpartyId"
|
||||
const [, counterpartyId] = args.conversationId.split('-');
|
||||
|
||||
const [, counterpartyId] = args.conversationId.split("-");
|
||||
|
||||
if (!counterpartyId) {
|
||||
throw new GraphQLError("Неверный ID беседы");
|
||||
}
|
||||
@ -2989,13 +3045,22 @@ export const resolvers = {
|
||||
},
|
||||
|
||||
// Создать заказ поставки расходников
|
||||
// Процесс: Селлер → Поставщик → Логистика → Фулфилмент
|
||||
// 1. Селлер создает заказ у поставщика расходников
|
||||
// 2. Поставщик получает заказ и готовит товары
|
||||
// 3. Логистика транспортирует товары на склад фулфилмента
|
||||
// 4. Фулфилмент принимает товары на склад
|
||||
// 5. Все участники видят информацию о поставке в своих кабинетах
|
||||
createSupplyOrder: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
partnerId: string;
|
||||
deliveryDate: string;
|
||||
fulfillmentCenterId?: string; // ID фулфилмент-центра для доставки
|
||||
logisticsPartnerId?: string; // ID логистической компании
|
||||
items: Array<{ productId: string; quantity: number }>;
|
||||
notes?: string; // Дополнительные заметки к заказу
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
@ -3015,13 +3080,40 @@ export const resolvers = {
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что это фулфилмент центр
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
// Проверяем тип организации и определяем роль в процессе поставки
|
||||
const allowedTypes = ["FULFILLMENT", "SELLER", "LOGIST"];
|
||||
if (!allowedTypes.includes(currentUser.organization.type)) {
|
||||
throw new GraphQLError(
|
||||
"Заказы поставок доступны только для фулфилмент центров"
|
||||
"Заказы поставок недоступны для данного типа организации"
|
||||
);
|
||||
}
|
||||
|
||||
// Определяем роль организации в процессе поставки
|
||||
const organizationRole = currentUser.organization.type;
|
||||
let fulfillmentCenterId = args.input.fulfillmentCenterId;
|
||||
|
||||
// Если заказ создает фулфилмент-центр, он сам является получателем
|
||||
if (organizationRole === "FULFILLMENT") {
|
||||
fulfillmentCenterId = currentUser.organization.id;
|
||||
}
|
||||
|
||||
// Если указан фулфилмент-центр, проверяем его существование
|
||||
if (fulfillmentCenterId) {
|
||||
const fulfillmentCenter = await prisma.organization.findFirst({
|
||||
where: {
|
||||
id: fulfillmentCenterId,
|
||||
type: "FULFILLMENT",
|
||||
},
|
||||
});
|
||||
|
||||
if (!fulfillmentCenter) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Указанный фулфилмент-центр не найден",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, что партнер существует и является оптовиком
|
||||
const partner = await prisma.organization.findFirst({
|
||||
where: {
|
||||
@ -3104,6 +3196,16 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
try {
|
||||
// Определяем начальный статус в зависимости от роли организации
|
||||
let initialStatus = "PENDING";
|
||||
if (organizationRole === "SELLER") {
|
||||
initialStatus = "PENDING"; // Селлер создает заказ, ждет подтверждения поставщика
|
||||
} else if (organizationRole === "FULFILLMENT") {
|
||||
initialStatus = "PENDING"; // Фулфилмент заказывает для своего склада
|
||||
} else if (organizationRole === "LOGIST") {
|
||||
initialStatus = "CONFIRMED"; // Логист может сразу подтверждать заказы
|
||||
}
|
||||
|
||||
const supplyOrder = await prisma.supplyOrder.create({
|
||||
data: {
|
||||
partnerId: args.input.partnerId,
|
||||
@ -3111,6 +3213,7 @@ export const resolvers = {
|
||||
totalAmount: new Prisma.Decimal(totalAmount),
|
||||
totalItems: totalItems,
|
||||
organizationId: currentUser.organization.id,
|
||||
status: initialStatus as any,
|
||||
items: {
|
||||
create: orderItems,
|
||||
},
|
||||
@ -3145,7 +3248,7 @@ export const resolvers = {
|
||||
const productWithCategory = supplyOrder.items.find(
|
||||
(orderItem) => orderItem.productId === item.productId
|
||||
)?.product;
|
||||
|
||||
|
||||
return {
|
||||
name: product.name,
|
||||
description: product.description || `Заказано у ${partner.name}`,
|
||||
@ -3167,10 +3270,31 @@ export const resolvers = {
|
||||
data: suppliesData,
|
||||
});
|
||||
|
||||
// Формируем сообщение в зависимости от роли организации
|
||||
let successMessage = "";
|
||||
if (organizationRole === "SELLER") {
|
||||
successMessage = `Заказ поставки расходников создан! Расходники будут доставлены ${
|
||||
fulfillmentCenterId
|
||||
? "на указанный фулфилмент-склад"
|
||||
: "согласно настройкам"
|
||||
}. Ожидайте подтверждения от поставщика.`;
|
||||
} else if (organizationRole === "FULFILLMENT") {
|
||||
successMessage = `Заказ поставки расходников создан для вашего склада! Ожидайте подтверждения от поставщика и координации с логистикой.`;
|
||||
} else if (organizationRole === "LOGIST") {
|
||||
successMessage = `Заказ поставки создан и подтвержден! Координируйте доставку расходников от поставщика на фулфилмент-склад.`;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Заказ поставки создан успешно! Добавлено ${suppliesData.length} расходников в каталог.`,
|
||||
message: successMessage,
|
||||
order: supplyOrder,
|
||||
processInfo: {
|
||||
role: organizationRole,
|
||||
supplier: partner.name || partner.fullName,
|
||||
fulfillmentCenter: fulfillmentCenterId,
|
||||
logistics: args.input.logisticsPartnerId,
|
||||
status: initialStatus,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating supply order:", error);
|
||||
@ -3185,22 +3309,22 @@ export const resolvers = {
|
||||
createProduct: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
isActive?: boolean;
|
||||
input: {
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
isActive?: boolean;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
@ -3260,7 +3384,7 @@ export const resolvers = {
|
||||
isActive: args.input.isActive ?? true,
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true,
|
||||
},
|
||||
@ -3284,23 +3408,23 @@ export const resolvers = {
|
||||
updateProduct: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
id: string;
|
||||
input: {
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
isActive?: boolean;
|
||||
id: string;
|
||||
input: {
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
isActive?: boolean;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
@ -3366,11 +3490,13 @@ export const resolvers = {
|
||||
weight: args.input.weight,
|
||||
dimensions: args.input.dimensions,
|
||||
material: args.input.material,
|
||||
images: args.input.images ? JSON.stringify(args.input.images) : undefined,
|
||||
images: args.input.images
|
||||
? JSON.stringify(args.input.images)
|
||||
: undefined,
|
||||
mainImage: args.input.mainImage,
|
||||
isActive: args.input.isActive ?? true,
|
||||
},
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true,
|
||||
},
|
||||
@ -3655,7 +3781,7 @@ export const resolvers = {
|
||||
if (existingCartItem) {
|
||||
// Обновляем количество
|
||||
const newQuantity = existingCartItem.quantity + args.quantity;
|
||||
|
||||
|
||||
if (newQuantity > product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
@ -4195,7 +4321,7 @@ export const resolvers = {
|
||||
|
||||
try {
|
||||
const employee = await prisma.employee.update({
|
||||
where: {
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
@ -4257,7 +4383,7 @@ export const resolvers = {
|
||||
|
||||
try {
|
||||
await prisma.employee.delete({
|
||||
where: {
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
@ -4405,7 +4531,7 @@ export const resolvers = {
|
||||
if (parent.users) {
|
||||
return parent.users;
|
||||
}
|
||||
|
||||
|
||||
// Иначе загружаем отдельно
|
||||
return await prisma.user.findMany({
|
||||
where: { organizationId: parent.id },
|
||||
@ -4416,7 +4542,7 @@ export const resolvers = {
|
||||
if (parent.services) {
|
||||
return parent.services;
|
||||
}
|
||||
|
||||
|
||||
// Иначе загружаем отдельно
|
||||
return await prisma.service.findMany({
|
||||
where: { organizationId: parent.id },
|
||||
@ -4429,7 +4555,7 @@ export const resolvers = {
|
||||
if (parent.supplies) {
|
||||
return parent.supplies;
|
||||
}
|
||||
|
||||
|
||||
// Иначе загружаем отдельно
|
||||
return await prisma.supply.findMany({
|
||||
where: { organizationId: parent.id },
|
||||
@ -4478,7 +4604,7 @@ export const resolvers = {
|
||||
if (parent.organization) {
|
||||
return parent.organization;
|
||||
}
|
||||
|
||||
|
||||
// Иначе загружаем отдельно если есть organizationId
|
||||
if (parent.organizationId) {
|
||||
return await prisma.organization.findUnique({
|
||||
@ -4497,7 +4623,7 @@ export const resolvers = {
|
||||
Product: {
|
||||
images: (parent: { images: unknown }) => {
|
||||
// Если images это строка JSON, парсим её в массив
|
||||
if (typeof parent.images === 'string') {
|
||||
if (typeof parent.images === "string") {
|
||||
try {
|
||||
return JSON.parse(parent.images);
|
||||
} catch {
|
||||
@ -4814,14 +4940,14 @@ const adminQueries = {
|
||||
|
||||
const limit = args.limit || 50;
|
||||
const offset = args.offset || 0;
|
||||
|
||||
|
||||
// Строим условие поиска
|
||||
const whereCondition: Prisma.UserWhereInput = args.search
|
||||
? {
|
||||
OR: [
|
||||
{ phone: { contains: args.search, mode: "insensitive" } },
|
||||
{ managerName: { contains: args.search, mode: "insensitive" } },
|
||||
{
|
||||
{
|
||||
organization: {
|
||||
OR: [
|
||||
{ name: { contains: args.search, mode: "insensitive" } },
|
||||
@ -4889,7 +5015,7 @@ const adminMutations = {
|
||||
args.password,
|
||||
admin.password
|
||||
);
|
||||
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return {
|
||||
success: false,
|
||||
@ -4905,7 +5031,7 @@ const adminMutations = {
|
||||
|
||||
// Создать токен
|
||||
const token = jwt.sign(
|
||||
{
|
||||
{
|
||||
adminId: admin.id,
|
||||
username: admin.username,
|
||||
type: "admin",
|
||||
@ -4963,19 +5089,19 @@ const wildberriesQueries = {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'SELLER') {
|
||||
if (!user?.organization || user.organization.type !== "SELLER") {
|
||||
throw new GraphQLError("Доступно только для продавцов");
|
||||
}
|
||||
|
||||
const wbApiKeyRecord = user.organization.apiKeys?.find(
|
||||
key => key.marketplace === 'WILDBERRIES' && key.isActive
|
||||
(key) => key.marketplace === "WILDBERRIES" && key.isActive
|
||||
);
|
||||
|
||||
|
||||
if (!wbApiKeyRecord) {
|
||||
throw new GraphQLError("WB API ключ не настроен");
|
||||
}
|
||||
@ -4985,8 +5111,8 @@ const wildberriesQueries = {
|
||||
// Получаем кампании во всех статусах
|
||||
const [active, completed, paused] = await Promise.all([
|
||||
wbService.getAdverts(9).catch(() => []), // активные
|
||||
wbService.getAdverts(7).catch(() => []), // завершенные
|
||||
wbService.getAdverts(11).catch(() => []) // на паузе
|
||||
wbService.getAdverts(7).catch(() => []), // завершенные
|
||||
wbService.getAdverts(11).catch(() => []), // на паузе
|
||||
]);
|
||||
|
||||
const allCampaigns = [...active, ...completed, ...paused];
|
||||
@ -4995,30 +5121,34 @@ const wildberriesQueries = {
|
||||
success: true,
|
||||
message: `Found ${active.length} active, ${completed.length} completed, ${paused.length} paused campaigns`,
|
||||
campaignsCount: allCampaigns.length,
|
||||
campaigns: allCampaigns.map(c => ({
|
||||
campaigns: allCampaigns.map((c) => ({
|
||||
id: c.advertId,
|
||||
name: c.name,
|
||||
status: c.status,
|
||||
type: c.type
|
||||
}))
|
||||
type: c.type,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error debugging WB adverts:', error);
|
||||
console.error("Error debugging WB adverts:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
campaignsCount: 0,
|
||||
campaigns: []
|
||||
campaigns: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
getWildberriesStatistics: async (
|
||||
_: unknown,
|
||||
{ period, startDate, endDate }: {
|
||||
period?: 'week' | 'month' | 'quarter'
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
{
|
||||
period,
|
||||
startDate,
|
||||
endDate,
|
||||
}: {
|
||||
period?: "week" | "month" | "quarter";
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
@ -5036,7 +5166,7 @@ const wildberriesQueries = {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -5045,14 +5175,14 @@ const wildberriesQueries = {
|
||||
throw new GraphQLError("Организация не найдена");
|
||||
}
|
||||
|
||||
if (user.organization.type !== 'SELLER') {
|
||||
if (user.organization.type !== "SELLER") {
|
||||
throw new GraphQLError("Доступно только для продавцов");
|
||||
}
|
||||
|
||||
const wbApiKeyRecord = user.organization.apiKeys?.find(
|
||||
key => key.marketplace === 'WILDBERRIES' && key.isActive
|
||||
(key) => key.marketplace === "WILDBERRIES" && key.isActive
|
||||
);
|
||||
|
||||
|
||||
if (!wbApiKeyRecord) {
|
||||
throw new GraphQLError("WB API ключ не настроен");
|
||||
}
|
||||
@ -5073,7 +5203,9 @@ const wildberriesQueries = {
|
||||
dateFrom = WildberriesService.getDatePeriodAgo(period);
|
||||
dateTo = WildberriesService.formatDate(new Date());
|
||||
} else {
|
||||
throw new GraphQLError("Необходимо указать либо period, либо startDate и endDate");
|
||||
throw new GraphQLError(
|
||||
"Необходимо указать либо period, либо startDate и endDate"
|
||||
);
|
||||
}
|
||||
|
||||
// Получаем статистику
|
||||
@ -5082,13 +5214,16 @@ const wildberriesQueries = {
|
||||
return {
|
||||
success: true,
|
||||
data: statistics,
|
||||
message: null
|
||||
message: null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching WB statistics:', error);
|
||||
console.error("Error fetching WB statistics:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Ошибка получения статистики',
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Ошибка получения статистики",
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
@ -5096,17 +5231,19 @@ const wildberriesQueries = {
|
||||
|
||||
getWildberriesCampaignStats: async (
|
||||
_: unknown,
|
||||
{ input }: {
|
||||
{
|
||||
input,
|
||||
}: {
|
||||
input: {
|
||||
campaigns: Array<{
|
||||
id: number
|
||||
dates?: string[]
|
||||
id: number;
|
||||
dates?: string[];
|
||||
interval?: {
|
||||
begin: string
|
||||
end: string
|
||||
}
|
||||
}>
|
||||
}
|
||||
begin: string;
|
||||
end: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
@ -5124,7 +5261,7 @@ const wildberriesQueries = {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -5133,14 +5270,14 @@ const wildberriesQueries = {
|
||||
throw new GraphQLError("Организация не найдена");
|
||||
}
|
||||
|
||||
if (user.organization.type !== 'SELLER') {
|
||||
if (user.organization.type !== "SELLER") {
|
||||
throw new GraphQLError("Доступно только для продавцов");
|
||||
}
|
||||
|
||||
const wbApiKeyRecord = user.organization.apiKeys?.find(
|
||||
key => key.marketplace === 'WILDBERRIES' && key.isActive
|
||||
(key) => key.marketplace === "WILDBERRIES" && key.isActive
|
||||
);
|
||||
|
||||
|
||||
if (!wbApiKeyRecord) {
|
||||
throw new GraphQLError("WB API ключ не настроен");
|
||||
}
|
||||
@ -5149,21 +5286,21 @@ const wildberriesQueries = {
|
||||
const wbService = new WildberriesService(wbApiKeyRecord.apiKey);
|
||||
|
||||
// Преобразуем запросы в нужный формат
|
||||
const requests = input.campaigns.map(campaign => {
|
||||
const requests = input.campaigns.map((campaign) => {
|
||||
if (campaign.dates && campaign.dates.length > 0) {
|
||||
return {
|
||||
id: campaign.id,
|
||||
dates: campaign.dates
|
||||
dates: campaign.dates,
|
||||
};
|
||||
} else if (campaign.interval) {
|
||||
return {
|
||||
id: campaign.id,
|
||||
interval: campaign.interval
|
||||
interval: campaign.interval,
|
||||
};
|
||||
} else {
|
||||
// Если не указаны ни даты, ни интервал, возвращаем данные только за последние сутки
|
||||
return {
|
||||
id: campaign.id
|
||||
id: campaign.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
@ -5174,13 +5311,16 @@ const wildberriesQueries = {
|
||||
return {
|
||||
success: true,
|
||||
data: campaignStats,
|
||||
message: null
|
||||
message: null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching WB campaign stats:', error);
|
||||
console.error("Error fetching WB campaign stats:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Ошибка получения статистики кампаний',
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Ошибка получения статистики кампаний",
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
@ -34,12 +34,15 @@ export const typeDefs = gql`
|
||||
# Расходники организации
|
||||
mySupplies: [Supply!]!
|
||||
|
||||
# Заказы поставок расходников
|
||||
supplyOrders: [SupplyOrder!]!
|
||||
|
||||
# Логистика организации
|
||||
myLogistics: [Logistics!]!
|
||||
|
||||
|
||||
# Поставки Wildberries
|
||||
myWildberriesSupplies: [WildberriesSupply!]!
|
||||
|
||||
|
||||
# Товары оптовика
|
||||
myProducts: [Product!]!
|
||||
|
||||
@ -68,7 +71,7 @@ export const typeDefs = gql`
|
||||
|
||||
# Публичные услуги контрагента (для фулфилмента)
|
||||
counterpartyServices(organizationId: ID!): [Service!]!
|
||||
|
||||
|
||||
# Публичные расходники контрагента (для оптовиков)
|
||||
counterpartySupplies(organizationId: ID!): [Supply!]!
|
||||
|
||||
@ -82,10 +85,10 @@ export const typeDefs = gql`
|
||||
startDate: String
|
||||
endDate: String
|
||||
): WildberriesStatisticsResponse!
|
||||
|
||||
|
||||
# Отладка рекламы (временно)
|
||||
debugWildberriesAdverts: DebugAdvertsResponse!
|
||||
|
||||
|
||||
# Статистика кампаний Wildberries
|
||||
getWildberriesCampaignStats(
|
||||
input: WildberriesCampaignStatsInput!
|
||||
@ -203,12 +206,17 @@ export const typeDefs = gql`
|
||||
updateEmployee(id: ID!, input: UpdateEmployeeInput!): EmployeeResponse!
|
||||
deleteEmployee(id: ID!): Boolean!
|
||||
updateEmployeeSchedule(input: UpdateScheduleInput!): Boolean!
|
||||
|
||||
|
||||
# Работа с поставками Wildberries
|
||||
createWildberriesSupply(input: CreateWildberriesSupplyInput!): WildberriesSupplyResponse!
|
||||
updateWildberriesSupply(id: ID!, input: UpdateWildberriesSupplyInput!): WildberriesSupplyResponse!
|
||||
createWildberriesSupply(
|
||||
input: CreateWildberriesSupplyInput!
|
||||
): WildberriesSupplyResponse!
|
||||
updateWildberriesSupply(
|
||||
id: ID!
|
||||
input: UpdateWildberriesSupplyInput!
|
||||
): WildberriesSupplyResponse!
|
||||
deleteWildberriesSupply(id: ID!): Boolean!
|
||||
|
||||
|
||||
# Админ мутации
|
||||
adminLogin(username: String!, password: String!): AdminAuthResponse!
|
||||
adminLogout: Boolean!
|
||||
@ -537,7 +545,10 @@ export const typeDefs = gql`
|
||||
input SupplyOrderInput {
|
||||
partnerId: ID!
|
||||
deliveryDate: DateTime!
|
||||
fulfillmentCenterId: ID # ID фулфилмент-центра для доставки
|
||||
logisticsPartnerId: ID # ID логистической компании
|
||||
items: [SupplyOrderItemInput!]!
|
||||
notes: String # Дополнительные заметки к заказу
|
||||
}
|
||||
|
||||
input SupplyOrderItemInput {
|
||||
@ -545,10 +556,19 @@ export const typeDefs = gql`
|
||||
quantity: Int!
|
||||
}
|
||||
|
||||
type SupplyOrderProcessInfo {
|
||||
role: String! # Роль организации в процессе (SELLER, FULFILLMENT, LOGIST)
|
||||
supplier: String! # Название поставщика
|
||||
fulfillmentCenter: ID # ID фулфилмент-центра
|
||||
logistics: ID # ID логистической компании
|
||||
status: String! # Текущий статус заказа
|
||||
}
|
||||
|
||||
type SupplyOrderResponse {
|
||||
success: Boolean!
|
||||
message: String!
|
||||
order: SupplyOrder
|
||||
processInfo: SupplyOrderProcessInfo # Информация о процессе поставки
|
||||
}
|
||||
|
||||
# Типы для логистики
|
||||
|
Reference in New Issue
Block a user