feat: модульная архитектура sidebar и улучшения навигации
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
848
src/graphql/resolvers/goods-supply-v2.ts
Normal file
848
src/graphql/resolvers/goods-supply-v2.ts
Normal file
@ -0,0 +1,848 @@
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
import { Context } from '../context'
|
||||
|
||||
// ========== GOODS SUPPLY V2 RESOLVERS (ЗАКОММЕНТИРОВАНО) ==========
|
||||
// Раскомментируйте для активации системы товарных поставок V2
|
||||
|
||||
// ========== V2 RESOLVERS START ==========
|
||||
|
||||
export const goodsSupplyV2Resolvers = {
|
||||
Query: {
|
||||
// Товарные поставки селлера
|
||||
myGoodsSupplyOrdersV2: async (_: unknown, __: unknown, context: Context) => {
|
||||
const { user } = context
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'SELLER') {
|
||||
throw new GraphQLError('Доступно только для селлеров', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const orders = await prisma.goodsSupplyOrder.findMany({
|
||||
where: {
|
||||
sellerId: user.organizationId!,
|
||||
},
|
||||
include: {
|
||||
seller: true,
|
||||
fulfillmentCenter: {
|
||||
include: {
|
||||
phones: true,
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
sizes: true,
|
||||
},
|
||||
},
|
||||
recipe: {
|
||||
include: {
|
||||
components: {
|
||||
include: {
|
||||
material: true,
|
||||
},
|
||||
},
|
||||
services: {
|
||||
include: {
|
||||
service: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedServices: {
|
||||
include: {
|
||||
service: true,
|
||||
completedBy: true,
|
||||
},
|
||||
},
|
||||
logisticsPartner: {
|
||||
include: {
|
||||
phones: true,
|
||||
},
|
||||
},
|
||||
supplier: {
|
||||
include: {
|
||||
phones: true,
|
||||
},
|
||||
},
|
||||
receivedBy: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
})
|
||||
|
||||
return orders
|
||||
} catch (error) {
|
||||
throw new GraphQLError('Ошибка получения товарных поставок', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Входящие товарные поставки (для фулфилмента)
|
||||
incomingGoodsSuppliesV2: async (_: unknown, __: unknown, context: Context) => {
|
||||
const { user } = context
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент-центров', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const orders = await prisma.goodsSupplyOrder.findMany({
|
||||
where: {
|
||||
fulfillmentCenterId: user.organizationId!,
|
||||
},
|
||||
include: {
|
||||
seller: {
|
||||
include: {
|
||||
phones: true,
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
fulfillmentCenter: true,
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
},
|
||||
},
|
||||
recipe: {
|
||||
include: {
|
||||
components: {
|
||||
include: {
|
||||
material: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
unit: true,
|
||||
// НЕ показываем цены селлера
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
services: {
|
||||
include: {
|
||||
service: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedServices: {
|
||||
include: {
|
||||
service: true,
|
||||
completedBy: true,
|
||||
},
|
||||
},
|
||||
logisticsPartner: {
|
||||
include: {
|
||||
phones: true,
|
||||
},
|
||||
},
|
||||
supplier: true,
|
||||
receivedBy: true,
|
||||
},
|
||||
orderBy: {
|
||||
requestedDeliveryDate: 'asc',
|
||||
},
|
||||
})
|
||||
|
||||
// Фильтруем коммерческие данные селлера
|
||||
return orders.map(order => ({
|
||||
...order,
|
||||
items: order.items.map(item => ({
|
||||
...item,
|
||||
price: null, // Скрываем закупочную цену селлера
|
||||
totalPrice: null, // Скрываем общую стоимость
|
||||
})),
|
||||
}))
|
||||
} catch (error) {
|
||||
throw new GraphQLError('Ошибка получения входящих поставок', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Товарные заказы для поставщиков
|
||||
myGoodsSupplyRequestsV2: async (_: unknown, __: unknown, context: Context) => {
|
||||
const { user } = context
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'WHOLESALE') {
|
||||
throw new GraphQLError('Доступно только для поставщиков', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const orders = await prisma.goodsSupplyOrder.findMany({
|
||||
where: {
|
||||
supplierId: user.organizationId!,
|
||||
},
|
||||
include: {
|
||||
seller: {
|
||||
include: {
|
||||
phones: true,
|
||||
},
|
||||
},
|
||||
fulfillmentCenter: true,
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// НЕ включаем requestedServices - поставщик не видит услуги ФФ
|
||||
},
|
||||
orderBy: {
|
||||
requestedDeliveryDate: 'asc',
|
||||
},
|
||||
})
|
||||
|
||||
// Показываем только релевантную для поставщика информацию
|
||||
return orders.map(order => ({
|
||||
...order,
|
||||
items: order.items.map(item => ({
|
||||
...item,
|
||||
recipe: null, // Поставщик не видит рецептуры
|
||||
})),
|
||||
}))
|
||||
} catch (error) {
|
||||
throw new GraphQLError('Ошибка получения заказов поставок', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Детали конкретной поставки
|
||||
goodsSupplyOrderV2: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
const { user } = context
|
||||
|
||||
if (!user?.organizationId) {
|
||||
throw new GraphQLError('Необходима авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const order = await prisma.goodsSupplyOrder.findUnique({
|
||||
where: { id: args.id },
|
||||
include: {
|
||||
seller: {
|
||||
include: {
|
||||
phones: true,
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
fulfillmentCenter: {
|
||||
include: {
|
||||
phones: true,
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
sizes: true,
|
||||
},
|
||||
},
|
||||
recipe: {
|
||||
include: {
|
||||
components: {
|
||||
include: {
|
||||
material: true,
|
||||
},
|
||||
},
|
||||
services: {
|
||||
include: {
|
||||
service: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedServices: {
|
||||
include: {
|
||||
service: true,
|
||||
completedBy: true,
|
||||
},
|
||||
},
|
||||
logisticsPartner: {
|
||||
include: {
|
||||
phones: true,
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
supplier: {
|
||||
include: {
|
||||
phones: true,
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
receivedBy: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
throw new GraphQLError('Поставка не найдена', {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
|
||||
// Проверка прав доступа
|
||||
const hasAccess =
|
||||
order.sellerId === user.organizationId ||
|
||||
order.fulfillmentCenterId === user.organizationId ||
|
||||
order.supplierId === user.organizationId ||
|
||||
order.logisticsPartnerId === user.organizationId
|
||||
|
||||
if (!hasAccess) {
|
||||
throw new GraphQLError('Доступ запрещен', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
// Фильтрация данных в зависимости от роли
|
||||
if (user.organization?.type === 'WHOLESALE') {
|
||||
// Поставщик не видит рецептуры и услуги ФФ
|
||||
return {
|
||||
...order,
|
||||
items: order.items.map(item => ({
|
||||
...item,
|
||||
recipe: null,
|
||||
})),
|
||||
requestedServices: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (user.organization?.type === 'FULFILLMENT') {
|
||||
// ФФ не видит закупочные цены селлера
|
||||
return {
|
||||
...order,
|
||||
items: order.items.map(item => ({
|
||||
...item,
|
||||
price: null,
|
||||
totalPrice: null,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
if (user.organization?.type === 'LOGIST') {
|
||||
// Логистика видит только логистическую информацию
|
||||
return {
|
||||
...order,
|
||||
items: order.items.map(item => ({
|
||||
...item,
|
||||
price: null,
|
||||
totalPrice: null,
|
||||
recipe: null,
|
||||
})),
|
||||
requestedServices: [],
|
||||
}
|
||||
}
|
||||
|
||||
// Селлер видит все свои данные
|
||||
return order
|
||||
} catch (error) {
|
||||
if (error instanceof GraphQLError) {
|
||||
throw error
|
||||
}
|
||||
throw new GraphQLError('Ошибка получения поставки', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Рецептуры товаров селлера
|
||||
myProductRecipes: async (_: unknown, __: unknown, context: Context) => {
|
||||
const { user } = context
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'SELLER') {
|
||||
throw new GraphQLError('Доступно только для селлеров', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const recipes = await prisma.productRecipe.findMany({
|
||||
where: {
|
||||
product: {
|
||||
organizationId: user.organizationId!,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
include: {
|
||||
material: true,
|
||||
},
|
||||
},
|
||||
services: {
|
||||
include: {
|
||||
service: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'desc',
|
||||
},
|
||||
})
|
||||
|
||||
return recipes
|
||||
} catch (error) {
|
||||
throw new GraphQLError('Ошибка получения рецептур', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
// Создание товарной поставки
|
||||
createGoodsSupplyOrder: async (_: unknown, args: any, context: Context) => {
|
||||
const { user } = context
|
||||
const { input } = args
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'SELLER') {
|
||||
throw new GraphQLError('Доступно только для селлеров', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Проверяем фулфилмент-центр
|
||||
const fulfillmentCenter = await prisma.organization.findFirst({
|
||||
where: {
|
||||
id: input.fulfillmentCenterId,
|
||||
type: 'FULFILLMENT',
|
||||
},
|
||||
})
|
||||
|
||||
if (!fulfillmentCenter) {
|
||||
throw new GraphQLError('Фулфилмент-центр не найден', {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
|
||||
// Проверяем товары и рецептуры
|
||||
for (const item of input.items) {
|
||||
const product = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: item.productId,
|
||||
organizationId: user.organizationId!,
|
||||
},
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
throw new GraphQLError(`Товар ${item.productId} не найден`, {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
|
||||
if (item.recipeId) {
|
||||
const recipe = await prisma.productRecipe.findFirst({
|
||||
where: {
|
||||
id: item.recipeId,
|
||||
productId: item.productId,
|
||||
},
|
||||
})
|
||||
|
||||
if (!recipe) {
|
||||
throw new GraphQLError(`Рецептура ${item.recipeId} не найдена`, {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем поставку в транзакции
|
||||
const order = await prisma.$transaction(async (tx) => {
|
||||
// Создаем основную запись
|
||||
const newOrder = await tx.goodsSupplyOrder.create({
|
||||
data: {
|
||||
sellerId: user.organizationId!,
|
||||
fulfillmentCenterId: input.fulfillmentCenterId,
|
||||
requestedDeliveryDate: new Date(input.requestedDeliveryDate),
|
||||
notes: input.notes,
|
||||
status: 'PENDING',
|
||||
},
|
||||
})
|
||||
|
||||
// Создаем товары
|
||||
let totalAmount = 0
|
||||
let totalItems = 0
|
||||
|
||||
for (const itemInput of input.items) {
|
||||
const itemTotal = itemInput.price * itemInput.quantity
|
||||
totalAmount += itemTotal
|
||||
totalItems += itemInput.quantity
|
||||
|
||||
await tx.goodsSupplyOrderItem.create({
|
||||
data: {
|
||||
orderId: newOrder.id,
|
||||
productId: itemInput.productId,
|
||||
quantity: itemInput.quantity,
|
||||
price: itemInput.price,
|
||||
totalPrice: itemTotal,
|
||||
recipeId: itemInput.recipeId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Создаем запросы услуг
|
||||
for (const serviceInput of input.requestedServices) {
|
||||
const service = await tx.service.findUnique({
|
||||
where: { id: serviceInput.serviceId },
|
||||
})
|
||||
|
||||
if (!service) {
|
||||
throw new Error(`Услуга ${serviceInput.serviceId} не найдена`)
|
||||
}
|
||||
|
||||
const serviceTotal = service.price * serviceInput.quantity
|
||||
totalAmount += serviceTotal
|
||||
|
||||
await tx.fulfillmentServiceRequest.create({
|
||||
data: {
|
||||
orderId: newOrder.id,
|
||||
serviceId: serviceInput.serviceId,
|
||||
quantity: serviceInput.quantity,
|
||||
price: service.price,
|
||||
totalPrice: serviceTotal,
|
||||
status: 'PENDING',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Обновляем итоги
|
||||
await tx.goodsSupplyOrder.update({
|
||||
where: { id: newOrder.id },
|
||||
data: {
|
||||
totalAmount,
|
||||
totalItems,
|
||||
},
|
||||
})
|
||||
|
||||
return newOrder
|
||||
})
|
||||
|
||||
// Получаем созданную поставку с полными данными
|
||||
const createdOrder = await prisma.goodsSupplyOrder.findUnique({
|
||||
where: { id: order.id },
|
||||
include: {
|
||||
seller: true,
|
||||
fulfillmentCenter: true,
|
||||
items: {
|
||||
include: {
|
||||
product: true,
|
||||
recipe: true,
|
||||
},
|
||||
},
|
||||
requestedServices: {
|
||||
include: {
|
||||
service: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товарная поставка успешно создана',
|
||||
order: createdOrder,
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof GraphQLError) {
|
||||
throw error
|
||||
}
|
||||
throw new GraphQLError('Ошибка создания поставки', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Обновление статуса товарной поставки
|
||||
updateGoodsSupplyOrderStatus: async (_: unknown, args: any, context: Context) => {
|
||||
const { user } = context
|
||||
const { id, status, notes } = args
|
||||
|
||||
if (!user?.organizationId) {
|
||||
throw new GraphQLError('Необходима авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const order = await prisma.goodsSupplyOrder.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
throw new GraphQLError('Поставка не найдена', {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
|
||||
// Проверка прав на изменение статуса
|
||||
const canUpdate =
|
||||
(status === 'SUPPLIER_APPROVED' && order.supplierId === user.organizationId) ||
|
||||
(status === 'LOGISTICS_CONFIRMED' && user.organization?.type === 'FULFILLMENT') ||
|
||||
(status === 'SHIPPED' && order.supplierId === user.organizationId) ||
|
||||
(status === 'IN_TRANSIT' && order.logisticsPartnerId === user.organizationId) ||
|
||||
(status === 'RECEIVED' && order.fulfillmentCenterId === user.organizationId) ||
|
||||
(status === 'CANCELLED' &&
|
||||
(order.sellerId === user.organizationId || order.fulfillmentCenterId === user.organizationId))
|
||||
|
||||
if (!canUpdate) {
|
||||
throw new GraphQLError('Недостаточно прав для изменения статуса', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
const updateData: any = {
|
||||
status,
|
||||
notes: notes || order.notes,
|
||||
}
|
||||
|
||||
// Устанавливаем временные метки
|
||||
if (status === 'SUPPLIER_APPROVED') {
|
||||
updateData.supplierApprovedAt = new Date()
|
||||
} else if (status === 'SHIPPED') {
|
||||
updateData.shippedAt = new Date()
|
||||
} else if (status === 'RECEIVED') {
|
||||
updateData.receivedAt = new Date()
|
||||
updateData.receivedById = user.id
|
||||
}
|
||||
|
||||
const updatedOrder = await prisma.goodsSupplyOrder.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
include: {
|
||||
receivedBy: true,
|
||||
},
|
||||
})
|
||||
|
||||
return updatedOrder
|
||||
} catch (error) {
|
||||
if (error instanceof GraphQLError) {
|
||||
throw error
|
||||
}
|
||||
throw new GraphQLError('Ошибка обновления статуса', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Приемка товарной поставки
|
||||
receiveGoodsSupplyOrder: async (_: unknown, args: any, context: Context) => {
|
||||
const { user } = context
|
||||
const { id, items } = args
|
||||
|
||||
if (!user?.organization || user.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент-центров', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const order = await prisma.goodsSupplyOrder.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
recipe: {
|
||||
include: {
|
||||
components: {
|
||||
include: {
|
||||
material: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
throw new GraphQLError('Поставка не найдена', {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
|
||||
if (order.fulfillmentCenterId !== user.organizationId) {
|
||||
throw new GraphQLError('Доступ запрещен', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
if (order.status !== 'IN_TRANSIT') {
|
||||
throw new GraphQLError('Поставка должна быть в статусе "В пути"', {
|
||||
extensions: { code: 'BAD_REQUEST' },
|
||||
})
|
||||
}
|
||||
|
||||
// Обрабатываем приемку в транзакции
|
||||
const updatedOrder = await prisma.$transaction(async (tx) => {
|
||||
// Обновляем данные приемки для каждого товара
|
||||
for (const itemInput of items) {
|
||||
const orderItem = order.items.find(item => item.id === itemInput.itemId)
|
||||
if (!orderItem) {
|
||||
throw new Error(`Товар ${itemInput.itemId} не найден в поставке`)
|
||||
}
|
||||
|
||||
await tx.goodsSupplyOrderItem.update({
|
||||
where: { id: itemInput.itemId },
|
||||
data: {
|
||||
receivedQuantity: itemInput.receivedQuantity,
|
||||
damagedQuantity: itemInput.damagedQuantity || 0,
|
||||
acceptanceNotes: itemInput.acceptanceNotes,
|
||||
},
|
||||
})
|
||||
|
||||
// Обновляем остатки расходников по рецептуре
|
||||
if (orderItem.recipe && itemInput.receivedQuantity > 0) {
|
||||
for (const component of orderItem.recipe.components) {
|
||||
const usedQuantity = component.quantity * itemInput.receivedQuantity
|
||||
|
||||
await tx.supply.update({
|
||||
where: { id: component.materialId },
|
||||
data: {
|
||||
currentStock: {
|
||||
decrement: usedQuantity,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем статус поставки
|
||||
const updated = await tx.goodsSupplyOrder.update({
|
||||
where: { id },
|
||||
data: {
|
||||
status: 'RECEIVED',
|
||||
receivedAt: new Date(),
|
||||
receivedById: user.id,
|
||||
},
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: true,
|
||||
recipe: {
|
||||
include: {
|
||||
components: {
|
||||
include: {
|
||||
material: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedServices: {
|
||||
include: {
|
||||
service: true,
|
||||
},
|
||||
},
|
||||
receivedBy: true,
|
||||
},
|
||||
})
|
||||
|
||||
return updated
|
||||
})
|
||||
|
||||
return updatedOrder
|
||||
} catch (error) {
|
||||
if (error instanceof GraphQLError) {
|
||||
throw error
|
||||
}
|
||||
throw new GraphQLError('Ошибка приемки поставки', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Отмена товарной поставки
|
||||
cancelGoodsSupplyOrder: async (_: unknown, args: any, context: Context) => {
|
||||
const { user } = context
|
||||
const { id, reason } = args
|
||||
|
||||
if (!user?.organizationId) {
|
||||
throw new GraphQLError('Необходима авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' },
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const order = await prisma.goodsSupplyOrder.findUnique({
|
||||
where: { id },
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
throw new GraphQLError('Поставка не найдена', {
|
||||
extensions: { code: 'NOT_FOUND' },
|
||||
})
|
||||
}
|
||||
|
||||
// Проверка прав на отмену
|
||||
const canCancel =
|
||||
order.sellerId === user.organizationId ||
|
||||
order.fulfillmentCenterId === user.organizationId ||
|
||||
(order.supplierId === user.organizationId && order.status === 'PENDING')
|
||||
|
||||
if (!canCancel) {
|
||||
throw new GraphQLError('Недостаточно прав для отмены поставки', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
})
|
||||
}
|
||||
|
||||
if (['RECEIVED', 'PROCESSING', 'COMPLETED'].includes(order.status)) {
|
||||
throw new GraphQLError('Нельзя отменить поставку в текущем статусе', {
|
||||
extensions: { code: 'BAD_REQUEST' },
|
||||
})
|
||||
}
|
||||
|
||||
const cancelledOrder = await prisma.goodsSupplyOrder.update({
|
||||
where: { id },
|
||||
data: {
|
||||
status: 'CANCELLED',
|
||||
notes: `${order.notes ? order.notes + '\n' : ''}ОТМЕНЕНО: ${reason}`,
|
||||
},
|
||||
})
|
||||
|
||||
return cancelledOrder
|
||||
} catch (error) {
|
||||
if (error instanceof GraphQLError) {
|
||||
throw error
|
||||
}
|
||||
throw new GraphQLError('Ошибка отмены поставки', {
|
||||
extensions: { code: 'INTERNAL_ERROR', originalError: error },
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
Reference in New Issue
Block a user