Добавлены модели услуг и расходников для фулфилмент центров, реализованы соответствующие мутации и запросы в GraphQL. Обновлен конфигурационный файл и добавлен новый компонент Toaster в макет приложения. Обновлены зависимости в package.json и package-lock.json.

This commit is contained in:
Bivekich
2025-07-17 10:47:20 +03:00
parent 205c9eae98
commit 99e91287f3
22 changed files with 2148 additions and 2 deletions

View File

@ -546,4 +546,92 @@ export const MARK_MESSAGES_AS_READ = gql`
mutation MarkMessagesAsRead($conversationId: ID!) {
markMessagesAsRead(conversationId: $conversationId)
}
`
// Мутации для услуг
export const CREATE_SERVICE = gql`
mutation CreateService($input: ServiceInput!) {
createService(input: $input) {
success
message
service {
id
name
description
price
imageUrl
createdAt
updatedAt
}
}
}
`
export const UPDATE_SERVICE = gql`
mutation UpdateService($id: ID!, $input: ServiceInput!) {
updateService(id: $id, input: $input) {
success
message
service {
id
name
description
price
imageUrl
createdAt
updatedAt
}
}
}
`
export const DELETE_SERVICE = gql`
mutation DeleteService($id: ID!) {
deleteService(id: $id)
}
`
// Мутации для расходников
export const CREATE_SUPPLY = gql`
mutation CreateSupply($input: SupplyInput!) {
createSupply(input: $input) {
success
message
supply {
id
name
description
price
quantity
imageUrl
createdAt
updatedAt
}
}
}
`
export const UPDATE_SUPPLY = gql`
mutation UpdateSupply($id: ID!, $input: SupplyInput!) {
updateSupply(id: $id, input: $input) {
success
message
supply {
id
name
description
price
quantity
imageUrl
createdAt
updatedAt
}
}
}
`
export const DELETE_SUPPLY = gql`
mutation DeleteSupply($id: ID!) {
deleteSupply(id: $id)
}
`

View File

@ -48,6 +48,35 @@ export const GET_ME = gql`
}
`
export const GET_MY_SERVICES = gql`
query GetMyServices {
myServices {
id
name
description
price
imageUrl
createdAt
updatedAt
}
}
`
export const GET_MY_SUPPLIES = gql`
query GetMySupplies {
mySupplies {
id
name
description
price
quantity
imageUrl
createdAt
updatedAt
}
}
`
// Запросы для контрагентов
export const SEARCH_ORGANIZATIONS = gql`
query SearchOrganizations($type: OrganizationType, $search: String) {

View File

@ -423,6 +423,64 @@ export const resolvers = {
// TODO: Здесь будет логика получения списка чатов
// Пока возвращаем пустой массив, так как таблица сообщений еще не создана
return []
},
// Мои услуги
myServices: 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('У пользователя нет организации')
}
// Проверяем, что это фулфилмент центр
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Услуги доступны только для фулфилмент центров')
}
return await prisma.service.findMany({
where: { organizationId: currentUser.organization.id },
include: { organization: true },
orderBy: { createdAt: 'desc' }
})
},
// Мои расходники
mySupplies: 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('У пользователя нет организации')
}
// Проверяем, что это фулфилмент центр
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Расходники доступны только для фулфилмент центров')
}
return await prisma.supply.findMany({
where: { organizationId: currentUser.organization.id },
include: { organization: true },
orderBy: { createdAt: 'desc' }
})
}
},
@ -1746,6 +1804,296 @@ export const resolvers = {
// TODO: Здесь будет логика обновления статуса сообщений
// Пока возвращаем успешный ответ
return true
},
// Создать услугу
createService: async (_: unknown, args: { input: { name: string; description?: string; price: number; imageUrl?: 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('У пользователя нет организации')
}
// Проверяем, что это фулфилмент центр
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Услуги доступны только для фулфилмент центров')
}
try {
const service = await prisma.service.create({
data: {
name: args.input.name,
description: args.input.description,
price: args.input.price,
imageUrl: args.input.imageUrl,
organizationId: currentUser.organization.id
},
include: { organization: true }
})
return {
success: true,
message: 'Услуга успешно создана',
service
}
} catch (error) {
console.error('Error creating service:', error)
return {
success: false,
message: 'Ошибка при создании услуги'
}
}
},
// Обновить услугу
updateService: 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' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
// Проверяем, что услуга принадлежит текущей организации
const existingService = await prisma.service.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
if (!existingService) {
throw new GraphQLError('Услуга не найдена или нет доступа')
}
try {
const service = await prisma.service.update({
where: { id: args.id },
data: {
name: args.input.name,
description: args.input.description,
price: args.input.price,
imageUrl: args.input.imageUrl
},
include: { organization: true }
})
return {
success: true,
message: 'Услуга успешно обновлена',
service
}
} catch (error) {
console.error('Error updating service:', error)
return {
success: false,
message: 'Ошибка при обновлении услуги'
}
}
},
// Удалить услугу
deleteService: 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('У пользователя нет организации')
}
// Проверяем, что услуга принадлежит текущей организации
const existingService = await prisma.service.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
if (!existingService) {
throw new GraphQLError('Услуга не найдена или нет доступа')
}
try {
await prisma.service.delete({
where: { id: args.id }
})
return true
} catch (error) {
console.error('Error deleting service:', error)
return false
}
},
// Создать расходник
createSupply: async (_: unknown, args: { input: { name: string; description?: string; price: number; quantity: number; imageUrl?: 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('У пользователя нет организации')
}
// Проверяем, что это фулфилмент центр
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Расходники доступны только для фулфилмент центров')
}
try {
const supply = await prisma.supply.create({
data: {
name: args.input.name,
description: args.input.description,
price: args.input.price,
quantity: args.input.quantity,
imageUrl: args.input.imageUrl,
organizationId: currentUser.organization.id
},
include: { organization: true }
})
return {
success: true,
message: 'Расходник успешно создан',
supply
}
} catch (error) {
console.error('Error creating supply:', error)
return {
success: false,
message: 'Ошибка при создании расходника'
}
}
},
// Обновить расходник
updateSupply: async (_: unknown, args: { id: string; input: { name: string; description?: string; price: number; quantity: number; imageUrl?: 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('У пользователя нет организации')
}
// Проверяем, что расходник принадлежит текущей организации
const existingSupply = await prisma.supply.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
if (!existingSupply) {
throw new GraphQLError('Расходник не найден или нет доступа')
}
try {
const supply = await prisma.supply.update({
where: { id: args.id },
data: {
name: args.input.name,
description: args.input.description,
price: args.input.price,
quantity: args.input.quantity,
imageUrl: args.input.imageUrl
},
include: { organization: true }
})
return {
success: true,
message: 'Расходник успешно обновлен',
supply
}
} catch (error) {
console.error('Error updating supply:', error)
return {
success: false,
message: 'Ошибка при обновлении расходника'
}
}
},
// Удалить расходник
deleteSupply: 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('У пользователя нет организации')
}
// Проверяем, что расходник принадлежит текущей организации
const existingSupply = await prisma.supply.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
if (!existingSupply) {
throw new GraphQLError('Расходник не найден или нет доступа')
}
try {
await prisma.supply.delete({
where: { id: args.id }
})
return true
} catch (error) {
console.error('Error deleting supply:', error)
return false
}
}
},

View File

@ -22,6 +22,12 @@ export const typeDefs = gql`
# Список чатов (последние сообщения с каждым контрагентом)
conversations: [Conversation!]!
# Услуги организации
myServices: [Service!]!
# Расходники организации
mySupplies: [Supply!]!
}
type Mutation {
@ -61,6 +67,16 @@ export const typeDefs = gql`
sendImageMessage(receiverOrganizationId: ID!, fileUrl: String!, fileName: String!, fileSize: Int!, fileType: String!): MessageResponse!
sendFileMessage(receiverOrganizationId: ID!, fileUrl: String!, fileName: String!, fileSize: Int!, fileType: String!): MessageResponse!
markMessagesAsRead(conversationId: ID!): Boolean!
# Работа с услугами
createService(input: ServiceInput!): ServiceResponse!
updateService(id: ID!, input: ServiceInput!): ServiceResponse!
deleteService(id: ID!): Boolean!
# Работа с расходниками
createSupply(input: SupplyInput!): SupplyResponse!
updateSupply(id: ID!, input: SupplyInput!): SupplyResponse!
deleteSupply(id: ID!): Boolean!
}
# Типы данных
@ -281,6 +297,58 @@ export const typeDefs = gql`
messageData: Message
}
# Типы для услуг
type Service {
id: ID!
name: String!
description: String
price: Float!
imageUrl: String
createdAt: String!
updatedAt: String!
organization: Organization!
}
input ServiceInput {
name: String!
description: String
price: Float!
imageUrl: String
}
type ServiceResponse {
success: Boolean!
message: String!
service: Service
}
# Типы для расходников
type Supply {
id: ID!
name: String!
description: String
price: Float!
quantity: Int!
imageUrl: String
createdAt: String!
updatedAt: String!
organization: Organization!
}
input SupplyInput {
name: String!
description: String
price: Float!
quantity: Int!
imageUrl: String
}
type SupplyResponse {
success: Boolean!
message: String!
supply: Supply
}
# JSON скаляр
scalar JSON
`