Добавлены модели товаров и корзины для оптовиков, реализованы соответствующие мутации и запросы в GraphQL. Обновлен API для загрузки файлов с учетом новых типов данных. Улучшена обработка ошибок и добавлены новые функции для работы с категориями товаров.
This commit is contained in:
@ -634,4 +634,187 @@ export const DELETE_SUPPLY = gql`
|
||||
mutation DeleteSupply($id: ID!) {
|
||||
deleteSupply(id: $id)
|
||||
}
|
||||
`
|
||||
`
|
||||
|
||||
// Мутации для товаров оптовика
|
||||
export const CREATE_PRODUCT = gql`
|
||||
mutation CreateProduct($input: ProductInput!) {
|
||||
createProduct(input: $input) {
|
||||
success
|
||||
message
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
brand
|
||||
color
|
||||
size
|
||||
weight
|
||||
dimensions
|
||||
material
|
||||
images
|
||||
mainImage
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const UPDATE_PRODUCT = gql`
|
||||
mutation UpdateProduct($id: ID!, $input: ProductInput!) {
|
||||
updateProduct(id: $id, input: $input) {
|
||||
success
|
||||
message
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
brand
|
||||
color
|
||||
size
|
||||
weight
|
||||
dimensions
|
||||
material
|
||||
images
|
||||
mainImage
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const DELETE_PRODUCT = gql`
|
||||
mutation DeleteProduct($id: ID!) {
|
||||
deleteProduct(id: $id)
|
||||
}
|
||||
`
|
||||
|
||||
// Мутации для корзины
|
||||
export const ADD_TO_CART = gql`
|
||||
mutation AddToCart($productId: ID!, $quantity: Int = 1) {
|
||||
addToCart(productId: $productId, quantity: $quantity) {
|
||||
success
|
||||
message
|
||||
cart {
|
||||
id
|
||||
totalPrice
|
||||
totalItems
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
totalPrice
|
||||
isAvailable
|
||||
availableQuantity
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
price
|
||||
quantity
|
||||
images
|
||||
mainImage
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const UPDATE_CART_ITEM = gql`
|
||||
mutation UpdateCartItem($productId: ID!, $quantity: Int!) {
|
||||
updateCartItem(productId: $productId, quantity: $quantity) {
|
||||
success
|
||||
message
|
||||
cart {
|
||||
id
|
||||
totalPrice
|
||||
totalItems
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
totalPrice
|
||||
isAvailable
|
||||
availableQuantity
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
price
|
||||
quantity
|
||||
images
|
||||
mainImage
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_FROM_CART = gql`
|
||||
mutation RemoveFromCart($productId: ID!) {
|
||||
removeFromCart(productId: $productId) {
|
||||
success
|
||||
message
|
||||
cart {
|
||||
id
|
||||
totalPrice
|
||||
totalItems
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
totalPrice
|
||||
isAvailable
|
||||
availableQuantity
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
price
|
||||
quantity
|
||||
images
|
||||
mainImage
|
||||
organization {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const CLEAR_CART = gql`
|
||||
mutation ClearCart {
|
||||
clearCart
|
||||
}
|
||||
`
|
@ -77,6 +77,34 @@ export const GET_MY_SUPPLIES = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_MY_PRODUCTS = gql`
|
||||
query GetMyProducts {
|
||||
myProducts {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
brand
|
||||
color
|
||||
size
|
||||
weight
|
||||
dimensions
|
||||
material
|
||||
images
|
||||
mainImage
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Запросы для контрагентов
|
||||
export const SEARCH_ORGANIZATIONS = gql`
|
||||
query SearchOrganizations($type: OrganizationType, $search: String) {
|
||||
@ -300,4 +328,112 @@ export const GET_CONVERSATIONS = gql`
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_CATEGORIES = gql`
|
||||
query GetCategories {
|
||||
categories {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_ALL_PRODUCTS = gql`
|
||||
query GetAllProducts($search: String, $category: String) {
|
||||
allProducts(search: $search, category: $category) {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
brand
|
||||
color
|
||||
size
|
||||
weight
|
||||
dimensions
|
||||
material
|
||||
images
|
||||
mainImage
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
organization {
|
||||
id
|
||||
inn
|
||||
name
|
||||
fullName
|
||||
type
|
||||
address
|
||||
phones
|
||||
emails
|
||||
users {
|
||||
id
|
||||
avatar
|
||||
managerName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_MY_CART = gql`
|
||||
query GetMyCart {
|
||||
myCart {
|
||||
id
|
||||
totalPrice
|
||||
totalItems
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
totalPrice
|
||||
isAvailable
|
||||
availableQuantity
|
||||
createdAt
|
||||
updatedAt
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
brand
|
||||
color
|
||||
size
|
||||
images
|
||||
mainImage
|
||||
isActive
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
organization {
|
||||
id
|
||||
inn
|
||||
name
|
||||
fullName
|
||||
type
|
||||
address
|
||||
phones
|
||||
emails
|
||||
users {
|
||||
id
|
||||
avatar
|
||||
managerName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`
|
@ -483,6 +483,161 @@ export const resolvers = {
|
||||
include: { organization: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
},
|
||||
|
||||
// Мои товары (для оптовиков)
|
||||
myProducts: 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 !== 'WHOLESALE') {
|
||||
throw new GraphQLError('Товары доступны только для оптовиков')
|
||||
}
|
||||
|
||||
return await prisma.product.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
category: true,
|
||||
organization: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
},
|
||||
|
||||
// Все товары всех оптовиков для маркета
|
||||
allProducts: async (_: unknown, args: { search?: string; category?: string }, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
}
|
||||
|
||||
const where: Record<string, unknown> = {
|
||||
isActive: true, // Показываем только активные товары
|
||||
organization: {
|
||||
type: 'WHOLESALE' // Только товары оптовиков
|
||||
}
|
||||
}
|
||||
|
||||
if (args.search) {
|
||||
where.OR = [
|
||||
{ name: { contains: args.search, mode: 'insensitive' } },
|
||||
{ article: { contains: args.search, mode: 'insensitive' } },
|
||||
{ description: { contains: args.search, mode: 'insensitive' } },
|
||||
{ brand: { contains: args.search, mode: 'insensitive' } }
|
||||
]
|
||||
}
|
||||
|
||||
if (args.category) {
|
||||
where.categoryId = args.category
|
||||
}
|
||||
|
||||
return await prisma.product.findMany({
|
||||
where,
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100 // Ограничиваем количество результатов
|
||||
})
|
||||
},
|
||||
|
||||
// Все категории
|
||||
categories: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
}
|
||||
|
||||
return await prisma.category.findMany({
|
||||
orderBy: { name: 'asc' }
|
||||
})
|
||||
},
|
||||
|
||||
// Корзина пользователя
|
||||
myCart: 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('У пользователя нет организации')
|
||||
}
|
||||
|
||||
// Найти или создать корзину для организации
|
||||
let cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!cart) {
|
||||
cart = await prisma.cart.create({
|
||||
data: {
|
||||
organizationId: currentUser.organization.id
|
||||
},
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return cart
|
||||
}
|
||||
},
|
||||
|
||||
@ -592,7 +747,7 @@ export const resolvers = {
|
||||
|
||||
registerFulfillmentOrganization: async (
|
||||
_: unknown,
|
||||
args: { input: { phone: string; inn: string } },
|
||||
args: { input: { phone: string; inn: string; type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE' } },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
@ -601,7 +756,7 @@ export const resolvers = {
|
||||
})
|
||||
}
|
||||
|
||||
const { inn } = args.input
|
||||
const { inn, type } = args.input
|
||||
|
||||
// Валидируем ИНН
|
||||
if (!dadataService.validateInn(inn)) {
|
||||
@ -675,7 +830,7 @@ export const resolvers = {
|
||||
revenue: organizationData.revenue,
|
||||
taxSystem: organizationData.taxSystem,
|
||||
|
||||
type: 'FULFILLMENT',
|
||||
type: type,
|
||||
dadataData: JSON.parse(JSON.stringify(organizationData.rawData))
|
||||
}
|
||||
})
|
||||
@ -695,7 +850,7 @@ export const resolvers = {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Фулфилмент организация успешно зарегистрирована',
|
||||
message: 'Организация успешно зарегистрирована',
|
||||
user: updatedUser
|
||||
}
|
||||
|
||||
@ -2096,6 +2251,599 @@ export const resolvers = {
|
||||
console.error('Error deleting supply:', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
// Создать товар
|
||||
createProduct: async (_: unknown, args: {
|
||||
input: {
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
}, 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 !== 'WHOLESALE') {
|
||||
throw new GraphQLError('Товары доступны только для оптовиков')
|
||||
}
|
||||
|
||||
// Проверяем уникальность артикула в рамках организации
|
||||
const existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
article: args.input.article,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
|
||||
if (existingProduct) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар с таким артикулом уже существует'
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await prisma.product.create({
|
||||
data: {
|
||||
name: args.input.name,
|
||||
article: args.input.article,
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
quantity: args.input.quantity,
|
||||
categoryId: args.input.categoryId,
|
||||
brand: args.input.brand,
|
||||
color: args.input.color,
|
||||
size: args.input.size,
|
||||
weight: args.input.weight,
|
||||
dimensions: args.input.dimensions,
|
||||
material: args.input.material,
|
||||
images: args.input.images || [],
|
||||
mainImage: args.input.mainImage,
|
||||
isActive: args.input.isActive ?? true,
|
||||
organizationId: currentUser.organization.id
|
||||
},
|
||||
include: {
|
||||
category: true,
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар успешно создан',
|
||||
product
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating product:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании товара'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить товар
|
||||
updateProduct: async (_: unknown, args: {
|
||||
id: string;
|
||||
input: {
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
categoryId?: string;
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
}, 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 existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
|
||||
if (!existingProduct) {
|
||||
throw new GraphQLError('Товар не найден или нет доступа')
|
||||
}
|
||||
|
||||
// Проверяем уникальность артикула (если он изменился)
|
||||
if (args.input.article !== existingProduct.article) {
|
||||
const duplicateProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
article: args.input.article,
|
||||
organizationId: currentUser.organization.id,
|
||||
NOT: { id: args.id }
|
||||
}
|
||||
})
|
||||
|
||||
if (duplicateProduct) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар с таким артикулом уже существует'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await prisma.product.update({
|
||||
where: { id: args.id },
|
||||
data: {
|
||||
name: args.input.name,
|
||||
article: args.input.article,
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
quantity: args.input.quantity,
|
||||
categoryId: args.input.categoryId,
|
||||
brand: args.input.brand,
|
||||
color: args.input.color,
|
||||
size: args.input.size,
|
||||
weight: args.input.weight,
|
||||
dimensions: args.input.dimensions,
|
||||
material: args.input.material,
|
||||
images: args.input.images || [],
|
||||
mainImage: args.input.mainImage,
|
||||
isActive: args.input.isActive ?? true
|
||||
},
|
||||
include: {
|
||||
category: true,
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар успешно обновлен',
|
||||
product
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating product:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении товара'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить товар
|
||||
deleteProduct: 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 existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
|
||||
if (!existingProduct) {
|
||||
throw new GraphQLError('Товар не найден или нет доступа')
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.product.delete({
|
||||
where: { id: args.id }
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error deleting product:', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
// Добавить товар в корзину
|
||||
addToCart: async (_: unknown, args: { productId: string; quantity: 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 product = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.productId,
|
||||
isActive: true
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар не найден или неактивен'
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
||||
if (product.organizationId === currentUser.organization.id) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Нельзя добавлять собственные товары в корзину'
|
||||
}
|
||||
}
|
||||
|
||||
// Найти или создать корзину
|
||||
let cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
|
||||
if (!cart) {
|
||||
cart = await prisma.cart.create({
|
||||
data: {
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Проверяем, есть ли уже такой товар в корзине
|
||||
const existingCartItem = await prisma.cartItem.findUnique({
|
||||
where: {
|
||||
cartId_productId: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (existingCartItem) {
|
||||
// Обновляем количество
|
||||
const newQuantity = existingCartItem.quantity + args.quantity
|
||||
|
||||
if (newQuantity > product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.cartItem.update({
|
||||
where: { id: existingCartItem.id },
|
||||
data: { quantity: newQuantity }
|
||||
})
|
||||
} else {
|
||||
// Создаем новый элемент корзины
|
||||
if (args.quantity > product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.cartItem.create({
|
||||
data: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId,
|
||||
quantity: args.quantity
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Возвращаем обновленную корзину
|
||||
const updatedCart = await prisma.cart.findUnique({
|
||||
where: { id: cart.id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар добавлен в корзину',
|
||||
cart: updatedCart
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding to cart:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при добавлении в корзину'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить количество товара в корзине
|
||||
updateCartItem: async (_: unknown, args: { productId: string; quantity: 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 cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
|
||||
if (!cart) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Корзина не найдена'
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, что товар существует в корзине
|
||||
const cartItem = await prisma.cartItem.findUnique({
|
||||
where: {
|
||||
cartId_productId: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId
|
||||
}
|
||||
},
|
||||
include: {
|
||||
product: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!cartItem) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар не найден в корзине'
|
||||
}
|
||||
}
|
||||
|
||||
if (args.quantity <= 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Количество должно быть больше 0'
|
||||
}
|
||||
}
|
||||
|
||||
if (args.quantity > cartItem.product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара в наличии. Доступно: ${cartItem.product.quantity}`
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.cartItem.update({
|
||||
where: { id: cartItem.id },
|
||||
data: { quantity: args.quantity }
|
||||
})
|
||||
|
||||
// Возвращаем обновленную корзину
|
||||
const updatedCart = await prisma.cart.findUnique({
|
||||
where: { id: cart.id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Количество товара обновлено',
|
||||
cart: updatedCart
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating cart item:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении корзины'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить товар из корзины
|
||||
removeFromCart: async (_: unknown, args: { productId: 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 cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
|
||||
if (!cart) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Корзина не найдена'
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.cartItem.delete({
|
||||
where: {
|
||||
cartId_productId: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Возвращаем обновленную корзину
|
||||
const updatedCart = await prisma.cart.findUnique({
|
||||
where: { id: cart.id },
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар удален из корзины',
|
||||
cart: updatedCart
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error removing from cart:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при удалении из корзины'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Очистить корзину
|
||||
clearCart: 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 cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
|
||||
if (!cart) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.cartItem.deleteMany({
|
||||
where: { cartId: cart.id }
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error clearing cart:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -2114,6 +2862,29 @@ export const resolvers = {
|
||||
}
|
||||
},
|
||||
|
||||
Cart: {
|
||||
totalPrice: (parent: { items: Array<{ product: { price: number }, quantity: number }> }) => {
|
||||
return parent.items.reduce((total, item) => {
|
||||
return total + (Number(item.product.price) * item.quantity)
|
||||
}, 0)
|
||||
},
|
||||
totalItems: (parent: { items: Array<{ quantity: number }> }) => {
|
||||
return parent.items.reduce((total, item) => total + item.quantity, 0)
|
||||
}
|
||||
},
|
||||
|
||||
CartItem: {
|
||||
totalPrice: (parent: { product: { price: number }, quantity: number }) => {
|
||||
return Number(parent.product.price) * parent.quantity
|
||||
},
|
||||
isAvailable: (parent: { product: { quantity: number, isActive: boolean }, quantity: number }) => {
|
||||
return parent.product.isActive && parent.product.quantity >= parent.quantity
|
||||
},
|
||||
availableQuantity: (parent: { product: { quantity: number } }) => {
|
||||
return parent.product.quantity
|
||||
}
|
||||
},
|
||||
|
||||
User: {
|
||||
organization: async (parent: { organizationId?: string; organization?: unknown }) => {
|
||||
// Если организация уже загружена через include, возвращаем её
|
||||
|
@ -28,6 +28,18 @@ export const typeDefs = gql`
|
||||
|
||||
# Расходники организации
|
||||
mySupplies: [Supply!]!
|
||||
|
||||
# Товары оптовика
|
||||
myProducts: [Product!]!
|
||||
|
||||
# Все товары всех оптовиков для маркета
|
||||
allProducts(search: String, category: String): [Product!]!
|
||||
|
||||
# Все категории
|
||||
categories: [Category!]!
|
||||
|
||||
# Корзина пользователя
|
||||
myCart: Cart
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@ -77,6 +89,17 @@ export const typeDefs = gql`
|
||||
createSupply(input: SupplyInput!): SupplyResponse!
|
||||
updateSupply(id: ID!, input: SupplyInput!): SupplyResponse!
|
||||
deleteSupply(id: ID!): Boolean!
|
||||
|
||||
# Работа с товарами (для оптовиков)
|
||||
createProduct(input: ProductInput!): ProductResponse!
|
||||
updateProduct(id: ID!, input: ProductInput!): ProductResponse!
|
||||
deleteProduct(id: ID!): Boolean!
|
||||
|
||||
# Работа с корзиной
|
||||
addToCart(productId: ID!, quantity: Int = 1): CartResponse!
|
||||
updateCartItem(productId: ID!, quantity: Int!): CartResponse!
|
||||
removeFromCart(productId: ID!): CartResponse!
|
||||
clearCart: Boolean!
|
||||
}
|
||||
|
||||
# Типы данных
|
||||
@ -160,6 +183,7 @@ export const typeDefs = gql`
|
||||
input FulfillmentRegistrationInput {
|
||||
phone: String!
|
||||
inn: String!
|
||||
type: OrganizationType!
|
||||
}
|
||||
|
||||
input SellerRegistrationInput {
|
||||
@ -349,6 +373,89 @@ export const typeDefs = gql`
|
||||
supply: Supply
|
||||
}
|
||||
|
||||
# Типы для категорий товаров
|
||||
type Category {
|
||||
id: ID!
|
||||
name: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
# Типы для товаров оптовика
|
||||
type Product {
|
||||
id: ID!
|
||||
name: String!
|
||||
article: String!
|
||||
description: String
|
||||
price: Float!
|
||||
quantity: Int!
|
||||
category: Category
|
||||
brand: String
|
||||
color: String
|
||||
size: String
|
||||
weight: Float
|
||||
dimensions: String
|
||||
material: String
|
||||
images: [String!]!
|
||||
mainImage: String
|
||||
isActive: Boolean!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
input ProductInput {
|
||||
name: String!
|
||||
article: String!
|
||||
description: String
|
||||
price: Float!
|
||||
quantity: Int!
|
||||
categoryId: ID
|
||||
brand: String
|
||||
color: String
|
||||
size: String
|
||||
weight: Float
|
||||
dimensions: String
|
||||
material: String
|
||||
images: [String!]
|
||||
mainImage: String
|
||||
isActive: Boolean
|
||||
}
|
||||
|
||||
type ProductResponse {
|
||||
success: Boolean!
|
||||
message: String!
|
||||
product: Product
|
||||
}
|
||||
|
||||
# Типы для корзины
|
||||
type Cart {
|
||||
id: ID!
|
||||
items: [CartItem!]!
|
||||
totalPrice: Float!
|
||||
totalItems: Int!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type CartItem {
|
||||
id: ID!
|
||||
product: Product!
|
||||
quantity: Int!
|
||||
totalPrice: Float!
|
||||
isAvailable: Boolean!
|
||||
availableQuantity: Int!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type CartResponse {
|
||||
success: Boolean!
|
||||
message: String!
|
||||
cart: Cart
|
||||
}
|
||||
|
||||
# JSON скаляр
|
||||
scalar JSON
|
||||
`
|
Reference in New Issue
Block a user