Добавлены модели и функциональность для управления логистикой, включая создание, обновление и удаление логистических маршрутов через GraphQL. Обновлены компоненты для отображения и управления логистикой, улучшен интерфейс взаимодействия с пользователем. Реализованы новые типы данных и интерфейсы для логистики, а также улучшена обработка ошибок.

This commit is contained in:
Bivekich
2025-07-18 15:40:12 +03:00
parent 7e7e4a9b4a
commit 93bb5827d2
20 changed files with 5015 additions and 667 deletions

View File

@ -602,7 +602,6 @@ export const CREATE_SUPPLY = gql`
name
description
price
quantity
imageUrl
createdAt
updatedAt
@ -621,7 +620,6 @@ export const UPDATE_SUPPLY = gql`
name
description
price
quantity
imageUrl
createdAt
updatedAt
@ -636,6 +634,51 @@ export const DELETE_SUPPLY = gql`
}
`
// Мутации для логистики
export const CREATE_LOGISTICS = gql`
mutation CreateLogistics($input: LogisticsInput!) {
createLogistics(input: $input) {
success
message
logistics {
id
fromLocation
toLocation
priceUnder1m3
priceOver1m3
description
createdAt
updatedAt
}
}
}
`
export const UPDATE_LOGISTICS = gql`
mutation UpdateLogistics($id: ID!, $input: LogisticsInput!) {
updateLogistics(id: $id, input: $input) {
success
message
logistics {
id
fromLocation
toLocation
priceUnder1m3
priceOver1m3
description
createdAt
updatedAt
}
}
}
`
export const DELETE_LOGISTICS = gql`
mutation DeleteLogistics($id: ID!) {
deleteLogistics(id: $id)
}
`
// Мутации для товаров оптовика
export const CREATE_PRODUCT = gql`
mutation CreateProduct($input: ProductInput!) {

View File

@ -69,7 +69,6 @@ export const GET_MY_SUPPLIES = gql`
name
description
price
quantity
imageUrl
createdAt
updatedAt
@ -77,6 +76,21 @@ export const GET_MY_SUPPLIES = gql`
}
`
export const GET_MY_LOGISTICS = gql`
query GetMyLogistics {
myLogistics {
id
fromLocation
toLocation
priceUnder1m3
priceOver1m3
description
createdAt
updatedAt
}
}
`
export const GET_MY_PRODUCTS = gql`
query GetMyProducts {
myProducts {

View File

@ -530,12 +530,31 @@ export const resolvers = {
throw new GraphQLError('У пользователя нет организации')
}
// Проверяем, что это фулфилмент центр
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Расходники доступны только для фулфилмент центров')
return await prisma.supply.findMany({
where: { organizationId: currentUser.organization.id },
include: { organization: true },
orderBy: { createdAt: 'desc' }
})
},
// Логистика организации
myLogistics: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
return await prisma.supply.findMany({
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
return await prisma.logistics.findMany({
where: { organizationId: currentUser.organization.id },
include: { organization: true },
orderBy: { createdAt: 'desc' }
@ -2322,7 +2341,7 @@ export const resolvers = {
},
// Создать расходник
createSupply: async (_: unknown, args: { input: { name: string; description?: string; price: number; quantity: number; imageUrl?: string } }, context: Context) => {
createSupply: async (_: unknown, args: { input: { name: string; description?: string; price: number; imageUrl?: string } }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
@ -2349,7 +2368,7 @@ export const resolvers = {
name: args.input.name,
description: args.input.description,
price: args.input.price,
quantity: args.input.quantity,
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
imageUrl: args.input.imageUrl,
organizationId: currentUser.organization.id
},
@ -2371,7 +2390,7 @@ export const resolvers = {
},
// Обновить расходник
updateSupply: async (_: unknown, args: { id: string; input: { name: string; description?: string; price: number; quantity: number; imageUrl?: string } }, context: Context) => {
updateSupply: async (_: unknown, args: { id: string; input: { name: string; description?: string; price: number; imageUrl?: string } }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
@ -2406,7 +2425,7 @@ export const resolvers = {
name: args.input.name,
description: args.input.description,
price: args.input.price,
quantity: args.input.quantity,
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
imageUrl: args.input.imageUrl
},
include: { organization: true }
@ -3558,4 +3577,163 @@ export const resolvers = {
})
}
}
}
// Логистические мутации
const logisticsMutations = {
// Создать логистический маршрут
createLogistics: async (_: unknown, args: { input: { fromLocation: string; toLocation: string; priceUnder1m3: number; priceOver1m3: number; description?: 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('У пользователя нет организации')
}
try {
const logistics = await prisma.logistics.create({
data: {
fromLocation: args.input.fromLocation,
toLocation: args.input.toLocation,
priceUnder1m3: args.input.priceUnder1m3,
priceOver1m3: args.input.priceOver1m3,
description: args.input.description,
organizationId: currentUser.organization.id
},
include: {
organization: true
}
})
console.log('✅ Logistics created:', logistics.id)
return {
success: true,
message: 'Логистический маршрут создан',
logistics
}
} catch (error) {
console.error('❌ Error creating logistics:', error)
return {
success: false,
message: 'Ошибка при создании логистического маршрута'
}
}
},
// Обновить логистический маршрут
updateLogistics: async (_: unknown, args: { id: string; input: { fromLocation: string; toLocation: string; priceUnder1m3: number; priceOver1m3: number; description?: 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('У пользователя нет организации')
}
try {
// Проверяем, что маршрут принадлежит организации пользователя
const existingLogistics = await prisma.logistics.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
if (!existingLogistics) {
throw new GraphQLError('Логистический маршрут не найден')
}
const logistics = await prisma.logistics.update({
where: { id: args.id },
data: {
fromLocation: args.input.fromLocation,
toLocation: args.input.toLocation,
priceUnder1m3: args.input.priceUnder1m3,
priceOver1m3: args.input.priceOver1m3,
description: args.input.description
},
include: {
organization: true
}
})
console.log('✅ Logistics updated:', logistics.id)
return {
success: true,
message: 'Логистический маршрут обновлен',
logistics
}
} catch (error) {
console.error('❌ Error updating logistics:', error)
return {
success: false,
message: 'Ошибка при обновлении логистического маршрута'
}
}
},
// Удалить логистический маршрут
deleteLogistics: async (_: unknown, args: { id: 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('У пользователя нет организации')
}
try {
// Проверяем, что маршрут принадлежит организации пользователя
const existingLogistics = await prisma.logistics.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
if (!existingLogistics) {
throw new GraphQLError('Логистический маршрут не найден')
}
await prisma.logistics.delete({
where: { id: args.id }
})
console.log('✅ Logistics deleted:', args.id)
return true
} catch (error) {
console.error('❌ Error deleting logistics:', error)
return false
}
}
}
// Добавляем логистические мутации к основным резолверам
resolvers.Mutation = {
...resolvers.Mutation,
...logisticsMutations
}

View File

@ -29,6 +29,9 @@ export const typeDefs = gql`
# Расходники организации
mySupplies: [Supply!]!
# Логистика организации
myLogistics: [Logistics!]!
# Товары оптовика
myProducts: [Product!]!
@ -100,6 +103,11 @@ export const typeDefs = gql`
updateSupply(id: ID!, input: SupplyInput!): SupplyResponse!
deleteSupply(id: ID!): Boolean!
# Работа с логистикой
createLogistics(input: LogisticsInput!): LogisticsResponse!
updateLogistics(id: ID!, input: LogisticsInput!): LogisticsResponse!
deleteLogistics(id: ID!): Boolean!
# Работа с товарами (для оптовиков)
createProduct(input: ProductInput!): ProductResponse!
updateProduct(id: ID!, input: ProductInput!): ProductResponse!
@ -372,7 +380,6 @@ export const typeDefs = gql`
name: String!
description: String
price: Float!
quantity: Int!
imageUrl: String
createdAt: String!
updatedAt: String!
@ -383,7 +390,6 @@ export const typeDefs = gql`
name: String!
description: String
price: Float!
quantity: Int!
imageUrl: String
}
@ -393,6 +399,33 @@ export const typeDefs = gql`
supply: Supply
}
# Типы для логистики
type Logistics {
id: ID!
fromLocation: String!
toLocation: String!
priceUnder1m3: Float!
priceOver1m3: Float!
description: String
createdAt: String!
updatedAt: String!
organization: Organization!
}
input LogisticsInput {
fromLocation: String!
toLocation: String!
priceUnder1m3: Float!
priceOver1m3: Float!
description: String
}
type LogisticsResponse {
success: Boolean!
message: String!
logistics: Logistics
}
# Типы для категорий товаров
type Category {
id: ID!