Удален резервный файл employees-dashboard.tsx и добавлены новые функции для проверки уникальности артикула в форме продукта. Обновлены мутации GraphQL для поддержки проверки уникальности артикула, а также добавлены уведомления о низких остатках на складе. Оптимизирован интерфейс для улучшения пользовательского опыта.
This commit is contained in:
@ -876,6 +876,69 @@ export const DELETE_PRODUCT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
// Мутация для проверки уникальности артикула
|
||||
export const CHECK_ARTICLE_UNIQUENESS = gql`
|
||||
mutation CheckArticleUniqueness($article: String!, $excludeId: ID) {
|
||||
checkArticleUniqueness(article: $article, excludeId: $excludeId) {
|
||||
isUnique
|
||||
existingProduct {
|
||||
id
|
||||
name
|
||||
article
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Мутация для резервирования товара (при заказе)
|
||||
export const RESERVE_PRODUCT_STOCK = gql`
|
||||
mutation ReserveProductStock($productId: ID!, $quantity: Int!) {
|
||||
reserveProductStock(productId: $productId, quantity: $quantity) {
|
||||
success
|
||||
message
|
||||
product {
|
||||
id
|
||||
quantity
|
||||
ordered
|
||||
stock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Мутация для освобождения резерва (при отмене заказа)
|
||||
export const RELEASE_PRODUCT_RESERVE = gql`
|
||||
mutation ReleaseProductReserve($productId: ID!, $quantity: Int!) {
|
||||
releaseProductReserve(productId: $productId, quantity: $quantity) {
|
||||
success
|
||||
message
|
||||
product {
|
||||
id
|
||||
quantity
|
||||
ordered
|
||||
stock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Мутация для обновления статуса "в пути"
|
||||
export const UPDATE_PRODUCT_IN_TRANSIT = gql`
|
||||
mutation UpdateProductInTransit($productId: ID!, $quantity: Int!, $operation: String!) {
|
||||
updateProductInTransit(productId: $productId, quantity: $quantity, operation: $operation) {
|
||||
success
|
||||
message
|
||||
product {
|
||||
id
|
||||
quantity
|
||||
ordered
|
||||
inTransit
|
||||
stock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Мутации для корзины
|
||||
export const ADD_TO_CART = gql`
|
||||
mutation AddToCart($productId: ID!, $quantity: Int = 1) {
|
||||
|
@ -4393,6 +4393,227 @@ export const resolvers = {
|
||||
}
|
||||
},
|
||||
|
||||
// Проверка уникальности артикула
|
||||
checkArticleUniqueness: async (
|
||||
_: unknown,
|
||||
args: { article: string; excludeId?: string },
|
||||
context: Context
|
||||
) => {
|
||||
const { currentUser, prisma } = context;
|
||||
|
||||
if (!currentUser?.organization?.id) {
|
||||
return {
|
||||
isUnique: false,
|
||||
existingProduct: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
article: args.article,
|
||||
organizationId: currentUser.organization.id,
|
||||
...(args.excludeId && { id: { not: args.excludeId } }),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
article: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
isUnique: !existingProduct,
|
||||
existingProduct,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error checking article uniqueness:", error);
|
||||
return {
|
||||
isUnique: false,
|
||||
existingProduct: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Резервирование товара при создании заказа
|
||||
reserveProductStock: async (
|
||||
_: unknown,
|
||||
args: { productId: string; quantity: number },
|
||||
context: Context
|
||||
) => {
|
||||
const { currentUser, prisma } = context;
|
||||
|
||||
if (!currentUser?.organization?.id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Необходимо авторизоваться",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: args.productId },
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Товар не найден",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем доступность товара
|
||||
const availableStock = (product.stock || product.quantity) - (product.ordered || 0);
|
||||
if (availableStock < args.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара на складе. Доступно: ${availableStock}, запрошено: ${args.quantity}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Резервируем товар (увеличиваем поле ordered)
|
||||
const updatedProduct = await prisma.product.update({
|
||||
where: { id: args.productId },
|
||||
data: {
|
||||
ordered: (product.ordered || 0) + args.quantity,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`📦 Зарезервировано ${args.quantity} единиц товара ${product.name}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Зарезервировано ${args.quantity} единиц товара`,
|
||||
product: updatedProduct,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error reserving product stock:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Ошибка при резервировании товара",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Освобождение резерва при отмене заказа
|
||||
releaseProductReserve: async (
|
||||
_: unknown,
|
||||
args: { productId: string; quantity: number },
|
||||
context: Context
|
||||
) => {
|
||||
const { currentUser, prisma } = context;
|
||||
|
||||
if (!currentUser?.organization?.id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Необходимо авторизоваться",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: args.productId },
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Товар не найден",
|
||||
};
|
||||
}
|
||||
|
||||
// Освобождаем резерв (уменьшаем поле ordered)
|
||||
const newOrdered = Math.max((product.ordered || 0) - args.quantity, 0);
|
||||
|
||||
const updatedProduct = await prisma.product.update({
|
||||
where: { id: args.productId },
|
||||
data: {
|
||||
ordered: newOrdered,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`🔄 Освобожден резерв ${args.quantity} единиц товара ${product.name}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Освобожден резерв ${args.quantity} единиц товара`,
|
||||
product: updatedProduct,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error releasing product reserve:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Ошибка при освобождении резерва",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновление статуса "в пути"
|
||||
updateProductInTransit: async (
|
||||
_: unknown,
|
||||
args: { productId: string; quantity: number; operation: string },
|
||||
context: Context
|
||||
) => {
|
||||
const { currentUser, prisma } = context;
|
||||
|
||||
if (!currentUser?.organization?.id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Необходимо авторизоваться",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: args.productId },
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Товар не найден",
|
||||
};
|
||||
}
|
||||
|
||||
let newInTransit = product.inTransit || 0;
|
||||
let newOrdered = product.ordered || 0;
|
||||
|
||||
if (args.operation === "ship") {
|
||||
// При отгрузке: переводим из "заказано" в "в пути"
|
||||
newInTransit = (product.inTransit || 0) + args.quantity;
|
||||
newOrdered = Math.max((product.ordered || 0) - args.quantity, 0);
|
||||
} else if (args.operation === "deliver") {
|
||||
// При доставке: убираем из "в пути", добавляем в "продано"
|
||||
newInTransit = Math.max((product.inTransit || 0) - args.quantity, 0);
|
||||
}
|
||||
|
||||
const updatedProduct = await prisma.product.update({
|
||||
where: { id: args.productId },
|
||||
data: {
|
||||
inTransit: newInTransit,
|
||||
ordered: newOrdered,
|
||||
...(args.operation === "deliver" && {
|
||||
sold: (product.sold || 0) + args.quantity,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`🚚 Обновлен статус "в пути" для товара ${product.name}: ${args.operation}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Статус товара обновлен: ${args.operation}`,
|
||||
product: updatedProduct,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating product in transit:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Ошибка при обновлении статуса товара",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить товар
|
||||
deleteProduct: async (
|
||||
_: unknown,
|
||||
@ -5608,6 +5829,24 @@ export const resolvers = {
|
||||
})),
|
||||
});
|
||||
|
||||
// 🔄 СИНХРОНИЗАЦИЯ: Обновляем товары поставщика (переводим из "в пути" в "продано")
|
||||
for (const item of existingOrder.items) {
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: item.product.id },
|
||||
});
|
||||
|
||||
if (product) {
|
||||
await prisma.product.update({
|
||||
where: { id: item.product.id },
|
||||
data: {
|
||||
inTransit: Math.max((product.inTransit || 0) - item.quantity, 0),
|
||||
sold: (product.sold || 0) + item.quantity,
|
||||
},
|
||||
});
|
||||
console.log(`✅ Товар поставщика "${product.name}" обновлен: доставлено ${item.quantity} единиц`);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем расходники
|
||||
for (const item of existingOrder.items) {
|
||||
console.log("📦 Обрабатываем товар:", {
|
||||
@ -5735,6 +5974,48 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Поставщик ${currentUser.organization.name} одобряет заказ ${args.id}`);
|
||||
|
||||
// 🔄 СИНХРОНИЗАЦИЯ ОСТАТКОВ: Резервируем товары у поставщика
|
||||
const orderWithItems = await prisma.supplyOrder.findUnique({
|
||||
where: { id: args.id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (orderWithItems) {
|
||||
for (const item of orderWithItems.items) {
|
||||
// Резервируем товар (увеличиваем поле ordered)
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: item.product.id },
|
||||
});
|
||||
|
||||
if (product) {
|
||||
const availableStock = (product.stock || product.quantity) - (product.ordered || 0);
|
||||
|
||||
if (availableStock < item.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара "${product.name}" на складе. Доступно: ${availableStock}, требуется: ${item.quantity}`,
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.product.update({
|
||||
where: { id: item.product.id },
|
||||
data: {
|
||||
ordered: (product.ordered || 0) + item.quantity,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`📦 Зарезервировано ${item.quantity} единиц товара "${product.name}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedOrder = await prisma.supplyOrder.update({
|
||||
where: { id: args.id },
|
||||
data: { status: "SUPPLIER_APPROVED" },
|
||||
@ -5759,7 +6040,7 @@ export const resolvers = {
|
||||
console.log(`[DEBUG] Заказ ${args.id} успешно обновлен до статуса: ${updatedOrder.status}`);
|
||||
return {
|
||||
success: true,
|
||||
message: "Заказ поставки одобрен поставщиком",
|
||||
message: "Заказ поставки одобрен поставщиком. Товары зарезервированы.",
|
||||
order: updatedOrder,
|
||||
};
|
||||
} catch (error) {
|
||||
@ -5880,6 +6161,38 @@ export const resolvers = {
|
||||
};
|
||||
}
|
||||
|
||||
// 🔄 СИНХРОНИЗАЦИЯ ОСТАТКОВ: Переводим товары из "заказано" в "в пути"
|
||||
const orderWithItems = await prisma.supplyOrder.findUnique({
|
||||
where: { id: args.id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (orderWithItems) {
|
||||
for (const item of orderWithItems.items) {
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: item.product.id },
|
||||
});
|
||||
|
||||
if (product) {
|
||||
await prisma.product.update({
|
||||
where: { id: item.product.id },
|
||||
data: {
|
||||
ordered: Math.max((product.ordered || 0) - item.quantity, 0),
|
||||
inTransit: (product.inTransit || 0) + item.quantity,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`🚚 Товар "${product.name}" переведен в статус "в пути": ${item.quantity} единиц`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedOrder = await prisma.supplyOrder.update({
|
||||
where: { id: args.id },
|
||||
data: { status: "SHIPPED" },
|
||||
@ -5903,7 +6216,7 @@ export const resolvers = {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Заказ отправлен поставщиком",
|
||||
message: "Заказ отправлен поставщиком. Товары переведены в статус 'в пути'.",
|
||||
order: updatedOrder,
|
||||
};
|
||||
} catch (error) {
|
||||
|
@ -228,6 +228,12 @@ export const typeDefs = gql`
|
||||
createProduct(input: ProductInput!): ProductResponse!
|
||||
updateProduct(id: ID!, input: ProductInput!): ProductResponse!
|
||||
deleteProduct(id: ID!): Boolean!
|
||||
|
||||
# Валидация и управление остатками товаров
|
||||
checkArticleUniqueness(article: String!, excludeId: ID): ArticleUniquenessResponse!
|
||||
reserveProductStock(productId: ID!, quantity: Int!): ProductStockResponse!
|
||||
releaseProductReserve(productId: ID!, quantity: Int!): ProductStockResponse!
|
||||
updateProductInTransit(productId: ID!, quantity: Int!, operation: String!): ProductStockResponse!
|
||||
|
||||
# Работа с категориями
|
||||
createCategory(input: CategoryInput!): CategoryResponse!
|
||||
@ -750,6 +756,17 @@ export const typeDefs = gql`
|
||||
product: Product
|
||||
}
|
||||
|
||||
type ArticleUniquenessResponse {
|
||||
isUnique: Boolean!
|
||||
existingProduct: Product
|
||||
}
|
||||
|
||||
type ProductStockResponse {
|
||||
success: Boolean!
|
||||
message: String!
|
||||
product: Product
|
||||
}
|
||||
|
||||
input CategoryInput {
|
||||
name: String!
|
||||
}
|
||||
|
Reference in New Issue
Block a user