Добавлены новые зависимости для работы с эмодзи и улучшена структура базы данных. Реализована модель сообщений и обновлены компоненты для поддержки новых функций мессенджера. Обновлены запросы и мутации для работы с сообщениями и чатом.
This commit is contained in:
@ -179,8 +179,30 @@ export const resolvers = {
|
||||
|
||||
const existingCounterpartyIds = existingCounterparties.map(c => c.counterpartyId)
|
||||
|
||||
const where: any = {
|
||||
id: { not: currentUser.organization.id } // Исключаем только собственную организацию
|
||||
// Получаем исходящие заявки для добавления флага hasOutgoingRequest
|
||||
const outgoingRequests = await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
senderId: currentUser.organization.id,
|
||||
status: 'PENDING'
|
||||
},
|
||||
select: { receiverId: true }
|
||||
})
|
||||
|
||||
const outgoingRequestIds = outgoingRequests.map(r => r.receiverId)
|
||||
|
||||
// Получаем входящие заявки для добавления флага hasIncomingRequest
|
||||
const incomingRequests = await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
receiverId: currentUser.organization.id,
|
||||
status: 'PENDING'
|
||||
},
|
||||
select: { senderId: true }
|
||||
})
|
||||
|
||||
const incomingRequestIds = incomingRequests.map(r => r.senderId)
|
||||
|
||||
const where: Record<string, unknown> = {
|
||||
// Больше не исключаем собственную организацию
|
||||
}
|
||||
|
||||
if (args.type) {
|
||||
@ -205,10 +227,13 @@ export const resolvers = {
|
||||
}
|
||||
})
|
||||
|
||||
// Добавляем флаг isCounterparty к каждой организации
|
||||
// Добавляем флаги isCounterparty, isCurrentUser, hasOutgoingRequest и hasIncomingRequest к каждой организации
|
||||
return organizations.map(org => ({
|
||||
...org,
|
||||
isCounterparty: existingCounterpartyIds.includes(org.id)
|
||||
isCounterparty: existingCounterpartyIds.includes(org.id),
|
||||
isCurrentUser: org.id === currentUser.organization?.id,
|
||||
hasOutgoingRequest: outgoingRequestIds.includes(org.id),
|
||||
hasIncomingRequest: incomingRequestIds.includes(org.id)
|
||||
}))
|
||||
},
|
||||
|
||||
@ -322,6 +347,82 @@ export const resolvers = {
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
},
|
||||
|
||||
// Сообщения с контрагентом
|
||||
messages: async (_: unknown, args: { counterpartyId: string; limit?: number; offset?: number }, 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 limit = args.limit || 50
|
||||
const offset = args.offset || 0
|
||||
|
||||
const messages = await prisma.message.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.counterpartyId
|
||||
},
|
||||
{
|
||||
senderOrganizationId: args.counterpartyId,
|
||||
receiverOrganizationId: currentUser.organization.id
|
||||
}
|
||||
]
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
take: limit,
|
||||
skip: offset
|
||||
})
|
||||
|
||||
return messages
|
||||
},
|
||||
|
||||
// Список чатов (последние сообщения с каждым контрагентом)
|
||||
conversations: 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('У пользователя нет организации')
|
||||
}
|
||||
|
||||
// TODO: Здесь будет логика получения списка чатов
|
||||
// Пока возвращаем пустой массив, так как таблица сообщений еще не создана
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
@ -608,13 +709,16 @@ export const resolvers = {
|
||||
})
|
||||
}
|
||||
|
||||
// Создаем организацию селлера - используем название магазина как основное имя
|
||||
const shopName = validationResults[0]?.data?.sellerName || 'Магазин'
|
||||
// Создаем организацию селлера - используем tradeMark как основное имя
|
||||
const tradeMark = validationResults[0]?.data?.tradeMark
|
||||
const sellerName = validationResults[0]?.data?.sellerName
|
||||
const shopName = tradeMark || sellerName || 'Магазин'
|
||||
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
inn: validationResults[0]?.data?.inn || `SELLER_${Date.now()}`,
|
||||
name: shopName,
|
||||
fullName: `Интернет-магазин "${shopName}"`,
|
||||
name: shopName, // Используем tradeMark как основное название
|
||||
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
|
||||
type: 'SELLER'
|
||||
}
|
||||
})
|
||||
@ -858,11 +962,19 @@ export const resolvers = {
|
||||
try {
|
||||
const { input } = args
|
||||
|
||||
// Обновляем аватар пользователя если указан
|
||||
// Обновляем данные пользователя (аватар, имя управляющего)
|
||||
const userUpdateData: { avatar?: string; managerName?: string } = {}
|
||||
if (input.avatar) {
|
||||
userUpdateData.avatar = input.avatar
|
||||
}
|
||||
if (input.managerName) {
|
||||
userUpdateData.managerName = input.managerName
|
||||
}
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) {
|
||||
await prisma.user.update({
|
||||
where: { id: context.user.id },
|
||||
data: { avatar: input.avatar }
|
||||
data: userUpdateData
|
||||
})
|
||||
}
|
||||
|
||||
@ -874,6 +986,9 @@ export const resolvers = {
|
||||
managementPost?: string
|
||||
} = {}
|
||||
|
||||
// Название организации больше не обновляется через профиль
|
||||
// Для селлеров устанавливается при регистрации, для остальных - при смене ИНН
|
||||
|
||||
// Обновляем контактные данные в JSON поле phones
|
||||
if (input.orgPhone) {
|
||||
updateData.phones = [{ value: input.orgPhone, type: 'main' }]
|
||||
@ -898,9 +1013,7 @@ export const resolvers = {
|
||||
}
|
||||
} = {}
|
||||
|
||||
if (input.managerName) {
|
||||
customContacts.managerName = input.managerName
|
||||
}
|
||||
// managerName теперь сохраняется в поле пользователя, а не в JSON
|
||||
|
||||
if (input.telegram) {
|
||||
customContacts.telegram = input.telegram
|
||||
@ -1015,7 +1128,8 @@ export const resolvers = {
|
||||
// Подготавливаем данные для обновления
|
||||
const updateData: Prisma.OrganizationUpdateInput = {
|
||||
kpp: organizationData.kpp,
|
||||
name: organizationData.name,
|
||||
// Для селлеров не обновляем название организации (это название магазина)
|
||||
...(user.organization.type !== 'SELLER' && { name: organizationData.name }),
|
||||
fullName: organizationData.fullName,
|
||||
address: organizationData.address,
|
||||
addressFull: organizationData.addressFull,
|
||||
@ -1023,7 +1137,7 @@ export const resolvers = {
|
||||
ogrnDate: organizationData.ogrnDate ? organizationData.ogrnDate.toISOString() : null,
|
||||
registrationDate: organizationData.registrationDate ? organizationData.registrationDate.toISOString() : null,
|
||||
liquidationDate: organizationData.liquidationDate ? organizationData.liquidationDate.toISOString() : null,
|
||||
managementName: organizationData.managementName,
|
||||
managementName: organizationData.managementName, // Всегда перезаписываем данными из DaData (может быть null)
|
||||
managementPost: user.organization.managementPost, // Сохраняем кастомные данные пользователя
|
||||
opfCode: organizationData.opfCode,
|
||||
opfFull: organizationData.opfFull,
|
||||
@ -1321,6 +1435,317 @@ export const resolvers = {
|
||||
console.error('Error removing counterparty:', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить сообщение
|
||||
sendMessage: async (_: unknown, args: { receiverOrganizationId: string; content?: string; type?: 'TEXT' | 'VOICE' }, 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 isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
}
|
||||
|
||||
// Получаем организацию получателя
|
||||
const receiverOrganization = await prisma.organization.findUnique({
|
||||
where: { id: args.receiverOrganizationId }
|
||||
})
|
||||
|
||||
if (!receiverOrganization) {
|
||||
throw new GraphQLError('Организация получателя не найдена')
|
||||
}
|
||||
|
||||
try {
|
||||
// Создаем сообщение
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: args.content?.trim() || null,
|
||||
type: args.type || 'TEXT',
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Сообщение отправлено',
|
||||
messageData: message
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке сообщения'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить голосовое сообщение
|
||||
sendVoiceMessage: async (_: unknown, args: { receiverOrganizationId: string; voiceUrl: string; voiceDuration: number }, 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 isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
}
|
||||
|
||||
// Получаем организацию получателя
|
||||
const receiverOrganization = await prisma.organization.findUnique({
|
||||
where: { id: args.receiverOrganizationId }
|
||||
})
|
||||
|
||||
if (!receiverOrganization) {
|
||||
throw new GraphQLError('Организация получателя не найдена')
|
||||
}
|
||||
|
||||
try {
|
||||
// Создаем голосовое сообщение
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: null,
|
||||
type: 'VOICE',
|
||||
voiceUrl: args.voiceUrl,
|
||||
voiceDuration: args.voiceDuration,
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Голосовое сообщение отправлено',
|
||||
messageData: message
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending voice message:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке голосового сообщения'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить изображение
|
||||
sendImageMessage: async (_: unknown, args: { receiverOrganizationId: string; fileUrl: string; fileName: string; fileSize: number; fileType: 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 isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
}
|
||||
|
||||
try {
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: null,
|
||||
type: 'IMAGE',
|
||||
fileUrl: args.fileUrl,
|
||||
fileName: args.fileName,
|
||||
fileSize: args.fileSize,
|
||||
fileType: args.fileType,
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Изображение отправлено',
|
||||
messageData: message
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending image:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке изображения'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить файл
|
||||
sendFileMessage: async (_: unknown, args: { receiverOrganizationId: string; fileUrl: string; fileName: string; fileSize: number; fileType: 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 isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
}
|
||||
|
||||
try {
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: null,
|
||||
type: 'FILE',
|
||||
fileUrl: args.fileUrl,
|
||||
fileName: args.fileName,
|
||||
fileSize: args.fileSize,
|
||||
fileType: args.fileType,
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Файл отправлен',
|
||||
messageData: message
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending file:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке файла'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Отметить сообщения как прочитанные
|
||||
markMessagesAsRead: async (_: unknown, args: { conversationId: string }, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Здесь будет логика обновления статуса сообщений
|
||||
// Пока возвращаем успешный ответ
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
@ -1359,5 +1784,23 @@ export const resolvers = {
|
||||
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
Message: {
|
||||
type: (parent: { type?: string | null }) => {
|
||||
return parent.type || 'TEXT'
|
||||
},
|
||||
createdAt: (parent: { createdAt: Date | string }) => {
|
||||
if (parent.createdAt instanceof Date) {
|
||||
return parent.createdAt.toISOString()
|
||||
}
|
||||
return parent.createdAt
|
||||
},
|
||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||
if (parent.updatedAt instanceof Date) {
|
||||
return parent.updatedAt.toISOString()
|
||||
}
|
||||
return parent.updatedAt
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user