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:
Veronika Smirnova
2025-09-12 15:55:24 +03:00
parent 13e33be260
commit 72118a3f66
25 changed files with 11330 additions and 0 deletions

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