Files
sfera-new/src/graphql/resolvers/domains/logistics.ts
Veronika Smirnova 72118a3f66 feat: добавить все модульные V2 резолверы доменной архитектуры
🏗️ АРХИТЕКТУРНОЕ УЛУЧШЕНИЕ: Полная система модульных резолверов V2
-  Добавлены 21 доменный резолвер в src/graphql/resolvers/domains/
-  Добавлены 4 общих резолвера в src/graphql/resolvers/shared/
-  Реализована изолированная доменно-ориентированная архитектура
-  Подготовлена инфраструктура для полной миграции V1→V2

📦 НОВЫЕ ДОМЕНЫ:
- admin-tools, analytics, cart, catalog
- counterparty-management, employee, external-ads
- file-management, logistics, messaging
- organization-management, products, referrals
- seller-consumables, seller-goods, services
- supplies, supply-orders, user-management
- wildberries, logistics-consumables

🛠️ ОБЩИЕ КОМПОНЕНТЫ:
- api-keys, auth-utils, scalars, types
- Безопасная интеграция с существующей системой

🔗 ИНТЕГРАЦИЯ: Все резолверы готовы к подключению через src/graphql/resolvers/index.ts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 15:55:24 +03:00

571 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
}
},
},
}