Добавлено отладочное логирование в компоненты создания и отображения товаров, обновлены типы продуктов в GraphQL запросах и резолверах. Опт… #2
96
diagnostic-script.js
Normal file
96
diagnostic-script.js
Normal file
@ -0,0 +1,96 @@
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function diagnoseDatabase() {
|
||||
try {
|
||||
console.log("🔍 ДИАГНОСТИКА БАЗЫ ДАННЫХ...\n");
|
||||
|
||||
// Проверяем пользователей
|
||||
const users = await prisma.user.findMany({
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("👥 ПОЛЬЗОВАТЕЛИ:");
|
||||
users.forEach((user) => {
|
||||
console.log(` - ID: ${user.id}`);
|
||||
console.log(` Телефон: ${user.phone}`);
|
||||
console.log(` Организация: ${user.organization?.name || "НЕТ"}`);
|
||||
console.log(` Тип организации: ${user.organization?.type || "НЕТ"}`);
|
||||
console.log("");
|
||||
});
|
||||
|
||||
// Проверяем организации
|
||||
const organizations = await prisma.organization.findMany();
|
||||
console.log("🏢 ОРГАНИЗАЦИИ:");
|
||||
organizations.forEach((org) => {
|
||||
console.log(` - ID: ${org.id}`);
|
||||
console.log(` Название: ${org.name}`);
|
||||
console.log(` Тип: ${org.type}`);
|
||||
console.log("");
|
||||
});
|
||||
|
||||
// Проверяем товары
|
||||
const products = await prisma.product.findMany({
|
||||
include: {
|
||||
organization: true,
|
||||
category: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("🛍️ ТОВАРЫ:");
|
||||
if (products.length === 0) {
|
||||
console.log(" НЕТ ТОВАРОВ В БАЗЕ ДАННЫХ");
|
||||
} else {
|
||||
products.forEach((product) => {
|
||||
console.log(` - ID: ${product.id}`);
|
||||
console.log(` Название: ${product.name}`);
|
||||
console.log(` Артикул: ${product.article}`);
|
||||
console.log(` Тип: ${product.type}`);
|
||||
console.log(` Активен: ${product.isActive}`);
|
||||
console.log(
|
||||
` Организация: ${product.organization?.name || "НЕТ"} (${
|
||||
product.organization?.type || "НЕТ"
|
||||
})`
|
||||
);
|
||||
console.log(` Создан: ${product.createdAt}`);
|
||||
console.log("");
|
||||
});
|
||||
}
|
||||
|
||||
// Проверяем товары поставщиков
|
||||
const wholesaleProducts = await prisma.product.findMany({
|
||||
where: {
|
||||
organization: {
|
||||
type: "WHOLESALE",
|
||||
},
|
||||
type: "PRODUCT",
|
||||
},
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("🏪 ТОВАРЫ ПОСТАВЩИКОВ (WHOLESALE + PRODUCT):");
|
||||
if (wholesaleProducts.length === 0) {
|
||||
console.log(" НЕТ ТОВАРОВ ПОСТАВЩИКОВ");
|
||||
} else {
|
||||
wholesaleProducts.forEach((product) => {
|
||||
console.log(
|
||||
` - ${product.name} (${product.article}) - ${product.organization?.name}`
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ ОШИБКА:", error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
diagnoseDatabase();
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
@ -54,6 +54,7 @@ interface FulfillmentConsumableProduct {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
type?: "PRODUCT" | "CONSUMABLE";
|
||||
category?: { name: string };
|
||||
images: string[];
|
||||
mainImage?: string;
|
||||
@ -128,6 +129,36 @@ export function CreateFulfillmentConsumablesSupplyPage() {
|
||||
)
|
||||
: [];
|
||||
|
||||
// Отладочное логирование
|
||||
React.useEffect(() => {
|
||||
console.log("🛒 FULFILLMENT CONSUMABLES DEBUG:", {
|
||||
selectedSupplier: selectedSupplier
|
||||
? {
|
||||
id: selectedSupplier.id,
|
||||
name: selectedSupplier.name || selectedSupplier.fullName,
|
||||
type: selectedSupplier.type,
|
||||
}
|
||||
: null,
|
||||
productsLoading,
|
||||
allProductsCount: productsData?.allProducts?.length || 0,
|
||||
supplierProductsCount: supplierProducts.length,
|
||||
allProducts:
|
||||
productsData?.allProducts?.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
organizationId: p.organization.id,
|
||||
organizationName: p.organization.name,
|
||||
type: p.type || "NO_TYPE",
|
||||
})) || [],
|
||||
supplierProducts: supplierProducts.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
organizationId: p.organization.id,
|
||||
organizationName: p.organization.name,
|
||||
})),
|
||||
});
|
||||
}, [selectedSupplier, productsData, productsLoading, supplierProducts]);
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
@ -363,7 +394,14 @@ export function CreateFulfillmentConsumablesSupplyPage() {
|
||||
width: "calc((100% - 48px) / 7)", // 48px = 6 gaps * 8px each
|
||||
animationDelay: `${index * 100}ms`,
|
||||
}}
|
||||
onClick={() => setSelectedSupplier(supplier)}
|
||||
onClick={() => {
|
||||
console.log("🔄 ВЫБРАН ПОСТАВЩИК:", {
|
||||
id: supplier.id,
|
||||
name: supplier.name || supplier.fullName,
|
||||
type: supplier.type,
|
||||
});
|
||||
setSelectedSupplier(supplier);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center h-full p-2 space-y-1">
|
||||
<div className="relative">
|
||||
|
@ -1,181 +1,197 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { useMutation, useQuery } from '@apollo/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { CREATE_PRODUCT, UPDATE_PRODUCT } from '@/graphql/mutations'
|
||||
import { GET_CATEGORIES } from '@/graphql/queries'
|
||||
import { X, Star, Upload } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { useState, useRef } from "react";
|
||||
import Image from "next/image";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { CREATE_PRODUCT, UPDATE_PRODUCT } from "@/graphql/mutations";
|
||||
import { GET_CATEGORIES } from "@/graphql/queries";
|
||||
import { X, Star, Upload } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Product {
|
||||
id: string
|
||||
name: string
|
||||
article: string
|
||||
description: string
|
||||
price: number
|
||||
quantity: number
|
||||
type: 'PRODUCT' | 'CONSUMABLE'
|
||||
category: { id: string; name: string } | null
|
||||
brand: string
|
||||
color: string
|
||||
size: string
|
||||
weight: number
|
||||
dimensions: string
|
||||
material: string
|
||||
images: string[]
|
||||
mainImage: string
|
||||
isActive: boolean
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
type: "PRODUCT" | "CONSUMABLE";
|
||||
category: { id: string; name: string } | null;
|
||||
brand: string;
|
||||
color: string;
|
||||
size: string;
|
||||
weight: number;
|
||||
dimensions: string;
|
||||
material: string;
|
||||
images: string[];
|
||||
mainImage: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
interface ProductFormProps {
|
||||
product?: Product | null
|
||||
onSave: () => void
|
||||
onCancel: () => void
|
||||
product?: Product | null;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
name: product?.name || '',
|
||||
article: product?.article || '',
|
||||
description: product?.description || '',
|
||||
name: product?.name || "",
|
||||
article: product?.article || "",
|
||||
description: product?.description || "",
|
||||
price: product?.price || 0,
|
||||
quantity: product?.quantity || 0,
|
||||
type: product?.type || 'PRODUCT' as 'PRODUCT' | 'CONSUMABLE',
|
||||
categoryId: product?.category?.id || 'none',
|
||||
brand: product?.brand || '',
|
||||
color: product?.color || '',
|
||||
size: product?.size || '',
|
||||
type: product?.type || ("PRODUCT" as "PRODUCT" | "CONSUMABLE"),
|
||||
categoryId: product?.category?.id || "none",
|
||||
brand: product?.brand || "",
|
||||
color: product?.color || "",
|
||||
size: product?.size || "",
|
||||
weight: product?.weight || 0,
|
||||
dimensions: product?.dimensions || '',
|
||||
material: product?.material || '',
|
||||
dimensions: product?.dimensions || "",
|
||||
material: product?.material || "",
|
||||
images: product?.images || [],
|
||||
mainImage: product?.mainImage || '',
|
||||
isActive: product?.isActive ?? true
|
||||
})
|
||||
mainImage: product?.mainImage || "",
|
||||
isActive: product?.isActive ?? true,
|
||||
});
|
||||
|
||||
const [isUploading] = useState(false)
|
||||
const [uploadingImages, setUploadingImages] = useState<Set<number>>(new Set())
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [isUploading] = useState(false);
|
||||
const [uploadingImages, setUploadingImages] = useState<Set<number>>(
|
||||
new Set()
|
||||
);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [createProduct, { loading: creating }] = useMutation(CREATE_PRODUCT);
|
||||
const [updateProduct, { loading: updating }] = useMutation(UPDATE_PRODUCT);
|
||||
|
||||
const [createProduct, { loading: creating }] = useMutation(CREATE_PRODUCT)
|
||||
const [updateProduct, { loading: updating }] = useMutation(UPDATE_PRODUCT)
|
||||
|
||||
// Загружаем категории
|
||||
const { data: categoriesData } = useQuery(GET_CATEGORIES)
|
||||
const { data: categoriesData } = useQuery(GET_CATEGORIES);
|
||||
|
||||
const loading = creating || updating
|
||||
const loading = creating || updating;
|
||||
|
||||
const handleInputChange = (field: string, value: string | number | boolean) => {
|
||||
setFormData(prev => ({
|
||||
const handleInputChange = (
|
||||
field: string,
|
||||
value: string | number | boolean
|
||||
) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}))
|
||||
}
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleImageUpload = async (files: FileList) => {
|
||||
const newUploadingIndexes = new Set<number>()
|
||||
const startIndex = formData.images.length
|
||||
const newUploadingIndexes = new Set<number>();
|
||||
const startIndex = formData.images.length;
|
||||
|
||||
// Добавляем плейсхолдеры для загружаемых изображений
|
||||
const placeholders = Array.from(files).map((_, index) => {
|
||||
newUploadingIndexes.add(startIndex + index)
|
||||
return '' // Пустой URL как плейсхолдер
|
||||
})
|
||||
newUploadingIndexes.add(startIndex + index);
|
||||
return ""; // Пустой URL как плейсхолдер
|
||||
});
|
||||
|
||||
setUploadingImages(prev => new Set([...prev, ...newUploadingIndexes]))
|
||||
setFormData(prev => ({
|
||||
setUploadingImages((prev) => new Set([...prev, ...newUploadingIndexes]));
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
images: [...prev.images, ...placeholders]
|
||||
}))
|
||||
images: [...prev.images, ...placeholders],
|
||||
}));
|
||||
|
||||
try {
|
||||
// Загружаем каждое изображение
|
||||
const uploadPromises = Array.from(files).map(async (file, index) => {
|
||||
const actualIndex = startIndex + index
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('type', 'product')
|
||||
const actualIndex = startIndex + index;
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("type", "product");
|
||||
|
||||
const response = await fetch('/api/upload-file', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
const response = await fetch("/api/upload-file", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка загрузки изображения')
|
||||
throw new Error("Ошибка загрузки изображения");
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return { index: actualIndex, url: result.fileUrl }
|
||||
})
|
||||
const result = await response.json();
|
||||
return { index: actualIndex, url: result.fileUrl };
|
||||
});
|
||||
|
||||
const results = await Promise.all(uploadPromises)
|
||||
const results = await Promise.all(uploadPromises);
|
||||
|
||||
// Обновляем URLs загруженных изображений
|
||||
setFormData(prev => {
|
||||
const newImages = [...prev.images]
|
||||
setFormData((prev) => {
|
||||
const newImages = [...prev.images];
|
||||
results.forEach(({ index, url }) => {
|
||||
newImages[index] = url
|
||||
})
|
||||
newImages[index] = url;
|
||||
});
|
||||
return {
|
||||
...prev,
|
||||
images: newImages,
|
||||
mainImage: prev.mainImage || results[0]?.url || '' // Устанавливаем первое изображение как главное
|
||||
}
|
||||
})
|
||||
mainImage: prev.mainImage || results[0]?.url || "", // Устанавливаем первое изображение как главное
|
||||
};
|
||||
});
|
||||
|
||||
toast.success('Изображения успешно загружены')
|
||||
toast.success("Изображения успешно загружены");
|
||||
} catch (error) {
|
||||
console.error('Error uploading images:', error)
|
||||
toast.error('Ошибка загрузки изображений')
|
||||
|
||||
console.error("Error uploading images:", error);
|
||||
toast.error("Ошибка загрузки изображений");
|
||||
|
||||
// Удаляем неудачные плейсхолдеры
|
||||
setFormData(prev => ({
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
images: prev.images.slice(0, startIndex)
|
||||
}))
|
||||
images: prev.images.slice(0, startIndex),
|
||||
}));
|
||||
} finally {
|
||||
// Убираем индикаторы загрузки
|
||||
setUploadingImages(prev => {
|
||||
const updated = new Set(prev)
|
||||
newUploadingIndexes.forEach(index => updated.delete(index))
|
||||
return updated
|
||||
})
|
||||
setUploadingImages((prev) => {
|
||||
const updated = new Set(prev);
|
||||
newUploadingIndexes.forEach((index) => updated.delete(index));
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveImage = (indexToRemove: number) => {
|
||||
setFormData(prev => {
|
||||
const newImages = prev.images.filter((_, index) => index !== indexToRemove)
|
||||
const removedImageUrl = prev.images[indexToRemove]
|
||||
|
||||
setFormData((prev) => {
|
||||
const newImages = prev.images.filter(
|
||||
(_, index) => index !== indexToRemove
|
||||
);
|
||||
const removedImageUrl = prev.images[indexToRemove];
|
||||
|
||||
return {
|
||||
...prev,
|
||||
images: newImages,
|
||||
mainImage: prev.mainImage === removedImageUrl ? (newImages[0] || '') : prev.mainImage
|
||||
}
|
||||
})
|
||||
}
|
||||
mainImage:
|
||||
prev.mainImage === removedImageUrl
|
||||
? newImages[0] || ""
|
||||
: prev.mainImage,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetMainImage = (imageUrl: string) => {
|
||||
setFormData(prev => ({
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
mainImage: imageUrl
|
||||
}))
|
||||
}
|
||||
mainImage: imageUrl,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
|
||||
if (!formData.name || !formData.article || formData.price <= 0) {
|
||||
toast.error('Пожалуйста, заполните все обязательные поля')
|
||||
return
|
||||
toast.error("Пожалуйста, заполните все обязательные поля");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -186,36 +202,43 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
price: formData.price,
|
||||
quantity: formData.quantity,
|
||||
type: formData.type,
|
||||
categoryId: formData.categoryId && formData.categoryId !== 'none' ? formData.categoryId : undefined,
|
||||
categoryId:
|
||||
formData.categoryId && formData.categoryId !== "none"
|
||||
? formData.categoryId
|
||||
: undefined,
|
||||
brand: formData.brand || undefined,
|
||||
color: formData.color || undefined,
|
||||
size: formData.size || undefined,
|
||||
weight: formData.weight || undefined,
|
||||
dimensions: formData.dimensions || undefined,
|
||||
material: formData.material || undefined,
|
||||
images: formData.images.filter(img => img), // Убираем пустые строки
|
||||
images: formData.images.filter((img) => img), // Убираем пустые строки
|
||||
mainImage: formData.mainImage || undefined,
|
||||
isActive: formData.isActive
|
||||
}
|
||||
isActive: formData.isActive,
|
||||
};
|
||||
|
||||
if (product) {
|
||||
await updateProduct({
|
||||
variables: { id: product.id, input }
|
||||
})
|
||||
toast.success('Товар успешно обновлен')
|
||||
variables: { id: product.id, input },
|
||||
refetchQueries: ["GetMyProducts"],
|
||||
});
|
||||
toast.success("Товар успешно обновлен");
|
||||
} else {
|
||||
await createProduct({
|
||||
variables: { input }
|
||||
})
|
||||
toast.success('Товар успешно создан')
|
||||
console.log("📝 СОЗДАНИЕ ТОВАРА - ОТПРАВКА ЗАПРОСА:", input);
|
||||
const result = await createProduct({
|
||||
variables: { input },
|
||||
refetchQueries: ["GetMyProducts"],
|
||||
});
|
||||
console.log("📝 РЕЗУЛЬТАТ СОЗДАНИЯ ТОВАРА:", result);
|
||||
toast.success("Товар успешно создан");
|
||||
}
|
||||
|
||||
onSave()
|
||||
onSave();
|
||||
} catch (error: unknown) {
|
||||
console.error('Error saving product:', error)
|
||||
toast.error((error as Error).message || 'Ошибка при сохранении товара')
|
||||
console.error("Error saving product:", error);
|
||||
toast.error((error as Error).message || "Ошибка при сохранении товара");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
@ -229,20 +252,20 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.name}
|
||||
onChange={(e) => handleInputChange('name', e.target.value)}
|
||||
onChange={(e) => handleInputChange("name", e.target.value)}
|
||||
placeholder="iPhone 15 Pro Max"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Артикул <span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={formData.article}
|
||||
onChange={(e) => handleInputChange('article', e.target.value)}
|
||||
onChange={(e) => handleInputChange("article", e.target.value)}
|
||||
placeholder="IP15PM-256-BLU"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
required
|
||||
@ -254,7 +277,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
<Label className="text-white/80 text-sm mb-2 block">Описание</Label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||
onChange={(e) => handleInputChange("description", e.target.value)}
|
||||
placeholder="Подробное описание товара..."
|
||||
className="glass-input text-white placeholder:text-white/40 w-full resize-none"
|
||||
rows={3}
|
||||
@ -267,7 +290,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.type}
|
||||
onValueChange={(value) => handleInputChange('type', value)}
|
||||
onValueChange={(value) => handleInputChange("type", value)}
|
||||
>
|
||||
<SelectTrigger className="glass-input text-white h-10">
|
||||
<SelectValue placeholder="Выберите тип" />
|
||||
@ -292,21 +315,27 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
value={formData.price || ''}
|
||||
onChange={(e) => handleInputChange('price', parseFloat(e.target.value) || 0)}
|
||||
value={formData.price || ""}
|
||||
onChange={(e) =>
|
||||
handleInputChange("price", parseFloat(e.target.value) || 0)
|
||||
}
|
||||
placeholder="99999.99"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Количество</Label>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Количество
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData.quantity || ''}
|
||||
onChange={(e) => handleInputChange('quantity', parseInt(e.target.value) || 0)}
|
||||
value={formData.quantity || ""}
|
||||
onChange={(e) =>
|
||||
handleInputChange("quantity", parseInt(e.target.value) || 0)
|
||||
}
|
||||
placeholder="100"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
@ -319,30 +348,38 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
<h3 className="text-white font-medium mb-4">Категоризация</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Категория</Label>
|
||||
<Label className="text-white/80 text-sm mb-2 block">
|
||||
Категория
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.categoryId}
|
||||
onValueChange={(value) => handleInputChange('categoryId', value)}
|
||||
onValueChange={(value) => handleInputChange("categoryId", value)}
|
||||
>
|
||||
<SelectTrigger className="glass-input text-white h-10">
|
||||
<SelectValue placeholder="Выберите категорию" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="glass-card">
|
||||
<SelectItem value="none">Без категории</SelectItem>
|
||||
{categoriesData?.categories?.map((category: { id: string; name: string }) => (
|
||||
<SelectItem key={category.id} value={category.id} className="text-white">
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
{categoriesData?.categories?.map(
|
||||
(category: { id: string; name: string }) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.id}
|
||||
className="text-white"
|
||||
>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Бренд</Label>
|
||||
<Input
|
||||
value={formData.brand}
|
||||
onChange={(e) => handleInputChange('brand', e.target.value)}
|
||||
onChange={(e) => handleInputChange("brand", e.target.value)}
|
||||
placeholder="Apple"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
@ -358,40 +395,42 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
<Label className="text-white/80 text-sm mb-2 block">Цвет</Label>
|
||||
<Input
|
||||
value={formData.color}
|
||||
onChange={(e) => handleInputChange('color', e.target.value)}
|
||||
onChange={(e) => handleInputChange("color", e.target.value)}
|
||||
placeholder="Синий"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Размер</Label>
|
||||
<Input
|
||||
value={formData.size}
|
||||
onChange={(e) => handleInputChange('size', e.target.value)}
|
||||
onChange={(e) => handleInputChange("size", e.target.value)}
|
||||
placeholder="L, XL, 42"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Вес (кг)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
value={formData.weight || ''}
|
||||
onChange={(e) => handleInputChange('weight', parseFloat(e.target.value) || 0)}
|
||||
value={formData.weight || ""}
|
||||
onChange={(e) =>
|
||||
handleInputChange("weight", parseFloat(e.target.value) || 0)
|
||||
}
|
||||
placeholder="0.221"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-white/80 text-sm mb-2 block">Габариты</Label>
|
||||
<Input
|
||||
value={formData.dimensions}
|
||||
onChange={(e) => handleInputChange('dimensions', e.target.value)}
|
||||
onChange={(e) => handleInputChange("dimensions", e.target.value)}
|
||||
placeholder="159.9 × 76.7 × 8.25 мм"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
@ -402,7 +441,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
<Label className="text-white/80 text-sm mb-2 block">Материал</Label>
|
||||
<Input
|
||||
value={formData.material}
|
||||
onChange={(e) => handleInputChange('material', e.target.value)}
|
||||
onChange={(e) => handleInputChange("material", e.target.value)}
|
||||
placeholder="Титан, стекло"
|
||||
className="glass-input text-white placeholder:text-white/40 h-10"
|
||||
/>
|
||||
@ -412,7 +451,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
{/* Изображения */}
|
||||
<Card className="bg-white/5 backdrop-blur border-white/10 p-4">
|
||||
<h3 className="text-white font-medium mb-4">Изображения товара</h3>
|
||||
|
||||
|
||||
{/* Кнопка загрузки */}
|
||||
<div className="mb-4">
|
||||
<input
|
||||
@ -420,7 +459,9 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
onChange={(e) => e.target.files && handleImageUpload(e.target.files)}
|
||||
onChange={(e) =>
|
||||
e.target.files && handleImageUpload(e.target.files)
|
||||
}
|
||||
className="hidden"
|
||||
/>
|
||||
<Button
|
||||
@ -431,7 +472,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
className="glass-secondary text-white hover:text-white cursor-pointer"
|
||||
>
|
||||
<Upload className="h-4 w-4 mr-2" />
|
||||
{isUploading ? 'Загрузка...' : 'Добавить изображения'}
|
||||
{isUploading ? "Загрузка..." : "Добавить изображения"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -453,14 +494,14 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
height={150}
|
||||
className="w-full aspect-square object-contain rounded-lg bg-white/5"
|
||||
/>
|
||||
|
||||
|
||||
{/* Индикатор главного изображения */}
|
||||
{formData.mainImage === imageUrl && (
|
||||
<div className="absolute top-2 left-2">
|
||||
<Star className="h-5 w-5 text-yellow-400 fill-current" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Кнопки управления */}
|
||||
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1">
|
||||
{formData.mainImage !== imageUrl && (
|
||||
@ -511,9 +552,13 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
|
||||
disabled={loading || isUploading}
|
||||
className="flex-1 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white border-0 shadow-lg shadow-purple-500/25 hover:shadow-purple-500/40 transition-all duration-300"
|
||||
>
|
||||
{loading ? 'Сохранение...' : (product ? 'Сохранить изменения' : 'Создать товар')}
|
||||
{loading
|
||||
? "Сохранение..."
|
||||
: product
|
||||
? "Сохранить изменения"
|
||||
: "Создать товар"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1,79 +1,106 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { ProductForm } from './product-form'
|
||||
import { ProductCard } from './product-card'
|
||||
import { GET_MY_PRODUCTS } from '@/graphql/queries'
|
||||
import { Plus, Search, Package } from 'lucide-react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import React, { useState } from "react";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { useSidebar } from "@/hooks/useSidebar";
|
||||
import { ProductForm } from "./product-form";
|
||||
import { ProductCard } from "./product-card";
|
||||
import { GET_MY_PRODUCTS } from "@/graphql/queries";
|
||||
import { Plus, Search, Package } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
interface Product {
|
||||
id: string
|
||||
name: string
|
||||
article: string
|
||||
description: string
|
||||
price: number
|
||||
quantity: number
|
||||
type: 'PRODUCT' | 'CONSUMABLE'
|
||||
category: { id: string; name: string } | null
|
||||
brand: string
|
||||
color: string
|
||||
size: string
|
||||
weight: number
|
||||
dimensions: string
|
||||
material: string
|
||||
images: string[]
|
||||
mainImage: string
|
||||
isActive: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
type: "PRODUCT" | "CONSUMABLE";
|
||||
category: { id: string; name: string } | null;
|
||||
brand: string;
|
||||
color: string;
|
||||
size: string;
|
||||
weight: number;
|
||||
dimensions: string;
|
||||
material: string;
|
||||
images: string[];
|
||||
mainImage: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function WarehouseDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const { getSidebarMargin } = useSidebar();
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const { data, loading, error, refetch } = useQuery(GET_MY_PRODUCTS, {
|
||||
errorPolicy: 'all'
|
||||
})
|
||||
errorPolicy: "all",
|
||||
});
|
||||
|
||||
const products: Product[] = data?.myProducts || []
|
||||
const products: Product[] = data?.myProducts || [];
|
||||
|
||||
// Отладочное логирование
|
||||
React.useEffect(() => {
|
||||
console.log("🏪 WAREHOUSE DASHBOARD DEBUG:", {
|
||||
loading,
|
||||
error: error?.message,
|
||||
dataReceived: !!data,
|
||||
productsCount: products.length,
|
||||
products: products.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
article: p.article,
|
||||
type: p.type,
|
||||
isActive: p.isActive,
|
||||
createdAt: p.createdAt,
|
||||
})),
|
||||
});
|
||||
}, [data, loading, error, products]);
|
||||
|
||||
// Фильтрация товаров по поисковому запросу
|
||||
const filteredProducts = products.filter(product =>
|
||||
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.article.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.category?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.brand?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
const filteredProducts = products.filter(
|
||||
(product) =>
|
||||
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.article.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.category?.name
|
||||
?.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
product.brand?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const handleCreateProduct = () => {
|
||||
setEditingProduct(null)
|
||||
setIsDialogOpen(true)
|
||||
}
|
||||
setEditingProduct(null);
|
||||
setIsDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleEditProduct = (product: Product) => {
|
||||
setEditingProduct(product)
|
||||
setIsDialogOpen(true)
|
||||
}
|
||||
setEditingProduct(product);
|
||||
setIsDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleProductSaved = () => {
|
||||
setIsDialogOpen(false)
|
||||
setEditingProduct(null)
|
||||
refetch()
|
||||
}
|
||||
setIsDialogOpen(false);
|
||||
setEditingProduct(null);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleProductDeleted = () => {
|
||||
refetch()
|
||||
}
|
||||
refetch();
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
@ -85,11 +112,13 @@ export function WarehouseDashboard() {
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-white mb-2">Ошибка загрузки</h3>
|
||||
<h3 className="text-lg font-medium text-white mb-2">
|
||||
Ошибка загрузки
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm mb-4">
|
||||
{error.message || 'Не удалось загрузить товары'}
|
||||
{error.message || "Не удалось загрузить товары"}
|
||||
</p>
|
||||
<Button
|
||||
<Button
|
||||
onClick={() => refetch()}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
@ -101,45 +130,51 @@ export function WarehouseDashboard() {
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<main
|
||||
className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}
|
||||
>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Заголовок и поиск */}
|
||||
<div className="flex items-center justify-between mb-4 flex-shrink-0">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white mb-1">Склад товаров</h1>
|
||||
<p className="text-white/70 text-sm">Управление ассортиментом вашего склада</p>
|
||||
<h1 className="text-xl font-bold text-white mb-1">Мой склад</h1>
|
||||
<p className="text-white/70 text-sm">
|
||||
Управление товарами и расходниками
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
<Button
|
||||
onClick={handleCreateProduct}
|
||||
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white border-0 shadow-lg shadow-purple-500/25 transition-all duration-300"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Добавить товар
|
||||
Добавить товар/расходник
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="glass-card max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">
|
||||
{editingProduct ? 'Редактировать товар' : 'Добавить новый товар'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ProductForm
|
||||
product={editingProduct}
|
||||
onSave={handleProductSaved}
|
||||
onCancel={() => setIsDialogOpen(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DialogContent className="glass-card max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">
|
||||
{editingProduct
|
||||
? "Редактировать товар/расходник"
|
||||
: "Добавить товар/расходник"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ProductForm
|
||||
product={editingProduct}
|
||||
onSave={handleProductSaved}
|
||||
onCancel={() => setIsDialogOpen(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -171,16 +206,15 @@ export function WarehouseDashboard() {
|
||||
<div className="text-center">
|
||||
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-white mb-2">
|
||||
{searchQuery ? 'Товары не найдены' : 'Склад пуст'}
|
||||
{searchQuery ? "Товары не найдены" : "Склад пуст"}
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm mb-4">
|
||||
{searchQuery
|
||||
? 'Попробуйте изменить критерии поиска'
|
||||
: 'Добавьте ваш первый товар на склад'
|
||||
}
|
||||
{searchQuery
|
||||
? "Попробуйте изменить критерии поиска"
|
||||
: "Добавьте ваш первый товар на склад"}
|
||||
</p>
|
||||
{!searchQuery && (
|
||||
<Button
|
||||
<Button
|
||||
onClick={handleCreateProduct}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
@ -208,5 +242,5 @@ export function WarehouseDashboard() {
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -464,6 +464,7 @@ export const GET_ALL_PRODUCTS = gql`
|
||||
description
|
||||
price
|
||||
quantity
|
||||
type
|
||||
category {
|
||||
id
|
||||
name
|
||||
|
@ -202,10 +202,6 @@ export const resolvers = {
|
||||
JSON: JSONScalar,
|
||||
DateTime: DateTimeScalar,
|
||||
|
||||
Product: {
|
||||
type: (parent: any) => parent.type || "PRODUCT",
|
||||
},
|
||||
|
||||
Query: {
|
||||
me: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
@ -781,11 +777,14 @@ export const resolvers = {
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// TypeScript assertion - мы знаем что organization не null после проверки выше
|
||||
const organization = currentUser.organization;
|
||||
|
||||
// Получаем заказы поставок, созданные этим фулфилмент-центром для себя
|
||||
const fulfillmentSupplyOrders = await prisma.supplyOrder.findMany({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id, // Создали мы
|
||||
fulfillmentCenterId: currentUser.organization.id, // Получатель - мы
|
||||
organizationId: organization.id, // Создали мы
|
||||
fulfillmentCenterId: organization.id, // Получатель - мы
|
||||
status: {
|
||||
in: ["PENDING", "CONFIRMED", "IN_TRANSIT", "DELIVERED"], // Все статусы
|
||||
},
|
||||
@ -832,8 +831,8 @@ export const resolvers = {
|
||||
imageUrl: null,
|
||||
createdAt: order.createdAt,
|
||||
updatedAt: order.updatedAt,
|
||||
organizationId: currentUser.organization.id,
|
||||
organization: currentUser.organization,
|
||||
organizationId: organization.id,
|
||||
organization: organization,
|
||||
shippedQuantity: 0,
|
||||
}))
|
||||
);
|
||||
@ -841,8 +840,8 @@ export const resolvers = {
|
||||
// Логирование для отладки
|
||||
console.log("🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED 🔥🔥🔥");
|
||||
console.log("📊 Расходники фулфилмента:", {
|
||||
organizationId: currentUser.organization.id,
|
||||
organizationType: currentUser.organization.type,
|
||||
organizationId: organization.id,
|
||||
organizationType: organization.type,
|
||||
fulfillmentOrdersCount: fulfillmentSupplyOrders.length,
|
||||
fulfillmentSuppliesCount: fulfillmentSupplies.length,
|
||||
fulfillmentOrders: fulfillmentSupplyOrders.map((o) => ({
|
||||
@ -1035,8 +1034,14 @@ export const resolvers = {
|
||||
});
|
||||
},
|
||||
|
||||
// Мои товары (для поставщиков)
|
||||
// Мои товары и расходники (для поставщиков)
|
||||
myProducts: async (_: unknown, __: unknown, context: Context) => {
|
||||
console.log("🔍 MY_PRODUCTS RESOLVER - ВЫЗВАН:", {
|
||||
hasUser: !!context.user,
|
||||
userId: context.user?.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (!context.user) {
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
@ -1048,19 +1053,30 @@ export const resolvers = {
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
console.log("👤 ПОЛЬЗОВАТЕЛЬ НАЙДЕН:", {
|
||||
userId: currentUser?.id,
|
||||
hasOrganization: !!currentUser?.organization,
|
||||
organizationType: currentUser?.organization?.type,
|
||||
organizationName: currentUser?.organization?.name,
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что это поставщик
|
||||
if (currentUser.organization.type !== "WHOLESALE") {
|
||||
console.log("❌ ДОСТУП ЗАПРЕЩЕН - НЕ ПОСТАВЩИК:", {
|
||||
actualType: currentUser.organization.type,
|
||||
requiredType: "WHOLESALE",
|
||||
});
|
||||
throw new GraphQLError("Товары доступны только для поставщиков");
|
||||
}
|
||||
|
||||
const products = await prisma.product.findMany({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
type: "PRODUCT", // Показываем только товары, исключаем расходники
|
||||
// Показываем и товары, и расходники поставщика
|
||||
},
|
||||
include: {
|
||||
category: true,
|
||||
@ -1070,13 +1086,18 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
console.log("🔥 MY_PRODUCTS RESOLVER DEBUG:", {
|
||||
userId: currentUser.id,
|
||||
organizationId: currentUser.organization.id,
|
||||
organizationType: currentUser.organization.type,
|
||||
organizationName: currentUser.organization.name,
|
||||
totalProducts: products.length,
|
||||
productTypes: products.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
article: p.article,
|
||||
type: p.type,
|
||||
isActive: p.isActive,
|
||||
createdAt: p.createdAt,
|
||||
})),
|
||||
});
|
||||
|
||||
@ -1132,7 +1153,7 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
// Собираем все товары из доставленных заказов
|
||||
const allProducts: any[] = [];
|
||||
const allProducts: unknown[] = [];
|
||||
|
||||
console.log("🔍 Резолвер warehouseProducts (доставленные заказы):", {
|
||||
currentUserId: currentUser.id,
|
||||
@ -1196,12 +1217,19 @@ export const resolvers = {
|
||||
return allProducts;
|
||||
},
|
||||
|
||||
// Все товары всех поставщиков для маркета
|
||||
// Все товары и расходники поставщиков для маркета
|
||||
allProducts: async (
|
||||
_: unknown,
|
||||
args: { search?: string; category?: string },
|
||||
context: Context
|
||||
) => {
|
||||
console.log("🛍️ ALL_PRODUCTS RESOLVER - ВЫЗВАН:", {
|
||||
userId: context.user?.id,
|
||||
search: args.search,
|
||||
category: args.category,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (!context.user) {
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
@ -1210,7 +1238,7 @@ export const resolvers = {
|
||||
|
||||
const where: Record<string, unknown> = {
|
||||
isActive: true, // Показываем только активные товары
|
||||
type: "PRODUCT", // Показываем только товары, исключаем расходники
|
||||
// Показываем и товары, и расходники поставщиков
|
||||
organization: {
|
||||
type: "WHOLESALE", // Только товары поставщиков
|
||||
},
|
||||
@ -1598,21 +1626,6 @@ export const resolvers = {
|
||||
|
||||
return scheduleRecords;
|
||||
},
|
||||
|
||||
// Получение всех категорий товаров
|
||||
categories: async () => {
|
||||
try {
|
||||
const categories = await prisma.category.findMany({
|
||||
orderBy: {
|
||||
name: "asc",
|
||||
},
|
||||
});
|
||||
return categories;
|
||||
} catch (error) {
|
||||
console.error("Ошибка получения категорий:", error);
|
||||
throw new GraphQLError("Не удалось получить категории");
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
@ -3537,8 +3550,6 @@ export const resolvers = {
|
||||
where: { id: args.input.supplyId },
|
||||
data: {
|
||||
currentStock: existingSupply.currentStock - args.input.quantityUsed,
|
||||
usedStock:
|
||||
(existingSupply.usedStock || 0) + args.input.quantityUsed,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
include: { organization: true },
|
||||
@ -3548,7 +3559,6 @@ export const resolvers = {
|
||||
supplyName: updatedSupply.name,
|
||||
quantityUsed: args.input.quantityUsed,
|
||||
remainingStock: updatedSupply.currentStock,
|
||||
totalUsed: updatedSupply.usedStock,
|
||||
description: args.input.description,
|
||||
});
|
||||
|
||||
@ -3723,7 +3733,7 @@ export const resolvers = {
|
||||
|
||||
try {
|
||||
// Определяем начальный статус в зависимости от роли организации
|
||||
let initialStatus = "PENDING";
|
||||
let initialStatus: "PENDING" | "CONFIRMED" = "PENDING";
|
||||
if (organizationRole === "SELLER") {
|
||||
initialStatus = "PENDING"; // Селлер создает заказ, ждет подтверждения поставщика
|
||||
} else if (organizationRole === "FULFILLMENT") {
|
||||
@ -3779,7 +3789,10 @@ export const resolvers = {
|
||||
const suppliesData = args.input.items.map((item) => {
|
||||
const product = products.find((p) => p.id === item.productId)!;
|
||||
const productWithCategory = supplyOrder.items.find(
|
||||
(orderItem) => orderItem.productId === item.productId
|
||||
(orderItem: {
|
||||
productId: string;
|
||||
product: { category?: { name: string } | null };
|
||||
}) => orderItem.productId === item.productId
|
||||
)?.product;
|
||||
|
||||
return {
|
||||
@ -3899,6 +3912,13 @@ export const resolvers = {
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
console.log("🆕 CREATE_PRODUCT RESOLVER - ВЫЗВАН:", {
|
||||
hasUser: !!context.user,
|
||||
userId: context.user?.id,
|
||||
inputData: args.input,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (!context.user) {
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
@ -3935,6 +3955,18 @@ export const resolvers = {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("🛍️ СОЗДАНИЕ ТОВАРА - НАЧАЛО:", {
|
||||
userId: currentUser.id,
|
||||
organizationId: currentUser.organization.id,
|
||||
organizationType: currentUser.organization.type,
|
||||
productData: {
|
||||
name: args.input.name,
|
||||
article: args.input.article,
|
||||
type: args.input.type || "PRODUCT",
|
||||
isActive: args.input.isActive ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
const product = await prisma.product.create({
|
||||
data: {
|
||||
name: args.input.name,
|
||||
@ -3961,6 +3993,16 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ ТОВАР УСПЕШНО СОЗДАН:", {
|
||||
productId: product.id,
|
||||
name: product.name,
|
||||
article: product.article,
|
||||
type: product.type,
|
||||
isActive: product.isActive,
|
||||
organizationId: product.organizationId,
|
||||
createdAt: product.createdAt,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Товар успешно создан",
|
||||
@ -5494,6 +5536,7 @@ export const resolvers = {
|
||||
},
|
||||
|
||||
Product: {
|
||||
type: (parent: { type?: string | null }) => parent.type || "PRODUCT",
|
||||
images: (parent: { images: unknown }) => {
|
||||
// Если images это строка JSON, парсим её в массив
|
||||
if (typeof parent.images === "string") {
|
||||
|
Reference in New Issue
Block a user