import { GraphQLError } from 'graphql' import { Context } from '../../context' import { prisma } from '../../../lib/prisma' import { DomainResolvers } from '../shared/types' // Logistics Domain Resolvers - управление логистикой и маршрутами export const logisticsDomainResolvers: DomainResolvers = { Query: { // Логистика организации myLogistics: 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('У пользователя нет организации') } const organizationId = currentUser.organization.id console.warn('🚚 MY_LOGISTICS RESOLVER CALLED:', { userId: context.user.id, organizationId, organizationType: currentUser.organization.type, timestamp: new Date().toISOString(), }) // Получаем логистические маршруты организации const logistics = await prisma.logistics.findMany({ where: { organizationId }, include: { organization: { select: { id: true, name: true, fullName: true, type: true, }, }, }, orderBy: { createdAt: 'desc' }, }) console.warn('📊 MY_LOGISTICS RESULT:', { logisticsCount: logistics.length, organizationType: currentUser.organization.type, }) return logistics }, // Логистика конкретной организации (для партнеров) organizationLogistics: async (_: unknown, args: { organizationId: 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('У пользователя нет организации') } console.warn('🏢 ORGANIZATION_LOGISTICS RESOLVER CALLED:', { requestedOrganizationId: args.organizationId, currentOrganizationId: currentUser.organization.id, timestamp: new Date().toISOString(), }) // Проверяем права доступа - только партнеры или сама организация let hasAccess = false // Если запрашиваем свою логистику if (args.organizationId === currentUser.organization.id) { hasAccess = true } else { // Проверяем, есть ли партнерство const partnership = await prisma.counterparty.findFirst({ where: { organizationId: currentUser.organization.id, counterpartyId: args.organizationId, }, }) hasAccess = !!partnership } if (!hasAccess) { throw new GraphQLError('Нет доступа к логистике этой организации', { extensions: { code: 'FORBIDDEN' }, }) } // Получаем логистические маршруты организации const logistics = await prisma.logistics.findMany({ where: { organizationId: args.organizationId }, include: { organization: { select: { id: true, name: true, fullName: true, type: true, }, }, }, orderBy: { createdAt: 'desc' }, }) return logistics }, // Логистические партнеры (организации-логисты) logisticsPartners: 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('У пользователя нет организации') } console.warn('📦 LOGISTICS_PARTNERS RESOLVER CALLED:', { organizationId: currentUser.organization.id, organizationType: currentUser.organization.type, timestamp: new Date().toISOString(), }) // Получаем логистические компании среди контрагентов const logisticsPartners = await prisma.counterparty.findMany({ where: { organizationId: currentUser.organization.id, counterparty: { type: 'LOGIST', }, }, include: { counterparty: { include: { users: true, apiKeys: true, }, }, }, orderBy: { createdAt: 'desc', }, }) const partners = logisticsPartners.map((partnership) => partnership.counterparty) console.warn('📊 LOGISTICS_PARTNERS RESULT:', { partnersCount: partners.length, organizationType: currentUser.organization.type, }) return partners }, }, Mutation: { // Создать логистический маршрут createLogistics: async ( _: unknown, args: { input: { fromAddress: string toAddress: string fromCoordinates?: string toCoordinates?: string distance?: number estimatedTime?: number cost?: number vehicleType?: string maxWeight?: number maxVolume?: number notes?: string } }, context: Context, ) => { console.warn('🆕 CREATE_LOGISTICS - ВЫЗВАН:', { fromAddress: args.input.fromAddress, toAddress: args.input.toAddress, vehicleType: args.input.vehicleType, timestamp: new Date().toISOString(), }) 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 !== 'LOGIST') { throw new GraphQLError('Только логистические компании могут создавать маршруты') } try { const logistics = await prisma.logistics.create({ data: { organizationId: currentUser.organization.id, fromAddress: args.input.fromAddress, toAddress: args.input.toAddress, fromCoordinates: args.input.fromCoordinates, toCoordinates: args.input.toCoordinates, distance: args.input.distance, estimatedTime: args.input.estimatedTime, cost: args.input.cost, vehicleType: args.input.vehicleType, maxWeight: args.input.maxWeight, maxVolume: args.input.maxVolume, notes: args.input.notes, }, include: { organization: { select: { id: true, name: true, fullName: true, type: true, }, }, }, }) console.warn('✅ ЛОГИСТИЧЕСКИЙ МАРШРУТ СОЗДАН:', { logisticsId: logistics.id, organizationId: currentUser.organization.id, fromAddress: args.input.fromAddress, toAddress: args.input.toAddress, cost: args.input.cost, }) return { success: true, message: 'Логистический маршрут успешно создан', logistics, } } catch (error) { console.error('Error creating logistics:', error) return { success: false, message: 'Ошибка при создании логистического маршрута', logistics: null, } } }, // Обновить логистический маршрут updateLogistics: async ( _: unknown, args: { id: string input: { fromAddress?: string toAddress?: string fromCoordinates?: string toCoordinates?: string distance?: number estimatedTime?: number cost?: number vehicleType?: string maxWeight?: number maxVolume?: number notes?: string } }, context: Context, ) => { console.warn('📝 UPDATE_LOGISTICS - ВЫЗВАН:', { logisticsId: args.id, hasUpdates: Object.keys(args.input).length, timestamp: new Date().toISOString(), }) 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 existingLogistics = await prisma.logistics.findFirst({ where: { id: args.id, organizationId: currentUser.organization.id, }, }) if (!existingLogistics) { return { success: false, message: 'Логистический маршрут не найден или нет доступа', logistics: null, } } // Обновляем маршрут const updatedLogistics = await prisma.logistics.update({ where: { id: args.id }, data: { ...args.input, updatedAt: new Date(), }, include: { organization: { select: { id: true, name: true, fullName: true, type: true, }, }, }, }) console.warn('✅ ЛОГИСТИЧЕСКИЙ МАРШРУТ ОБНОВЛЕН:', { logisticsId: args.id, organizationId: currentUser.organization.id, updatedFields: Object.keys(args.input), }) return { success: true, message: 'Логистический маршрут успешно обновлен', logistics: updatedLogistics, } } catch (error) { console.error('Error updating logistics:', error) return { success: false, message: 'Ошибка при обновлении логистического маршрута', logistics: null, } } }, // Удалить логистический маршрут deleteLogistics: async (_: unknown, args: { id: string }, context: Context) => { console.warn('🗑️ DELETE_LOGISTICS - ВЫЗВАН:', { logisticsId: args.id, timestamp: new Date().toISOString(), }) 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 existingLogistics = await prisma.logistics.findFirst({ where: { id: args.id, organizationId: currentUser.organization.id, }, }) if (!existingLogistics) { return { success: false, message: 'Логистический маршрут не найден или нет доступа', } } // Проверяем, что маршрут не используется в активных заказах const activeSupplyOrders = await prisma.supplyOrder.findMany({ where: { logisticsPartnerId: currentUser.organization.id, status: { in: ['CONFIRMED', 'IN_TRANSIT'] }, }, }) if (activeSupplyOrders.length > 0) { return { success: false, message: 'Нельзя удалить маршрут, используемый в активных заказах', } } // Удаляем маршрут await prisma.logistics.delete({ where: { id: args.id }, }) console.warn('🗑️ ЛОГИСТИЧЕСКИЙ МАРШРУТ УДАЛЕН:', { logisticsId: args.id, organizationId: currentUser.organization.id, }) return { success: true, message: 'Логистический маршрут успешно удален', } } catch (error) { console.error('Error deleting logistics:', error) return { success: false, message: 'Ошибка при удалении логистического маршрута', } } }, // Назначить логистику к заказу (дублируется из supplies, но с другой логикой) assignLogisticsToSupply: async ( _: unknown, args: { supplyOrderId: string logisticsId: string estimatedDeliveryDate?: string specialInstructions?: string }, context: Context, ) => { console.warn('🚛 ASSIGN_LOGISTICS - ВЫЗВАН:', { supplyOrderId: args.supplyOrderId, logisticsId: args.logisticsId, timestamp: new Date().toISOString(), }) 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 { // Проверяем, что это логистическая компания if (currentUser.organization.type !== 'LOGIST') { throw new GraphQLError('Только логистические компании могут назначать свои маршруты') } // Проверяем заказ поставки const supplyOrder = await prisma.supplyOrder.findFirst({ where: { id: args.supplyOrderId, status: 'CONFIRMED', logisticsPartnerId: currentUser.organization.id, }, }) if (!supplyOrder) { return { success: false, message: 'Заказ поставки не найден или не назначен этой логистической компании', supplyOrder: null, } } // Проверяем логистический маршрут const logistics = await prisma.logistics.findFirst({ where: { id: args.logisticsId, organizationId: currentUser.organization.id, }, }) if (!logistics) { return { success: false, message: 'Логистический маршрут не найден', supplyOrder: null, } } // Назначаем маршрут и обновляем статус const updatedSupplyOrder = await prisma.supplyOrder.update({ where: { id: args.supplyOrderId }, data: { // TODO: добавить поле logisticsId когда будет в модели deliveryDate: args.estimatedDeliveryDate ? new Date(args.estimatedDeliveryDate) : supplyOrder.deliveryDate, status: 'IN_TRANSIT', notes: args.specialInstructions || supplyOrder.notes, updatedAt: new Date(), }, include: { organization: true, fulfillmentCenter: true, logisticsPartner: true, items: { include: { product: true, }, }, }, }) console.warn('✅ ЛОГИСТИКА НАЗНАЧЕНА НА ЗАКАЗ:', { supplyOrderId: args.supplyOrderId, logisticsId: args.logisticsId, logisticsCompany: currentUser.organization.name || currentUser.organization.fullName, route: `${logistics.fromAddress} → ${logistics.toAddress}`, }) return { success: true, message: 'Логистический маршрут назначен на заказ', supplyOrder: updatedSupplyOrder, } } catch (error) { console.error('Error assigning logistics:', error) return { success: false, message: 'Ошибка при назначении логистического маршрута', supplyOrder: null, } } }, }, }