diff --git a/diagnostic-script.js b/diagnostic-script.js
new file mode 100644
index 0000000..575bb70
--- /dev/null
+++ b/diagnostic-script.js
@@ -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();
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx
index ddab942..f89415e 100644
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx
@@ -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);
+ }}
>
diff --git a/src/components/warehouse/product-form.tsx b/src/components/warehouse/product-form.tsx
index 9a1611d..02a69d5 100644
--- a/src/components/warehouse/product-form.tsx
+++ b/src/components/warehouse/product-form.tsx
@@ -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>(new Set())
- const fileInputRef = useRef(null)
+ const [isUploading] = useState(false);
+ const [uploadingImages, setUploadingImages] = useState>(
+ new Set()
+ );
+ const fileInputRef = useRef(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()
- const startIndex = formData.images.length
+ const newUploadingIndexes = new Set();
+ 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 (
-
+
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) {
-
+
-
+
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) {
Категоризация
-
+
-
+
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) {
handleInputChange('color', e.target.value)}
+ onChange={(e) => handleInputChange("color", e.target.value)}
placeholder="Синий"
className="glass-input text-white placeholder:text-white/40 h-10"
/>
-
+
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"
/>
-
+
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"
/>
-
+
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) {
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) {
{/* Изображения */}
Изображения товара
-
+
{/* Кнопка загрузки */}
e.target.files && handleImageUpload(e.target.files)}
+ onChange={(e) =>
+ e.target.files && handleImageUpload(e.target.files)
+ }
className="hidden"
/>
@@ -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 && (
)}
-
+
{/* Кнопки управления */}
{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
+ ? "Сохранить изменения"
+ : "Создать товар"}
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/src/components/warehouse/warehouse-dashboard.tsx b/src/components/warehouse/warehouse-dashboard.tsx
index 5005c59..7dfd067 100644
--- a/src/components/warehouse/warehouse-dashboard.tsx
+++ b/src/components/warehouse/warehouse-dashboard.tsx
@@ -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(null)
- const [searchQuery, setSearchQuery] = useState('')
+ const { getSidebarMargin } = useSidebar();
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const [editingProduct, setEditingProduct] = useState(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() {
-
Ошибка загрузки
+
+ Ошибка загрузки
+
- {error.message || 'Не удалось загрузить товары'}
+ {error.message || "Не удалось загрузить товары"}
-
- )
+ );
}
return (
-
+
{/* Заголовок и поиск */}
-
Склад товаров
-
Управление ассортиментом вашего склада
+
Мой склад
+
+ Управление товарами и расходниками
+
-
+
+
+
+
+ {editingProduct
+ ? "Редактировать товар/расходник"
+ : "Добавить товар/расходник"}
+
+
+ setIsDialogOpen(false)}
+ />
+
+
@@ -171,16 +206,15 @@ export function WarehouseDashboard() {
- {searchQuery ? 'Товары не найдены' : 'Склад пуст'}
+ {searchQuery ? "Товары не найдены" : "Склад пуст"}
- {searchQuery
- ? 'Попробуйте изменить критерии поиска'
- : 'Добавьте ваш первый товар на склад'
- }
+ {searchQuery
+ ? "Попробуйте изменить критерии поиска"
+ : "Добавьте ваш первый товар на склад"}
{!searchQuery && (
-
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts
index 6e1e061..6fd3269 100644
--- a/src/graphql/queries.ts
+++ b/src/graphql/queries.ts
@@ -464,6 +464,7 @@ export const GET_ALL_PRODUCTS = gql`
description
price
quantity
+ type
category {
id
name
diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts
index 636e151..4811824 100644
--- a/src/graphql/resolvers.ts
+++ b/src/graphql/resolvers.ts
@@ -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 = {
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") {