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>
This commit is contained in:
571
src/graphql/resolvers/domains/logistics.ts
Normal file
571
src/graphql/resolvers/domains/logistics.ts
Normal file
@ -0,0 +1,571 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
Reference in New Issue
Block a user