Compare commits
3 Commits
d74b4c1266
...
d964b9b6d4
Author | SHA1 | Date | |
---|---|---|---|
d964b9b6d4 | |||
cad5c9b44a | |||
96a328b3ac |
@ -97,6 +97,8 @@ model Organization {
|
||||
supplies Supply[]
|
||||
users User[]
|
||||
logistics Logistics[]
|
||||
supplyOrders SupplyOrder[]
|
||||
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
|
||||
wildberriesSupplies WildberriesSupply[]
|
||||
|
||||
@@map("organizations")
|
||||
@ -230,6 +232,7 @@ model Product {
|
||||
organizationId String
|
||||
cartItems CartItem[]
|
||||
favorites Favorites[]
|
||||
supplyOrderItems SupplyOrderItem[]
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@ -404,6 +407,14 @@ enum ScheduleStatus {
|
||||
ABSENT
|
||||
}
|
||||
|
||||
enum SupplyOrderStatus {
|
||||
PENDING
|
||||
CONFIRMED
|
||||
IN_TRANSIT
|
||||
DELIVERED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum WildberriesSupplyStatus {
|
||||
DRAFT
|
||||
CREATED
|
||||
@ -426,3 +437,36 @@ model Logistics {
|
||||
|
||||
@@map("logistics")
|
||||
}
|
||||
|
||||
model SupplyOrder {
|
||||
id String @id @default(cuid())
|
||||
partnerId String
|
||||
deliveryDate DateTime
|
||||
status SupplyOrderStatus @default(PENDING)
|
||||
totalAmount Decimal @db.Decimal(12, 2)
|
||||
totalItems Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
items SupplyOrderItem[]
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id])
|
||||
|
||||
@@map("supply_orders")
|
||||
}
|
||||
|
||||
model SupplyOrderItem {
|
||||
id String @id @default(cuid())
|
||||
supplyOrderId String
|
||||
productId String
|
||||
quantity Int
|
||||
price Decimal @db.Decimal(12, 2)
|
||||
totalPrice Decimal @db.Decimal(12, 2)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
supplyOrder SupplyOrder @relation(fields: [supplyOrderId], references: [id], onDelete: Cascade)
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
|
||||
@@unique([supplyOrderId, productId])
|
||||
@@map("supply_order_items")
|
||||
}
|
||||
|
10
src/app/fulfillment-supplies/materials/order/page.tsx
Normal file
10
src/app/fulfillment-supplies/materials/order/page.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { AuthGuard } from "@/components/auth-guard";
|
||||
import { MaterialsOrderForm } from "@/components/fulfillment-supplies/materials-supplies/materials-order-form";
|
||||
|
||||
export default function MaterialsOrderPage() {
|
||||
return (
|
||||
<AuthGuard>
|
||||
<MaterialsOrderForm />
|
||||
</AuthGuard>
|
||||
);
|
||||
}
|
@ -0,0 +1,610 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { useSidebar } from "@/hooks/useSidebar";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Building2,
|
||||
MapPin,
|
||||
Phone,
|
||||
Mail,
|
||||
Star,
|
||||
Search,
|
||||
Calendar,
|
||||
Package,
|
||||
Plus,
|
||||
Minus,
|
||||
ShoppingCart,
|
||||
} from "lucide-react";
|
||||
import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from "@/graphql/queries";
|
||||
import { CREATE_SUPPLY_ORDER } from "@/graphql/mutations";
|
||||
import { OrganizationAvatar } from "@/components/market/organization-avatar";
|
||||
import { toast } from "sonner";
|
||||
import Image from "next/image";
|
||||
|
||||
interface Partner {
|
||||
id: string;
|
||||
inn: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: "FULFILLMENT" | "SELLER" | "LOGIST" | "WHOLESALE";
|
||||
address?: string;
|
||||
phones?: Array<{ value: string }>;
|
||||
emails?: Array<{ value: string }>;
|
||||
users?: Array<{ id: string; avatar?: string; managerName?: string }>;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
category?: { id: string; name: string };
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
weight?: number;
|
||||
dimensions?: string;
|
||||
material?: string;
|
||||
images: string[];
|
||||
mainImage?: string;
|
||||
isActive: boolean;
|
||||
organization: {
|
||||
id: string;
|
||||
inn: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SelectedProduct extends Product {
|
||||
selectedQuantity: number;
|
||||
}
|
||||
|
||||
export function MaterialsOrderForm() {
|
||||
const router = useRouter();
|
||||
const { getSidebarMargin } = useSidebar();
|
||||
const [selectedPartner, setSelectedPartner] = useState<Partner | null>(null);
|
||||
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>(
|
||||
[]
|
||||
);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [deliveryDate, setDeliveryDate] = useState("");
|
||||
|
||||
// Загружаем контрагентов-оптовиков
|
||||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(
|
||||
GET_MY_COUNTERPARTIES
|
||||
);
|
||||
|
||||
// Загружаем товары для выбранного партнера
|
||||
const { data: productsData, loading: productsLoading } = useQuery(
|
||||
GET_ALL_PRODUCTS,
|
||||
{
|
||||
skip: !selectedPartner,
|
||||
variables: { search: null, category: null },
|
||||
}
|
||||
);
|
||||
|
||||
// Мутация для создания заказа поставки
|
||||
const [createSupplyOrder, { loading: isCreatingOrder }] = useMutation(CREATE_SUPPLY_ORDER);
|
||||
|
||||
// Фильтруем только оптовиков из партнеров
|
||||
const wholesalePartners = (counterpartiesData?.myCounterparties || []).filter(
|
||||
(org: Partner) => org.type === "WHOLESALE"
|
||||
);
|
||||
|
||||
// Фильтруем партнеров по поисковому запросу
|
||||
const filteredPartners = wholesalePartners.filter(
|
||||
(partner: Partner) =>
|
||||
partner.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
partner.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
partner.inn?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
// Фильтруем товары по выбранному партнеру
|
||||
const partnerProducts = selectedPartner
|
||||
? (productsData?.allProducts || []).filter(
|
||||
(product: Product) => product.organization.id === selectedPartner.id
|
||||
)
|
||||
: [];
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const updateProductQuantity = (productId: string, quantity: number) => {
|
||||
const product = partnerProducts.find((p: Product) => p.id === productId);
|
||||
if (!product) return;
|
||||
|
||||
setSelectedProducts((prev) => {
|
||||
const existing = prev.find((p) => p.id === productId);
|
||||
|
||||
if (quantity === 0) {
|
||||
return prev.filter((p) => p.id !== productId);
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
return prev.map((p) =>
|
||||
p.id === productId ? { ...p, selectedQuantity: quantity } : p
|
||||
);
|
||||
} else {
|
||||
return [...prev, { ...product, selectedQuantity: quantity }];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getSelectedQuantity = (productId: string): number => {
|
||||
const selected = selectedProducts.find((p) => p.id === productId);
|
||||
return selected ? selected.selectedQuantity : 0;
|
||||
};
|
||||
|
||||
const getTotalAmount = () => {
|
||||
return selectedProducts.reduce(
|
||||
(sum, product) => sum + product.price * product.selectedQuantity,
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
const getTotalItems = () => {
|
||||
return selectedProducts.reduce(
|
||||
(sum, product) => sum + product.selectedQuantity,
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
const handleCreateOrder = async () => {
|
||||
if (!selectedPartner || selectedProducts.length === 0 || !deliveryDate) {
|
||||
toast.error("Заполните все обязательные поля");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await createSupplyOrder({
|
||||
variables: {
|
||||
input: {
|
||||
partnerId: selectedPartner.id,
|
||||
deliveryDate: deliveryDate,
|
||||
items: selectedProducts.map(product => ({
|
||||
productId: product.id,
|
||||
quantity: product.selectedQuantity
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (result.data?.createSupplyOrder?.success) {
|
||||
toast.success("Заказ поставки создан успешно!");
|
||||
router.push("/fulfillment-supplies");
|
||||
} else {
|
||||
toast.error(result.data?.createSupplyOrder?.message || "Ошибка при создании заказа");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating supply order:", error);
|
||||
toast.error("Ошибка при создании заказа поставки");
|
||||
}
|
||||
};
|
||||
|
||||
const renderStars = (rating: number = 4.5) => {
|
||||
return Array.from({ length: 5 }, (_, i) => (
|
||||
<Star
|
||||
key={i}
|
||||
className={`h-3 w-3 ${
|
||||
i < Math.floor(rating)
|
||||
? "text-yellow-400 fill-current"
|
||||
: "text-gray-400"
|
||||
}`}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
// Если выбран партнер и есть товары, показываем товары
|
||||
if (selectedPartner && partnerProducts.length > 0) {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<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-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setSelectedPartner(null)}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Назад к партнерам
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">
|
||||
Товары партнера
|
||||
</h1>
|
||||
<p className="text-white/60">
|
||||
{selectedPartner.name || selectedPartner.fullName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => router.push("/fulfillment-supplies")}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Список товаров */}
|
||||
<div className="lg:col-span-2 overflow-hidden">
|
||||
<Card className="glass-card h-full overflow-hidden">
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">
|
||||
Доступные товары
|
||||
</h3>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{partnerProducts.map((product: Product) => {
|
||||
const selectedQuantity = getSelectedQuantity(
|
||||
product.id
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={product.id}
|
||||
className="glass-secondary p-4"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{/* Изображение товара */}
|
||||
{product.mainImage && (
|
||||
<div className="relative h-32 w-full bg-white/5 rounded overflow-hidden">
|
||||
<Image
|
||||
src={product.mainImage}
|
||||
alt={product.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Информация о товаре */}
|
||||
<div>
|
||||
<h4 className="text-white font-medium text-sm">
|
||||
{product.name}
|
||||
</h4>
|
||||
<p className="text-white/60 text-xs">
|
||||
Артикул: {product.article}
|
||||
</p>
|
||||
{product.description && (
|
||||
<p className="text-white/60 text-xs mt-1 line-clamp-2">
|
||||
{product.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Цена и наличие */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-white font-bold">
|
||||
{formatCurrency(product.price)}
|
||||
</div>
|
||||
<div className="text-white/60 text-xs">
|
||||
В наличии: {product.quantity}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Выбор количества */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
updateProductQuantity(
|
||||
product.id,
|
||||
Math.max(0, selectedQuantity - 1)
|
||||
)
|
||||
}
|
||||
disabled={selectedQuantity === 0}
|
||||
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
<Minus className="h-4 w-4" />
|
||||
</Button>
|
||||
<Input
|
||||
type="number"
|
||||
value={selectedQuantity}
|
||||
onChange={(e) => {
|
||||
const value = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
product.quantity,
|
||||
parseInt(e.target.value) || 0
|
||||
)
|
||||
);
|
||||
updateProductQuantity(product.id, value);
|
||||
}}
|
||||
className="h-8 w-16 text-center bg-white/10 border-white/20 text-white"
|
||||
min={0}
|
||||
max={product.quantity}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
updateProductQuantity(
|
||||
product.id,
|
||||
Math.min(
|
||||
product.quantity,
|
||||
selectedQuantity + 1
|
||||
)
|
||||
)
|
||||
}
|
||||
disabled={
|
||||
selectedQuantity >= product.quantity
|
||||
}
|
||||
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Сводка заказа */}
|
||||
<div className="overflow-hidden">
|
||||
<Card className="glass-card h-full overflow-hidden">
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">
|
||||
Сводка заказа
|
||||
</h3>
|
||||
|
||||
{/* Дата поставки */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-white/80 text-sm mb-2">
|
||||
<Calendar className="h-4 w-4 inline mr-2" />
|
||||
Дата поставки
|
||||
</label>
|
||||
<Input
|
||||
type="date"
|
||||
value={deliveryDate}
|
||||
onChange={(e) => setDeliveryDate(e.target.value)}
|
||||
className="bg-white/10 border-white/20 text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Выбранные товары */}
|
||||
<div className="flex-1 overflow-y-auto mb-4">
|
||||
{selectedProducts.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Package className="h-12 w-12 text-white/20 mx-auto mb-2" />
|
||||
<p className="text-white/60 text-sm">
|
||||
Товары не выбраны
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{selectedProducts.map((product) => (
|
||||
<Card
|
||||
key={product.id}
|
||||
className="glass-secondary p-3"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="text-white text-sm font-medium">
|
||||
{product.name}
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-white/60">
|
||||
<span>
|
||||
{product.selectedQuantity} шт ×{" "}
|
||||
{formatCurrency(product.price)}
|
||||
</span>
|
||||
<span className="text-white font-medium">
|
||||
{formatCurrency(
|
||||
product.price * product.selectedQuantity
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Итого */}
|
||||
<div className="border-t border-white/20 pt-4 space-y-2">
|
||||
<div className="flex justify-between text-white/80">
|
||||
<span>Товаров:</span>
|
||||
<span>{getTotalItems()} шт</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-white font-bold text-lg">
|
||||
<span>Итого:</span>
|
||||
<span>{formatCurrency(getTotalAmount())}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Кнопка создания заказа */}
|
||||
<Button
|
||||
onClick={handleCreateOrder}
|
||||
disabled={selectedProducts.length === 0 || !deliveryDate || isCreatingOrder}
|
||||
className="w-full mt-4 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white"
|
||||
>
|
||||
<ShoppingCart className="h-4 w-4 mr-2" />
|
||||
{isCreatingOrder ? "Создание заказа..." : "Создать заказ поставки"}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</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`}
|
||||
>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Заголовок */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => router.push("/fulfillment-supplies")}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />К поставкам
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">
|
||||
Заказ расходников
|
||||
</h1>
|
||||
<p className="text-white/60">
|
||||
Выберите партнера-оптовика для заказа расходников
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Поиск */}
|
||||
<div className="mb-6">
|
||||
<div className="relative max-w-md">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-white/40" />
|
||||
<Input
|
||||
placeholder="Поиск партнеров..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 bg-white/10 border-white/20 text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список партнеров */}
|
||||
<Card className="glass-card flex-1 overflow-hidden">
|
||||
<div className="p-6 h-full flex flex-col">
|
||||
{counterpartiesLoading ? (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-white/60">Загрузка партнеров...</div>
|
||||
</div>
|
||||
) : filteredPartners.length === 0 ? (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Building2 className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
||||
<p className="text-white/60">
|
||||
{wholesalePartners.length === 0
|
||||
? "У вас пока нет партнеров-оптовиков"
|
||||
: "Партнеры не найдены"}
|
||||
</p>
|
||||
<p className="text-white/40 text-sm mt-2">
|
||||
Добавьте партнеров в разделе "Партнеры"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{filteredPartners.map((partner: Partner) => (
|
||||
<Card
|
||||
key={partner.id}
|
||||
className="glass-secondary p-4 cursor-pointer transition-all hover:bg-white/15 hover:border-white/30 hover:scale-105"
|
||||
onClick={() => setSelectedPartner(partner)}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{/* Заголовок карточки */}
|
||||
<div className="flex items-start space-x-3">
|
||||
<OrganizationAvatar
|
||||
organization={partner}
|
||||
size="sm"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-semibold text-sm mb-1 truncate">
|
||||
{partner.name || partner.fullName}
|
||||
</h3>
|
||||
<div className="flex items-center space-x-1 mb-2">
|
||||
{renderStars()}
|
||||
<span className="text-white/60 text-xs ml-1">
|
||||
4.5
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация */}
|
||||
<div className="space-y-1">
|
||||
{partner.address && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<MapPin className="h-3 w-3 text-gray-400" />
|
||||
<span className="text-white/80 text-xs truncate">
|
||||
{partner.address}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{partner.phones && partner.phones.length > 0 && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Phone className="h-3 w-3 text-gray-400" />
|
||||
<span className="text-white/80 text-xs">
|
||||
{partner.phones[0].value}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{partner.emails && partner.emails.length > 0 && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Mail className="h-3 w-3 text-gray-400" />
|
||||
<span className="text-white/80 text-xs truncate">
|
||||
{partner.emails[0].value}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ИНН */}
|
||||
<div className="pt-2 border-t border-white/10">
|
||||
<p className="text-white/60 text-xs">
|
||||
ИНН: {partner.inn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -175,6 +175,7 @@ export function MaterialsSuppliesTab() {
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => window.location.href = '/fulfillment-supplies/materials/order'}
|
||||
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white text-xs"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
|
@ -634,6 +634,54 @@ export const DELETE_SUPPLY = gql`
|
||||
}
|
||||
`
|
||||
|
||||
// Мутация для заказа поставки расходников
|
||||
export const CREATE_SUPPLY_ORDER = gql`
|
||||
mutation CreateSupplyOrder($input: SupplyOrderInput!) {
|
||||
createSupplyOrder(input: $input) {
|
||||
success
|
||||
message
|
||||
order {
|
||||
id
|
||||
partnerId
|
||||
deliveryDate
|
||||
status
|
||||
totalAmount
|
||||
totalItems
|
||||
createdAt
|
||||
partner {
|
||||
id
|
||||
inn
|
||||
name
|
||||
fullName
|
||||
address
|
||||
phones
|
||||
emails
|
||||
}
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
price
|
||||
totalPrice
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
description
|
||||
price
|
||||
quantity
|
||||
images
|
||||
mainImage
|
||||
category {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Мутации для логистики
|
||||
export const CREATE_LOGISTICS = gql`
|
||||
mutation CreateLogistics($input: LogisticsInput!) {
|
||||
|
@ -1,190 +1,198 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { GraphQLScalarType, Kind } from 'graphql'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { SmsService } from '@/services/sms-service'
|
||||
import { DaDataService } from '@/services/dadata-service'
|
||||
import { MarketplaceService } from '@/services/marketplace-service'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import jwt from "jsonwebtoken";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { GraphQLError } from "graphql";
|
||||
import { GraphQLScalarType, Kind } from "graphql";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { SmsService } from "@/services/sms-service";
|
||||
import { DaDataService } from "@/services/dadata-service";
|
||||
import { MarketplaceService } from "@/services/marketplace-service";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
// Сервисы
|
||||
const smsService = new SmsService()
|
||||
const dadataService = new DaDataService()
|
||||
const marketplaceService = new MarketplaceService()
|
||||
const smsService = new SmsService();
|
||||
const dadataService = new DaDataService();
|
||||
const marketplaceService = new MarketplaceService();
|
||||
|
||||
// Интерфейсы для типизации
|
||||
interface Context {
|
||||
user?: {
|
||||
id: string
|
||||
phone: string
|
||||
}
|
||||
id: string;
|
||||
phone: string;
|
||||
};
|
||||
admin?: {
|
||||
id: string
|
||||
username: string
|
||||
}
|
||||
id: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateEmployeeInput {
|
||||
firstName: string
|
||||
lastName: string
|
||||
middleName?: string
|
||||
birthDate?: string
|
||||
avatar?: string
|
||||
passportPhoto?: string
|
||||
passportSeries?: string
|
||||
passportNumber?: string
|
||||
passportIssued?: string
|
||||
passportDate?: string
|
||||
address?: string
|
||||
position: string
|
||||
department?: string
|
||||
hireDate: string
|
||||
salary?: number
|
||||
phone: string
|
||||
email?: string
|
||||
telegram?: string
|
||||
whatsapp?: string
|
||||
emergencyContact?: string
|
||||
emergencyPhone?: string
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
middleName?: string;
|
||||
birthDate?: string;
|
||||
avatar?: string;
|
||||
passportPhoto?: string;
|
||||
passportSeries?: string;
|
||||
passportNumber?: string;
|
||||
passportIssued?: string;
|
||||
passportDate?: string;
|
||||
address?: string;
|
||||
position: string;
|
||||
department?: string;
|
||||
hireDate: string;
|
||||
salary?: number;
|
||||
phone: string;
|
||||
email?: string;
|
||||
telegram?: string;
|
||||
whatsapp?: string;
|
||||
emergencyContact?: string;
|
||||
emergencyPhone?: string;
|
||||
}
|
||||
|
||||
interface UpdateEmployeeInput {
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
middleName?: string
|
||||
birthDate?: string
|
||||
avatar?: string
|
||||
passportPhoto?: string
|
||||
passportSeries?: string
|
||||
passportNumber?: string
|
||||
passportIssued?: string
|
||||
passportDate?: string
|
||||
address?: string
|
||||
position?: string
|
||||
department?: string
|
||||
hireDate?: string
|
||||
salary?: number
|
||||
status?: 'ACTIVE' | 'VACATION' | 'SICK' | 'FIRED'
|
||||
phone?: string
|
||||
email?: string
|
||||
telegram?: string
|
||||
whatsapp?: string
|
||||
emergencyContact?: string
|
||||
emergencyPhone?: string
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
middleName?: string;
|
||||
birthDate?: string;
|
||||
avatar?: string;
|
||||
passportPhoto?: string;
|
||||
passportSeries?: string;
|
||||
passportNumber?: string;
|
||||
passportIssued?: string;
|
||||
passportDate?: string;
|
||||
address?: string;
|
||||
position?: string;
|
||||
department?: string;
|
||||
hireDate?: string;
|
||||
salary?: number;
|
||||
status?: "ACTIVE" | "VACATION" | "SICK" | "FIRED";
|
||||
phone?: string;
|
||||
email?: string;
|
||||
telegram?: string;
|
||||
whatsapp?: string;
|
||||
emergencyContact?: string;
|
||||
emergencyPhone?: string;
|
||||
}
|
||||
|
||||
interface UpdateScheduleInput {
|
||||
employeeId: string
|
||||
date: string
|
||||
status: 'WORK' | 'WEEKEND' | 'VACATION' | 'SICK' | 'ABSENT'
|
||||
hoursWorked?: number
|
||||
notes?: string
|
||||
employeeId: string;
|
||||
date: string;
|
||||
status: "WORK" | "WEEKEND" | "VACATION" | "SICK" | "ABSENT";
|
||||
hoursWorked?: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
interface AuthTokenPayload {
|
||||
userId: string
|
||||
phone: string
|
||||
userId: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
// JWT утилиты
|
||||
const generateToken = (payload: AuthTokenPayload): string => {
|
||||
return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '30d' })
|
||||
}
|
||||
return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: "30d" });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const verifyToken = (token: string): AuthTokenPayload => {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (error) {
|
||||
throw new GraphQLError('Недействительный токен', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Недействительный токен", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Скалярный тип для JSON
|
||||
const JSONScalar = new GraphQLScalarType({
|
||||
name: 'JSON',
|
||||
description: 'JSON custom scalar type',
|
||||
name: "JSON",
|
||||
description: "JSON custom scalar type",
|
||||
serialize(value: unknown) {
|
||||
return value // значение отправляется клиенту
|
||||
return value; // значение отправляется клиенту
|
||||
},
|
||||
parseValue(value: unknown) {
|
||||
return value // значение получено от клиента
|
||||
return value; // значение получено от клиента
|
||||
},
|
||||
parseLiteral(ast) {
|
||||
switch (ast.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
return ast.value
|
||||
return ast.value;
|
||||
case Kind.INT:
|
||||
case Kind.FLOAT:
|
||||
return parseFloat(ast.value)
|
||||
return parseFloat(ast.value);
|
||||
case Kind.OBJECT: {
|
||||
const value = Object.create(null)
|
||||
ast.fields.forEach(field => {
|
||||
value[field.name.value] = parseLiteral(field.value)
|
||||
})
|
||||
return value
|
||||
const value = Object.create(null);
|
||||
ast.fields.forEach((field) => {
|
||||
value[field.name.value] = parseLiteral(field.value);
|
||||
});
|
||||
return value;
|
||||
}
|
||||
case Kind.LIST:
|
||||
return ast.values.map(parseLiteral)
|
||||
return ast.values.map(parseLiteral);
|
||||
default:
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
// Скалярный тип для DateTime
|
||||
const DateTimeScalar = new GraphQLScalarType({
|
||||
name: 'DateTime',
|
||||
description: 'DateTime custom scalar type',
|
||||
name: "DateTime",
|
||||
description: "DateTime custom scalar type",
|
||||
serialize(value: unknown) {
|
||||
if (value instanceof Date) {
|
||||
return value.toISOString() // значение отправляется клиенту как ISO строка
|
||||
return value.toISOString(); // значение отправляется клиенту как ISO строка
|
||||
}
|
||||
return value
|
||||
return value;
|
||||
},
|
||||
parseValue(value: unknown) {
|
||||
if (typeof value === 'string') {
|
||||
return new Date(value) // значение получено от клиента, парсим как дату
|
||||
if (typeof value === "string") {
|
||||
return new Date(value); // значение получено от клиента, парсим как дату
|
||||
}
|
||||
return value
|
||||
return value;
|
||||
},
|
||||
parseLiteral(ast) {
|
||||
if (ast.kind === Kind.STRING) {
|
||||
return new Date(ast.value) // AST значение как дата
|
||||
return new Date(ast.value); // AST значение как дата
|
||||
}
|
||||
return null
|
||||
}
|
||||
})
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
function parseLiteral(ast: unknown): unknown {
|
||||
const astNode = ast as { kind: string; value?: unknown; fields?: unknown[]; values?: unknown[] }
|
||||
|
||||
const astNode = ast as {
|
||||
kind: string;
|
||||
value?: unknown;
|
||||
fields?: unknown[];
|
||||
values?: unknown[];
|
||||
};
|
||||
|
||||
switch (astNode.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
return astNode.value
|
||||
return astNode.value;
|
||||
case Kind.INT:
|
||||
case Kind.FLOAT:
|
||||
return parseFloat(astNode.value as string)
|
||||
return parseFloat(astNode.value as string);
|
||||
case Kind.OBJECT: {
|
||||
const value = Object.create(null)
|
||||
const value = Object.create(null);
|
||||
if (astNode.fields) {
|
||||
astNode.fields.forEach((field: unknown) => {
|
||||
const fieldNode = field as { name: { value: string }; value: unknown }
|
||||
value[fieldNode.name.value] = parseLiteral(fieldNode.value)
|
||||
})
|
||||
const fieldNode = field as {
|
||||
name: { value: string };
|
||||
value: unknown;
|
||||
};
|
||||
value[fieldNode.name.value] = parseLiteral(fieldNode.value);
|
||||
});
|
||||
}
|
||||
return value
|
||||
return value;
|
||||
}
|
||||
case Kind.LIST:
|
||||
return (ast as { values: unknown[] }).values.map(parseLiteral)
|
||||
return (ast as { values: unknown[] }).values.map(parseLiteral);
|
||||
default:
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,9 +203,9 @@ export const resolvers = {
|
||||
Query: {
|
||||
me: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
return await prisma.user.findUnique({
|
||||
@ -205,497 +213,546 @@ export const resolvers = {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
organization: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
organization: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const organization = await prisma.organization.findUnique({
|
||||
where: { id: args.id },
|
||||
include: {
|
||||
apiKeys: true,
|
||||
users: true
|
||||
}
|
||||
})
|
||||
users: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
throw new GraphQLError('Организация не найдена')
|
||||
throw new GraphQLError("Организация не найдена");
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь имеет доступ к этой организации
|
||||
const hasAccess = organization.users.some(user => user.id === context.user!.id)
|
||||
const hasAccess = organization.users.some(
|
||||
(user) => user.id === context.user!.id
|
||||
);
|
||||
if (!hasAccess) {
|
||||
throw new GraphQLError('Нет доступа к этой организации', {
|
||||
extensions: { code: 'FORBIDDEN' }
|
||||
})
|
||||
throw new GraphQLError("Нет доступа к этой организации", {
|
||||
extensions: { code: "FORBIDDEN" },
|
||||
});
|
||||
}
|
||||
|
||||
return organization
|
||||
return organization;
|
||||
},
|
||||
|
||||
// Поиск организаций по типу для добавления в контрагенты
|
||||
searchOrganizations: async (_: unknown, args: { type?: string; search?: string }, context: Context) => {
|
||||
searchOrganizations: async (
|
||||
_: unknown,
|
||||
args: { type?: string; search?: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
// Получаем текущую организацию пользователя
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Получаем уже существующих контрагентов для добавления флага
|
||||
const existingCounterparties = await prisma.counterparty.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
select: { counterpartyId: true }
|
||||
})
|
||||
select: { counterpartyId: true },
|
||||
});
|
||||
|
||||
const existingCounterpartyIds = existingCounterparties.map(c => c.counterpartyId)
|
||||
const existingCounterpartyIds = existingCounterparties.map(
|
||||
(c) => c.counterpartyId
|
||||
);
|
||||
|
||||
// Получаем исходящие заявки для добавления флага hasOutgoingRequest
|
||||
const outgoingRequests = await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
senderId: currentUser.organization.id,
|
||||
status: 'PENDING'
|
||||
status: "PENDING",
|
||||
},
|
||||
select: { receiverId: true }
|
||||
})
|
||||
select: { receiverId: true },
|
||||
});
|
||||
|
||||
const outgoingRequestIds = outgoingRequests.map(r => r.receiverId)
|
||||
const outgoingRequestIds = outgoingRequests.map((r) => r.receiverId);
|
||||
|
||||
// Получаем входящие заявки для добавления флага hasIncomingRequest
|
||||
const incomingRequests = await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
receiverId: currentUser.organization.id,
|
||||
status: 'PENDING'
|
||||
status: "PENDING",
|
||||
},
|
||||
select: { senderId: true }
|
||||
})
|
||||
select: { senderId: true },
|
||||
});
|
||||
|
||||
const incomingRequestIds = incomingRequests.map(r => r.senderId)
|
||||
const incomingRequestIds = incomingRequests.map((r) => r.senderId);
|
||||
|
||||
const where: Record<string, unknown> = {
|
||||
// Больше не исключаем собственную организацию
|
||||
}
|
||||
};
|
||||
|
||||
if (args.type) {
|
||||
where.type = args.type
|
||||
where.type = args.type;
|
||||
}
|
||||
|
||||
if (args.search) {
|
||||
where.OR = [
|
||||
{ name: { contains: args.search, mode: 'insensitive' } },
|
||||
{ fullName: { contains: args.search, mode: 'insensitive' } },
|
||||
{ inn: { contains: args.search } }
|
||||
]
|
||||
{ name: { contains: args.search, mode: "insensitive" } },
|
||||
{ fullName: { contains: args.search, mode: "insensitive" } },
|
||||
{ inn: { contains: args.search } },
|
||||
];
|
||||
}
|
||||
|
||||
const organizations = await prisma.organization.findMany({
|
||||
where,
|
||||
take: 50, // Ограничиваем количество результатов
|
||||
orderBy: { createdAt: 'desc' },
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: {
|
||||
users: true,
|
||||
apiKeys: true
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Добавляем флаги isCounterparty, isCurrentUser, hasOutgoingRequest и hasIncomingRequest к каждой организации
|
||||
return organizations.map(org => ({
|
||||
return organizations.map((org) => ({
|
||||
...org,
|
||||
isCounterparty: existingCounterpartyIds.includes(org.id),
|
||||
isCurrentUser: org.id === currentUser.organization?.id,
|
||||
hasOutgoingRequest: outgoingRequestIds.includes(org.id),
|
||||
hasIncomingRequest: incomingRequestIds.includes(org.id)
|
||||
}))
|
||||
hasIncomingRequest: incomingRequestIds.includes(org.id),
|
||||
}));
|
||||
},
|
||||
|
||||
// Мои контрагенты
|
||||
myCounterparties: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
const counterparties = await prisma.counterparty.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
include: {
|
||||
counterparty: {
|
||||
include: {
|
||||
users: true,
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return counterparties.map(c => c.counterparty)
|
||||
return counterparties.map((c) => c.counterparty);
|
||||
},
|
||||
|
||||
// Входящие заявки
|
||||
incomingRequests: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
return await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
receiverId: currentUser.organization.id,
|
||||
status: 'PENDING'
|
||||
status: "PENDING",
|
||||
},
|
||||
include: {
|
||||
sender: {
|
||||
include: {
|
||||
users: true,
|
||||
apiKeys: true
|
||||
}
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
receiver: {
|
||||
include: {
|
||||
users: true,
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Исходящие заявки
|
||||
outgoingRequests: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
return await prisma.counterpartyRequest.findMany({
|
||||
where: {
|
||||
where: {
|
||||
senderId: currentUser.organization.id,
|
||||
status: { in: ['PENDING', 'REJECTED'] }
|
||||
status: { in: ["PENDING", "REJECTED"] },
|
||||
},
|
||||
include: {
|
||||
sender: {
|
||||
include: {
|
||||
users: true,
|
||||
apiKeys: true
|
||||
}
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
receiver: {
|
||||
include: {
|
||||
users: true,
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Сообщения с контрагентом
|
||||
messages: async (_: unknown, args: { counterpartyId: string; limit?: number; offset?: number }, context: Context) => {
|
||||
messages: async (
|
||||
_: unknown,
|
||||
args: { counterpartyId: string; limit?: number; offset?: number },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
const limit = args.limit || 50
|
||||
const offset = args.offset || 0
|
||||
const limit = args.limit || 50;
|
||||
const offset = args.offset || 0;
|
||||
|
||||
const messages = await prisma.message.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.counterpartyId
|
||||
receiverOrganizationId: args.counterpartyId,
|
||||
},
|
||||
{
|
||||
senderOrganizationId: args.counterpartyId,
|
||||
receiverOrganizationId: currentUser.organization.id
|
||||
}
|
||||
]
|
||||
receiverOrganizationId: currentUser.organization.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
orderBy: { createdAt: "asc" },
|
||||
take: limit,
|
||||
skip: offset
|
||||
})
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
return messages
|
||||
return messages;
|
||||
},
|
||||
|
||||
// Список чатов (последние сообщения с каждым контрагентом)
|
||||
conversations: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// TODO: Здесь будет логика получения списка чатов
|
||||
// Пока возвращаем пустой массив, так как таблица сообщений еще не создана
|
||||
return []
|
||||
return [];
|
||||
},
|
||||
|
||||
// Мои услуги
|
||||
myServices: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что это фулфилмент центр
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Услуги доступны только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Услуги доступны только для фулфилмент центров");
|
||||
}
|
||||
|
||||
return await prisma.service.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: { organization: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Мои расходники
|
||||
mySupplies: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
return await prisma.supply.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: { organization: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Логистика организации
|
||||
myLogistics: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
return await prisma.logistics.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: { organization: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
||||
// Мои товары (для оптовиков)
|
||||
myProducts: async (_: unknown, __: unknown, context: Context) => {
|
||||
// Мои поставки Wildberries
|
||||
myWildberriesSupplies: async (
|
||||
_: unknown,
|
||||
__: unknown,
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
return await prisma.wildberriesSupply.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
organization: true,
|
||||
cards: 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('Товары доступны только для оптовиков')
|
||||
if (currentUser.organization.type !== "WHOLESALE") {
|
||||
throw new GraphQLError("Товары доступны только для оптовиков");
|
||||
}
|
||||
|
||||
return await prisma.product.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true
|
||||
organization: true,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Все товары всех оптовиков для маркета
|
||||
allProducts: async (_: unknown, args: { search?: string; category?: string }, context: Context) => {
|
||||
allProducts: async (
|
||||
_: unknown,
|
||||
args: { search?: string; category?: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const where: Record<string, unknown> = {
|
||||
isActive: true, // Показываем только активные товары
|
||||
organization: {
|
||||
type: 'WHOLESALE' // Только товары оптовиков
|
||||
}
|
||||
}
|
||||
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' } }
|
||||
]
|
||||
{ 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
|
||||
where.categoryId = args.category;
|
||||
}
|
||||
|
||||
return await prisma.product.findMany({
|
||||
where,
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100 // Ограничиваем количество результатов
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 100, // Ограничиваем количество результатов
|
||||
});
|
||||
},
|
||||
|
||||
// Все категории
|
||||
categories: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user && !context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
return await prisma.category.findMany({
|
||||
orderBy: { name: 'asc' }
|
||||
})
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
},
|
||||
|
||||
// Корзина пользователя
|
||||
myCart: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Найти или создать корзину для организации
|
||||
@ -709,21 +766,21 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!cart) {
|
||||
cart = await prisma.cart.create({
|
||||
data: {
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: {
|
||||
items: {
|
||||
@ -733,36 +790,36 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return cart
|
||||
return cart;
|
||||
},
|
||||
|
||||
// Избранные товары пользователя
|
||||
myFavorites: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Получаем избранные товары
|
||||
@ -774,210 +831,228 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return favorites.map(favorite => favorite.product)
|
||||
return favorites.map((favorite) => favorite.product);
|
||||
},
|
||||
|
||||
// Сотрудники организации
|
||||
myEmployees: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
const employees = await prisma.employee.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: {
|
||||
organization: true
|
||||
organization: true,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return employees
|
||||
return employees;
|
||||
},
|
||||
|
||||
// Получение сотрудника по ID
|
||||
employee: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
const employee = await prisma.employee.findFirst({
|
||||
where: {
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return employee
|
||||
return employee;
|
||||
},
|
||||
|
||||
// Получить табель сотрудника за месяц
|
||||
employeeSchedule: async (_: unknown, args: { employeeId: string; year: number; month: number }, context: Context) => {
|
||||
employeeSchedule: async (
|
||||
_: unknown,
|
||||
args: { employeeId: string; year: number; month: number },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
// Проверяем что сотрудник принадлежит организации
|
||||
const employee = await prisma.employee.findFirst({
|
||||
where: {
|
||||
id: args.employeeId,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!employee) {
|
||||
throw new GraphQLError('Сотрудник не найден')
|
||||
throw new GraphQLError("Сотрудник не найден");
|
||||
}
|
||||
|
||||
// Получаем записи табеля за указанный месяц
|
||||
const startDate = new Date(args.year, args.month, 1)
|
||||
const endDate = new Date(args.year, args.month + 1, 0)
|
||||
const startDate = new Date(args.year, args.month, 1);
|
||||
const endDate = new Date(args.year, args.month + 1, 0);
|
||||
|
||||
const scheduleRecords = await prisma.employeeSchedule.findMany({
|
||||
where: {
|
||||
employeeId: args.employeeId,
|
||||
date: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
lte: endDate,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
date: 'asc'
|
||||
}
|
||||
})
|
||||
date: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
return scheduleRecords
|
||||
}
|
||||
return scheduleRecords;
|
||||
},
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
sendSmsCode: async (_: unknown, args: { phone: string }) => {
|
||||
const result = await smsService.sendSmsCode(args.phone)
|
||||
const result = await smsService.sendSmsCode(args.phone);
|
||||
return {
|
||||
success: result.success,
|
||||
message: result.message || 'SMS код отправлен'
|
||||
}
|
||||
message: result.message || "SMS код отправлен",
|
||||
};
|
||||
},
|
||||
|
||||
verifySmsCode: async (_: unknown, args: { phone: string; code: string }) => {
|
||||
const verificationResult = await smsService.verifySmsCode(args.phone, args.code)
|
||||
|
||||
verifySmsCode: async (
|
||||
_: unknown,
|
||||
args: { phone: string; code: string }
|
||||
) => {
|
||||
const verificationResult = await smsService.verifySmsCode(
|
||||
args.phone,
|
||||
args.code
|
||||
);
|
||||
|
||||
if (!verificationResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
message: verificationResult.message || 'Неверный код'
|
||||
}
|
||||
message: verificationResult.message || "Неверный код",
|
||||
};
|
||||
}
|
||||
|
||||
// Найти или создать пользователя
|
||||
const formattedPhone = args.phone.replace(/\D/g, '')
|
||||
const formattedPhone = args.phone.replace(/\D/g, "");
|
||||
let user = await prisma.user.findUnique({
|
||||
where: { phone: formattedPhone },
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
user = await prisma.user.create({
|
||||
data: {
|
||||
phone: formattedPhone
|
||||
phone: formattedPhone,
|
||||
},
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const token = generateToken({
|
||||
userId: user.id,
|
||||
phone: user.phone
|
||||
})
|
||||
phone: user.phone,
|
||||
});
|
||||
|
||||
console.log('verifySmsCode - Generated token:', token ? `${token.substring(0, 20)}...` : 'No token')
|
||||
console.log('verifySmsCode - Full token:', token)
|
||||
console.log('verifySmsCode - User object:', { id: user.id, phone: user.phone })
|
||||
console.log(
|
||||
"verifySmsCode - Generated token:",
|
||||
token ? `${token.substring(0, 20)}...` : "No token"
|
||||
);
|
||||
console.log("verifySmsCode - Full token:", token);
|
||||
console.log("verifySmsCode - User object:", {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
});
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
message: 'Авторизация успешна',
|
||||
message: "Авторизация успешна",
|
||||
token,
|
||||
user
|
||||
}
|
||||
user,
|
||||
};
|
||||
|
||||
console.log('verifySmsCode - Returning result:', {
|
||||
success: result.success,
|
||||
console.log("verifySmsCode - Returning result:", {
|
||||
success: result.success,
|
||||
hasToken: !!result.token,
|
||||
hasUser: !!result.user,
|
||||
message: result.message,
|
||||
tokenPreview: result.token ? `${result.token.substring(0, 20)}...` : 'No token in result'
|
||||
})
|
||||
tokenPreview: result.token
|
||||
? `${result.token.substring(0, 20)}...`
|
||||
: "No token in result",
|
||||
});
|
||||
|
||||
return result
|
||||
return result;
|
||||
},
|
||||
|
||||
verifyInn: async (_: unknown, args: { inn: string }) => {
|
||||
@ -985,72 +1060,80 @@ export const resolvers = {
|
||||
if (!dadataService.validateInn(args.inn)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверный формат ИНН'
|
||||
}
|
||||
message: "Неверный формат ИНН",
|
||||
};
|
||||
}
|
||||
|
||||
// Получаем данные организации из DaData
|
||||
const organizationData = await dadataService.getOrganizationByInn(args.inn)
|
||||
const organizationData = await dadataService.getOrganizationByInn(
|
||||
args.inn
|
||||
);
|
||||
if (!organizationData) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Организация с указанным ИНН не найдена'
|
||||
}
|
||||
message: "Организация с указанным ИНН не найдена",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'ИНН найден',
|
||||
message: "ИНН найден",
|
||||
organization: {
|
||||
name: organizationData.name,
|
||||
fullName: organizationData.fullName,
|
||||
address: organizationData.address,
|
||||
isActive: organizationData.isActive
|
||||
}
|
||||
}
|
||||
isActive: organizationData.isActive,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
registerFulfillmentOrganization: async (
|
||||
_: unknown,
|
||||
args: { input: { phone: string; inn: string; type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE' } },
|
||||
args: {
|
||||
input: {
|
||||
phone: string;
|
||||
inn: string;
|
||||
type: "FULFILLMENT" | "LOGIST" | "WHOLESALE";
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const { inn, type } = args.input
|
||||
const { inn, type } = args.input;
|
||||
|
||||
// Валидируем ИНН
|
||||
if (!dadataService.validateInn(inn)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверный формат ИНН'
|
||||
}
|
||||
message: "Неверный формат ИНН",
|
||||
};
|
||||
}
|
||||
|
||||
// Получаем данные организации из DaData
|
||||
const organizationData = await dadataService.getOrganizationByInn(inn)
|
||||
const organizationData = await dadataService.getOrganizationByInn(inn);
|
||||
if (!organizationData) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Организация с указанным ИНН не найдена'
|
||||
}
|
||||
message: "Организация с указанным ИНН не найдена",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Проверяем, что организация еще не зарегистрирована
|
||||
const existingOrg = await prisma.organization.findUnique({
|
||||
where: { inn: organizationData.inn }
|
||||
})
|
||||
where: { inn: organizationData.inn },
|
||||
});
|
||||
|
||||
if (existingOrg) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Организация с таким ИНН уже зарегистрирована'
|
||||
}
|
||||
message: "Организация с таким ИНН уже зарегистрирована",
|
||||
};
|
||||
}
|
||||
|
||||
// Создаем организацию со всеми данными из DaData
|
||||
@ -1064,41 +1147,45 @@ export const resolvers = {
|
||||
addressFull: organizationData.addressFull,
|
||||
ogrn: organizationData.ogrn,
|
||||
ogrnDate: organizationData.ogrnDate,
|
||||
|
||||
|
||||
// Статус организации
|
||||
status: organizationData.status,
|
||||
actualityDate: organizationData.actualityDate,
|
||||
registrationDate: organizationData.registrationDate,
|
||||
liquidationDate: organizationData.liquidationDate,
|
||||
|
||||
|
||||
// Руководитель
|
||||
managementName: organizationData.managementName,
|
||||
managementPost: organizationData.managementPost,
|
||||
|
||||
|
||||
// ОПФ
|
||||
opfCode: organizationData.opfCode,
|
||||
opfFull: organizationData.opfFull,
|
||||
opfShort: organizationData.opfShort,
|
||||
|
||||
|
||||
// Коды статистики
|
||||
okato: organizationData.okato,
|
||||
oktmo: organizationData.oktmo,
|
||||
okpo: organizationData.okpo,
|
||||
okved: organizationData.okved,
|
||||
|
||||
|
||||
// Контакты
|
||||
phones: organizationData.phones ? JSON.parse(JSON.stringify(organizationData.phones)) : null,
|
||||
emails: organizationData.emails ? JSON.parse(JSON.stringify(organizationData.emails)) : null,
|
||||
|
||||
phones: organizationData.phones
|
||||
? JSON.parse(JSON.stringify(organizationData.phones))
|
||||
: null,
|
||||
emails: organizationData.emails
|
||||
? JSON.parse(JSON.stringify(organizationData.emails))
|
||||
: null,
|
||||
|
||||
// Финансовые данные
|
||||
employeeCount: organizationData.employeeCount,
|
||||
revenue: organizationData.revenue,
|
||||
taxSystem: organizationData.taxSystem,
|
||||
|
||||
|
||||
type: type,
|
||||
dadataData: JSON.parse(JSON.stringify(organizationData.rawData))
|
||||
}
|
||||
})
|
||||
dadataData: JSON.parse(JSON.stringify(organizationData.rawData)),
|
||||
},
|
||||
});
|
||||
|
||||
// Привязываем пользователя к организации
|
||||
const updatedUser = await prisma.user.update({
|
||||
@ -1107,24 +1194,23 @@ export const resolvers = {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Организация успешно зарегистрирована',
|
||||
user: updatedUser
|
||||
}
|
||||
|
||||
message: "Организация успешно зарегистрирована",
|
||||
user: updatedUser,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error registering fulfillment organization:', error)
|
||||
console.error("Error registering fulfillment organization:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при регистрации организации'
|
||||
}
|
||||
message: "Ошибка при регистрации организации",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@ -1132,87 +1218,96 @@ export const resolvers = {
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
phone: string
|
||||
wbApiKey?: string
|
||||
ozonApiKey?: string
|
||||
ozonClientId?: string
|
||||
}
|
||||
phone: string;
|
||||
wbApiKey?: string;
|
||||
ozonApiKey?: string;
|
||||
ozonClientId?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const { wbApiKey, ozonApiKey, ozonClientId } = args.input
|
||||
const { wbApiKey, ozonApiKey, ozonClientId } = args.input;
|
||||
|
||||
if (!wbApiKey && !ozonApiKey) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Необходимо указать хотя бы один API ключ маркетплейса'
|
||||
}
|
||||
message: "Необходимо указать хотя бы один API ключ маркетплейса",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Валидируем API ключи
|
||||
const validationResults = []
|
||||
const validationResults = [];
|
||||
|
||||
if (wbApiKey) {
|
||||
const wbResult = await marketplaceService.validateWildberriesApiKey(wbApiKey)
|
||||
const wbResult = await marketplaceService.validateWildberriesApiKey(
|
||||
wbApiKey
|
||||
);
|
||||
if (!wbResult.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Wildberries: ${wbResult.message}`
|
||||
}
|
||||
message: `Wildberries: ${wbResult.message}`,
|
||||
};
|
||||
}
|
||||
validationResults.push({
|
||||
marketplace: 'WILDBERRIES',
|
||||
marketplace: "WILDBERRIES",
|
||||
apiKey: wbApiKey,
|
||||
data: wbResult.data
|
||||
})
|
||||
data: wbResult.data,
|
||||
});
|
||||
}
|
||||
|
||||
if (ozonApiKey && ozonClientId) {
|
||||
const ozonResult = await marketplaceService.validateOzonApiKey(ozonApiKey, ozonClientId)
|
||||
const ozonResult = await marketplaceService.validateOzonApiKey(
|
||||
ozonApiKey,
|
||||
ozonClientId
|
||||
);
|
||||
if (!ozonResult.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Ozon: ${ozonResult.message}`
|
||||
}
|
||||
message: `Ozon: ${ozonResult.message}`,
|
||||
};
|
||||
}
|
||||
validationResults.push({
|
||||
marketplace: 'OZON',
|
||||
marketplace: "OZON",
|
||||
apiKey: ozonApiKey,
|
||||
data: ozonResult.data
|
||||
})
|
||||
data: ozonResult.data,
|
||||
});
|
||||
}
|
||||
|
||||
// Создаем организацию селлера - используем tradeMark как основное имя
|
||||
const tradeMark = validationResults[0]?.data?.tradeMark
|
||||
const sellerName = validationResults[0]?.data?.sellerName
|
||||
const shopName = tradeMark || sellerName || 'Магазин'
|
||||
|
||||
const tradeMark = validationResults[0]?.data?.tradeMark;
|
||||
const sellerName = validationResults[0]?.data?.sellerName;
|
||||
const shopName = tradeMark || sellerName || "Магазин";
|
||||
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
inn: (validationResults[0]?.data?.inn as string) || `SELLER_${Date.now()}`,
|
||||
inn:
|
||||
(validationResults[0]?.data?.inn as string) ||
|
||||
`SELLER_${Date.now()}`,
|
||||
name: shopName, // Используем tradeMark как основное название
|
||||
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
|
||||
type: 'SELLER'
|
||||
}
|
||||
})
|
||||
fullName: sellerName
|
||||
? `${sellerName} (${shopName})`
|
||||
: `Интернет-магазин "${shopName}"`,
|
||||
type: "SELLER",
|
||||
},
|
||||
});
|
||||
|
||||
// Добавляем API ключи
|
||||
for (const validation of validationResults) {
|
||||
await prisma.apiKey.create({
|
||||
data: {
|
||||
marketplace: validation.marketplace as 'WILDBERRIES' | 'OZON',
|
||||
marketplace: validation.marketplace as "WILDBERRIES" | "OZON",
|
||||
apiKey: validation.apiKey,
|
||||
organizationId: organization.id,
|
||||
validationData: JSON.parse(JSON.stringify(validation.data))
|
||||
}
|
||||
})
|
||||
validationData: JSON.parse(JSON.stringify(validation.data)),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Привязываем пользователя к организации
|
||||
@ -1222,24 +1317,23 @@ export const resolvers = {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Селлер организация успешно зарегистрирована',
|
||||
user: updatedUser
|
||||
}
|
||||
|
||||
message: "Селлер организация успешно зарегистрирована",
|
||||
user: updatedUser,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error registering seller organization:', error)
|
||||
console.error("Error registering seller organization:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при регистрации организации'
|
||||
}
|
||||
message: "Ошибка при регистрации организации",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@ -1247,84 +1341,85 @@ export const resolvers = {
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
marketplace: 'WILDBERRIES' | 'OZON'
|
||||
apiKey: string
|
||||
clientId?: string
|
||||
validateOnly?: boolean
|
||||
}
|
||||
marketplace: "WILDBERRIES" | "OZON";
|
||||
apiKey: string;
|
||||
clientId?: string;
|
||||
validateOnly?: boolean;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
// Разрешаем валидацию без авторизации
|
||||
if (!args.input.validateOnly && !context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const { marketplace, apiKey, clientId, validateOnly } = args.input
|
||||
const { marketplace, apiKey, clientId, validateOnly } = args.input;
|
||||
|
||||
// Валидируем API ключ
|
||||
const validationResult = await marketplaceService.validateApiKey(
|
||||
marketplace,
|
||||
apiKey,
|
||||
clientId
|
||||
)
|
||||
);
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: validationResult.message
|
||||
}
|
||||
message: validationResult.message,
|
||||
};
|
||||
}
|
||||
|
||||
// Если это только валидация, возвращаем результат без сохранения
|
||||
if (validateOnly) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'API ключ действителен',
|
||||
message: "API ключ действителен",
|
||||
apiKey: {
|
||||
id: 'validate-only',
|
||||
id: "validate-only",
|
||||
marketplace,
|
||||
isActive: true,
|
||||
validationData: validationResult,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Для сохранения API ключа нужна авторизация
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация для сохранения API ключа', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError(
|
||||
"Требуется авторизация для сохранения API ключа",
|
||||
{
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!user?.organization) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Пользователь не привязан к организации'
|
||||
}
|
||||
message: "Пользователь не привязан к организации",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
try {
|
||||
// Проверяем, что такого ключа еще нет
|
||||
const existingKey = await prisma.apiKey.findUnique({
|
||||
where: {
|
||||
organizationId_marketplace: {
|
||||
organizationId: user.organization.id,
|
||||
marketplace
|
||||
}
|
||||
}
|
||||
})
|
||||
marketplace,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingKey) {
|
||||
// Обновляем существующий ключ
|
||||
@ -1332,16 +1427,16 @@ export const resolvers = {
|
||||
where: { id: existingKey.id },
|
||||
data: {
|
||||
apiKey,
|
||||
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||
isActive: true
|
||||
}
|
||||
})
|
||||
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'API ключ успешно обновлен',
|
||||
apiKey: updatedKey
|
||||
}
|
||||
message: "API ключ успешно обновлен",
|
||||
apiKey: updatedKey,
|
||||
};
|
||||
} else {
|
||||
// Создаем новый ключ
|
||||
const newKey = await prisma.apiKey.create({
|
||||
@ -1349,44 +1444,43 @@ export const resolvers = {
|
||||
marketplace,
|
||||
apiKey,
|
||||
organizationId: user.organization.id,
|
||||
validationData: JSON.parse(JSON.stringify(validationResult.data))
|
||||
}
|
||||
})
|
||||
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'API ключ успешно добавлен',
|
||||
apiKey: newKey
|
||||
}
|
||||
message: "API ключ успешно добавлен",
|
||||
apiKey: newKey,
|
||||
};
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error adding marketplace API key:', error)
|
||||
console.error("Error adding marketplace API key:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при добавлении API ключа'
|
||||
}
|
||||
message: "Ошибка при добавлении API ключа",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
removeMarketplaceApiKey: async (
|
||||
_: unknown,
|
||||
args: { marketplace: 'WILDBERRIES' | 'OZON' },
|
||||
args: { marketplace: "WILDBERRIES" | "OZON" },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!user?.organization) {
|
||||
throw new GraphQLError('Пользователь не привязан к организации')
|
||||
throw new GraphQLError("Пользователь не привязан к организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1394,128 +1488,139 @@ export const resolvers = {
|
||||
where: {
|
||||
organizationId_marketplace: {
|
||||
organizationId: user.organization.id,
|
||||
marketplace: args.marketplace
|
||||
}
|
||||
}
|
||||
})
|
||||
marketplace: args.marketplace,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error removing marketplace API key:', error)
|
||||
return false
|
||||
console.error("Error removing marketplace API key:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
updateUserProfile: async (_: unknown, args: { input: {
|
||||
avatar?: string
|
||||
orgPhone?: string
|
||||
managerName?: string
|
||||
telegram?: string
|
||||
whatsapp?: string
|
||||
email?: string
|
||||
bankName?: string
|
||||
bik?: string
|
||||
accountNumber?: string
|
||||
corrAccount?: string
|
||||
} }, context: Context) => {
|
||||
updateUserProfile: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
avatar?: string;
|
||||
orgPhone?: string;
|
||||
managerName?: string;
|
||||
telegram?: string;
|
||||
whatsapp?: string;
|
||||
email?: string;
|
||||
bankName?: string;
|
||||
bik?: string;
|
||||
accountNumber?: string;
|
||||
corrAccount?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user?.organization) {
|
||||
throw new GraphQLError('Пользователь не привязан к организации')
|
||||
throw new GraphQLError("Пользователь не привязан к организации");
|
||||
}
|
||||
|
||||
try {
|
||||
const { input } = args
|
||||
|
||||
const { input } = args;
|
||||
|
||||
// Обновляем данные пользователя (аватар, имя управляющего)
|
||||
const userUpdateData: { avatar?: string; managerName?: string } = {}
|
||||
const userUpdateData: { avatar?: string; managerName?: string } = {};
|
||||
if (input.avatar) {
|
||||
userUpdateData.avatar = input.avatar
|
||||
userUpdateData.avatar = input.avatar;
|
||||
}
|
||||
if (input.managerName) {
|
||||
userUpdateData.managerName = input.managerName
|
||||
userUpdateData.managerName = input.managerName;
|
||||
}
|
||||
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) {
|
||||
await prisma.user.update({
|
||||
where: { id: context.user.id },
|
||||
data: userUpdateData
|
||||
})
|
||||
data: userUpdateData,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Подготавливаем данные для обновления организации
|
||||
const updateData: {
|
||||
phones?: object
|
||||
emails?: object
|
||||
managementName?: string
|
||||
managementPost?: string
|
||||
} = {}
|
||||
|
||||
phones?: object;
|
||||
emails?: object;
|
||||
managementName?: string;
|
||||
managementPost?: string;
|
||||
} = {};
|
||||
|
||||
// Название организации больше не обновляется через профиль
|
||||
// Для селлеров устанавливается при регистрации, для остальных - при смене ИНН
|
||||
|
||||
|
||||
// Обновляем контактные данные в JSON поле phones
|
||||
if (input.orgPhone) {
|
||||
updateData.phones = [{ value: input.orgPhone, type: 'main' }]
|
||||
updateData.phones = [{ value: input.orgPhone, type: "main" }];
|
||||
}
|
||||
|
||||
// Обновляем email в JSON поле emails
|
||||
|
||||
// Обновляем email в JSON поле emails
|
||||
if (input.email) {
|
||||
updateData.emails = [{ value: input.email, type: 'main' }]
|
||||
updateData.emails = [{ value: input.email, type: "main" }];
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем дополнительные контакты в custom полях
|
||||
// Пока добавим их как дополнительные JSON поля
|
||||
const customContacts: {
|
||||
managerName?: string
|
||||
telegram?: string
|
||||
whatsapp?: string
|
||||
managerName?: string;
|
||||
telegram?: string;
|
||||
whatsapp?: string;
|
||||
bankDetails?: {
|
||||
bankName?: string
|
||||
bik?: string
|
||||
accountNumber?: string
|
||||
corrAccount?: string
|
||||
}
|
||||
} = {}
|
||||
|
||||
bankName?: string;
|
||||
bik?: string;
|
||||
accountNumber?: string;
|
||||
corrAccount?: string;
|
||||
};
|
||||
} = {};
|
||||
|
||||
// managerName теперь сохраняется в поле пользователя, а не в JSON
|
||||
|
||||
|
||||
if (input.telegram) {
|
||||
customContacts.telegram = input.telegram
|
||||
customContacts.telegram = input.telegram;
|
||||
}
|
||||
|
||||
|
||||
if (input.whatsapp) {
|
||||
customContacts.whatsapp = input.whatsapp
|
||||
customContacts.whatsapp = input.whatsapp;
|
||||
}
|
||||
|
||||
if (input.bankName || input.bik || input.accountNumber || input.corrAccount) {
|
||||
|
||||
if (
|
||||
input.bankName ||
|
||||
input.bik ||
|
||||
input.accountNumber ||
|
||||
input.corrAccount
|
||||
) {
|
||||
customContacts.bankDetails = {
|
||||
bankName: input.bankName,
|
||||
bik: input.bik,
|
||||
accountNumber: input.accountNumber,
|
||||
corrAccount: input.corrAccount
|
||||
}
|
||||
corrAccount: input.corrAccount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Если есть дополнительные контакты, сохраним их в поле managementPost временно
|
||||
// В идеале нужно добавить отдельную таблицу для контактов
|
||||
if (Object.keys(customContacts).length > 0) {
|
||||
updateData.managementPost = JSON.stringify(customContacts)
|
||||
updateData.managementPost = JSON.stringify(customContacts);
|
||||
}
|
||||
|
||||
// Обновляем организацию
|
||||
@ -1523,56 +1628,60 @@ export const resolvers = {
|
||||
where: { id: user.organization.id },
|
||||
data: updateData,
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Получаем обновленного пользователя
|
||||
const updatedUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Профиль успешно обновлен',
|
||||
user: updatedUser
|
||||
}
|
||||
message: "Профиль успешно обновлен",
|
||||
user: updatedUser,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating user profile:', error)
|
||||
console.error("Error updating user profile:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении профиля'
|
||||
}
|
||||
message: "Ошибка при обновлении профиля",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
updateOrganizationByInn: async (_: unknown, args: { inn: string }, context: Context) => {
|
||||
updateOrganizationByInn: async (
|
||||
_: unknown,
|
||||
args: { inn: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user?.organization) {
|
||||
throw new GraphQLError('Пользователь не привязан к организации')
|
||||
throw new GraphQLError("Пользователь не привязан к организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1580,43 +1689,57 @@ export const resolvers = {
|
||||
if (!dadataService.validateInn(args.inn)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверный формат ИНН'
|
||||
}
|
||||
message: "Неверный формат ИНН",
|
||||
};
|
||||
}
|
||||
|
||||
// Получаем данные организации из DaData
|
||||
const organizationData = await dadataService.getOrganizationByInn(args.inn)
|
||||
const organizationData = await dadataService.getOrganizationByInn(
|
||||
args.inn
|
||||
);
|
||||
if (!organizationData) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Организация с указанным ИНН не найдена в федеральном реестре'
|
||||
}
|
||||
message:
|
||||
"Организация с указанным ИНН не найдена в федеральном реестре",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем, есть ли уже организация с таким ИНН в базе (кроме текущей)
|
||||
const existingOrganization = await prisma.organization.findUnique({
|
||||
where: { inn: organizationData.inn }
|
||||
})
|
||||
where: { inn: organizationData.inn },
|
||||
});
|
||||
|
||||
if (existingOrganization && existingOrganization.id !== user.organization.id) {
|
||||
if (
|
||||
existingOrganization &&
|
||||
existingOrganization.id !== user.organization.id
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Организация с ИНН ${organizationData.inn} уже существует в системе`
|
||||
}
|
||||
message: `Организация с ИНН ${organizationData.inn} уже существует в системе`,
|
||||
};
|
||||
}
|
||||
|
||||
// Подготавливаем данные для обновления
|
||||
const updateData: Prisma.OrganizationUpdateInput = {
|
||||
kpp: organizationData.kpp,
|
||||
// Для селлеров не обновляем название организации (это название магазина)
|
||||
...(user.organization.type !== 'SELLER' && { name: organizationData.name }),
|
||||
...(user.organization.type !== "SELLER" && {
|
||||
name: organizationData.name,
|
||||
}),
|
||||
fullName: organizationData.fullName,
|
||||
address: organizationData.address,
|
||||
addressFull: organizationData.addressFull,
|
||||
ogrn: organizationData.ogrn,
|
||||
ogrnDate: organizationData.ogrnDate ? organizationData.ogrnDate.toISOString() : null,
|
||||
registrationDate: organizationData.registrationDate ? organizationData.registrationDate.toISOString() : null,
|
||||
liquidationDate: organizationData.liquidationDate ? organizationData.liquidationDate.toISOString() : null,
|
||||
ogrnDate: organizationData.ogrnDate
|
||||
? organizationData.ogrnDate.toISOString()
|
||||
: null,
|
||||
registrationDate: organizationData.registrationDate
|
||||
? organizationData.registrationDate.toISOString()
|
||||
: null,
|
||||
liquidationDate: organizationData.liquidationDate
|
||||
? organizationData.liquidationDate.toISOString()
|
||||
: null,
|
||||
managementName: organizationData.managementName, // Всегда перезаписываем данными из DaData (может быть null)
|
||||
managementPost: user.organization.managementPost, // Сохраняем кастомные данные пользователя
|
||||
opfCode: organizationData.opfCode,
|
||||
@ -1626,12 +1749,12 @@ export const resolvers = {
|
||||
oktmo: organizationData.oktmo,
|
||||
okpo: organizationData.okpo,
|
||||
okved: organizationData.okved,
|
||||
status: organizationData.status
|
||||
}
|
||||
status: organizationData.status,
|
||||
};
|
||||
|
||||
// Добавляем ИНН только если он отличается от текущего
|
||||
if (user.organization.inn !== organizationData.inn) {
|
||||
updateData.inn = organizationData.inn
|
||||
updateData.inn = organizationData.inn;
|
||||
}
|
||||
|
||||
// Обновляем организацию
|
||||
@ -1639,70 +1762,74 @@ export const resolvers = {
|
||||
where: { id: user.organization.id },
|
||||
data: updateData,
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Получаем обновленного пользователя
|
||||
const updatedUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: {
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
apiKeys: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
apiKeys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Данные организации успешно обновлены',
|
||||
user: updatedUser
|
||||
}
|
||||
message: "Данные организации успешно обновлены",
|
||||
user: updatedUser,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating organization by INN:', error)
|
||||
console.error("Error updating organization by INN:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении данных организации'
|
||||
}
|
||||
message: "Ошибка при обновлении данных организации",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
// В stateless JWT системе logout происходит на клиенте
|
||||
// Можно добавить blacklist токенов, если нужно
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
|
||||
// Отправить заявку на добавление в контрагенты
|
||||
sendCounterpartyRequest: async (_: unknown, args: { organizationId: string; message?: string }, context: Context) => {
|
||||
sendCounterpartyRequest: async (
|
||||
_: unknown,
|
||||
args: { organizationId: string; message?: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.id === args.organizationId) {
|
||||
throw new GraphQLError('Нельзя отправить заявку самому себе')
|
||||
throw new GraphQLError("Нельзя отправить заявку самому себе");
|
||||
}
|
||||
|
||||
// Проверяем, что организация-получатель существует
|
||||
const receiverOrganization = await prisma.organization.findUnique({
|
||||
where: { id: args.organizationId }
|
||||
})
|
||||
where: { id: args.organizationId },
|
||||
});
|
||||
|
||||
if (!receiverOrganization) {
|
||||
throw new GraphQLError('Организация не найдена')
|
||||
throw new GraphQLError("Организация не найдена");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1711,55 +1838,59 @@ export const resolvers = {
|
||||
where: {
|
||||
senderId_receiverId: {
|
||||
senderId: currentUser.organization.id,
|
||||
receiverId: args.organizationId
|
||||
}
|
||||
receiverId: args.organizationId,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
status: 'PENDING',
|
||||
status: "PENDING",
|
||||
message: args.message,
|
||||
updatedAt: new Date()
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
create: {
|
||||
senderId: currentUser.organization.id,
|
||||
receiverId: args.organizationId,
|
||||
message: args.message,
|
||||
status: 'PENDING'
|
||||
status: "PENDING",
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
receiver: true
|
||||
}
|
||||
})
|
||||
receiver: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Заявка отправлена',
|
||||
request
|
||||
}
|
||||
message: "Заявка отправлена",
|
||||
request,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending counterparty request:', error)
|
||||
console.error("Error sending counterparty request:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке заявки'
|
||||
}
|
||||
message: "Ошибка при отправке заявки",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Ответить на заявку контрагента
|
||||
respondToCounterpartyRequest: async (_: unknown, args: { requestId: string; accept: boolean }, context: Context) => {
|
||||
respondToCounterpartyRequest: async (
|
||||
_: unknown,
|
||||
args: { requestId: string; accept: boolean },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1768,23 +1899,23 @@ export const resolvers = {
|
||||
where: { id: args.requestId },
|
||||
include: {
|
||||
sender: true,
|
||||
receiver: true
|
||||
}
|
||||
})
|
||||
receiver: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
throw new GraphQLError('Заявка не найдена')
|
||||
throw new GraphQLError("Заявка не найдена");
|
||||
}
|
||||
|
||||
if (request.receiverId !== currentUser.organization.id) {
|
||||
throw new GraphQLError('Нет прав на обработку этой заявки')
|
||||
throw new GraphQLError("Нет прав на обработку этой заявки");
|
||||
}
|
||||
|
||||
if (request.status !== 'PENDING') {
|
||||
throw new GraphQLError('Заявка уже обработана')
|
||||
if (request.status !== "PENDING") {
|
||||
throw new GraphQLError("Заявка уже обработана");
|
||||
}
|
||||
|
||||
const newStatus = args.accept ? 'ACCEPTED' : 'REJECTED'
|
||||
const newStatus = args.accept ? "ACCEPTED" : "REJECTED";
|
||||
|
||||
// Обновляем статус заявки
|
||||
const updatedRequest = await prisma.counterpartyRequest.update({
|
||||
@ -1792,9 +1923,9 @@ export const resolvers = {
|
||||
data: { status: newStatus },
|
||||
include: {
|
||||
sender: true,
|
||||
receiver: true
|
||||
}
|
||||
})
|
||||
receiver: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Если заявка принята, создаем связи контрагентов в обе стороны
|
||||
if (args.accept) {
|
||||
@ -1803,94 +1934,102 @@ export const resolvers = {
|
||||
prisma.counterparty.create({
|
||||
data: {
|
||||
organizationId: request.receiverId,
|
||||
counterpartyId: request.senderId
|
||||
}
|
||||
counterpartyId: request.senderId,
|
||||
},
|
||||
}),
|
||||
// Добавляем получателя в контрагенты отправителя
|
||||
prisma.counterparty.create({
|
||||
data: {
|
||||
organizationId: request.senderId,
|
||||
counterpartyId: request.receiverId
|
||||
}
|
||||
})
|
||||
])
|
||||
counterpartyId: request.receiverId,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: args.accept ? 'Заявка принята' : 'Заявка отклонена',
|
||||
request: updatedRequest
|
||||
}
|
||||
message: args.accept ? "Заявка принята" : "Заявка отклонена",
|
||||
request: updatedRequest,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error responding to counterparty request:', error)
|
||||
console.error("Error responding to counterparty request:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обработке заявки'
|
||||
}
|
||||
message: "Ошибка при обработке заявки",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Отменить заявку
|
||||
cancelCounterpartyRequest: async (_: unknown, args: { requestId: string }, context: Context) => {
|
||||
cancelCounterpartyRequest: async (
|
||||
_: unknown,
|
||||
args: { requestId: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
const request = await prisma.counterpartyRequest.findUnique({
|
||||
where: { id: args.requestId }
|
||||
})
|
||||
where: { id: args.requestId },
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
throw new GraphQLError('Заявка не найдена')
|
||||
throw new GraphQLError("Заявка не найдена");
|
||||
}
|
||||
|
||||
if (request.senderId !== currentUser.organization.id) {
|
||||
throw new GraphQLError('Можно отменить только свои заявки')
|
||||
throw new GraphQLError("Можно отменить только свои заявки");
|
||||
}
|
||||
|
||||
if (request.status !== 'PENDING') {
|
||||
throw new GraphQLError('Можно отменить только ожидающие заявки')
|
||||
if (request.status !== "PENDING") {
|
||||
throw new GraphQLError("Можно отменить только ожидающие заявки");
|
||||
}
|
||||
|
||||
await prisma.counterpartyRequest.update({
|
||||
where: { id: args.requestId },
|
||||
data: { status: 'CANCELLED' }
|
||||
})
|
||||
data: { status: "CANCELLED" },
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error cancelling counterparty request:', error)
|
||||
return false
|
||||
console.error("Error cancelling counterparty request:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить контрагента
|
||||
removeCounterparty: async (_: unknown, args: { organizationId: string }, context: Context) => {
|
||||
removeCounterparty: async (
|
||||
_: unknown,
|
||||
args: { organizationId: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1899,60 +2038,70 @@ export const resolvers = {
|
||||
prisma.counterparty.deleteMany({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.organizationId
|
||||
}
|
||||
counterpartyId: args.organizationId,
|
||||
},
|
||||
}),
|
||||
prisma.counterparty.deleteMany({
|
||||
where: {
|
||||
organizationId: args.organizationId,
|
||||
counterpartyId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
])
|
||||
counterpartyId: currentUser.organization.id,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error removing counterparty:', error)
|
||||
return false
|
||||
console.error("Error removing counterparty:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить сообщение
|
||||
sendMessage: async (_: unknown, args: { receiverOrganizationId: string; content?: string; type?: 'TEXT' | 'VOICE' }, context: Context) => {
|
||||
sendMessage: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
receiverOrganizationId: string;
|
||||
content?: string;
|
||||
type?: "TEXT" | "VOICE";
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что получатель является контрагентом
|
||||
const isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
counterpartyId: args.receiverOrganizationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
throw new GraphQLError(
|
||||
"Можно отправлять сообщения только контрагентам"
|
||||
);
|
||||
}
|
||||
|
||||
// Получаем организацию получателя
|
||||
const receiverOrganization = await prisma.organization.findUnique({
|
||||
where: { id: args.receiverOrganizationId }
|
||||
})
|
||||
where: { id: args.receiverOrganizationId },
|
||||
});
|
||||
|
||||
if (!receiverOrganization) {
|
||||
throw new GraphQLError('Организация получателя не найдена')
|
||||
throw new GraphQLError("Организация получателя не найдена");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1960,76 +2109,86 @@ export const resolvers = {
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: args.content?.trim() || null,
|
||||
type: args.type || 'TEXT',
|
||||
type: args.type || "TEXT",
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
receiverOrganizationId: args.receiverOrganizationId,
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Сообщение отправлено',
|
||||
messageData: message
|
||||
}
|
||||
message: "Сообщение отправлено",
|
||||
messageData: message,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
console.error("Error sending message:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке сообщения'
|
||||
}
|
||||
message: "Ошибка при отправке сообщения",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить голосовое сообщение
|
||||
sendVoiceMessage: async (_: unknown, args: { receiverOrganizationId: string; voiceUrl: string; voiceDuration: number }, context: Context) => {
|
||||
sendVoiceMessage: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
receiverOrganizationId: string;
|
||||
voiceUrl: string;
|
||||
voiceDuration: number;
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что получатель является контрагентом
|
||||
const isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
counterpartyId: args.receiverOrganizationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
throw new GraphQLError(
|
||||
"Можно отправлять сообщения только контрагентам"
|
||||
);
|
||||
}
|
||||
|
||||
// Получаем организацию получателя
|
||||
const receiverOrganization = await prisma.organization.findUnique({
|
||||
where: { id: args.receiverOrganizationId }
|
||||
})
|
||||
where: { id: args.receiverOrganizationId },
|
||||
});
|
||||
|
||||
if (!receiverOrganization) {
|
||||
throw new GraphQLError('Организация получателя не найдена')
|
||||
throw new GraphQLError("Организация получателя не найдена");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2037,217 +2196,256 @@ export const resolvers = {
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: null,
|
||||
type: 'VOICE',
|
||||
type: "VOICE",
|
||||
voiceUrl: args.voiceUrl,
|
||||
voiceDuration: args.voiceDuration,
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
receiverOrganizationId: args.receiverOrganizationId,
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Голосовое сообщение отправлено',
|
||||
messageData: message
|
||||
}
|
||||
message: "Голосовое сообщение отправлено",
|
||||
messageData: message,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending voice message:', error)
|
||||
console.error("Error sending voice message:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке голосового сообщения'
|
||||
}
|
||||
message: "Ошибка при отправке голосового сообщения",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить изображение
|
||||
sendImageMessage: async (_: unknown, args: { receiverOrganizationId: string; fileUrl: string; fileName: string; fileSize: number; fileType: string }, context: Context) => {
|
||||
sendImageMessage: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
receiverOrganizationId: string;
|
||||
fileUrl: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
fileType: string;
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что получатель является контрагентом
|
||||
const isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
counterpartyId: args.receiverOrganizationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
throw new GraphQLError(
|
||||
"Можно отправлять сообщения только контрагентам"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: null,
|
||||
type: 'IMAGE',
|
||||
type: "IMAGE",
|
||||
fileUrl: args.fileUrl,
|
||||
fileName: args.fileName,
|
||||
fileSize: args.fileSize,
|
||||
fileType: args.fileType,
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
receiverOrganizationId: args.receiverOrganizationId,
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Изображение отправлено',
|
||||
messageData: message
|
||||
}
|
||||
message: "Изображение отправлено",
|
||||
messageData: message,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending image:', error)
|
||||
console.error("Error sending image:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке изображения'
|
||||
}
|
||||
message: "Ошибка при отправке изображения",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Отправить файл
|
||||
sendFileMessage: async (_: unknown, args: { receiverOrganizationId: string; fileUrl: string; fileName: string; fileSize: number; fileType: string }, context: Context) => {
|
||||
sendFileMessage: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
receiverOrganizationId: string;
|
||||
fileUrl: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
fileType: string;
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что получатель является контрагентом
|
||||
const isCounterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.receiverOrganizationId
|
||||
}
|
||||
})
|
||||
counterpartyId: args.receiverOrganizationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isCounterparty) {
|
||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
||||
throw new GraphQLError(
|
||||
"Можно отправлять сообщения только контрагентам"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content: null,
|
||||
type: 'FILE',
|
||||
type: "FILE",
|
||||
fileUrl: args.fileUrl,
|
||||
fileName: args.fileName,
|
||||
fileSize: args.fileSize,
|
||||
fileType: args.fileType,
|
||||
senderId: context.user.id,
|
||||
senderOrganizationId: currentUser.organization.id,
|
||||
receiverOrganizationId: args.receiverOrganizationId
|
||||
receiverOrganizationId: args.receiverOrganizationId,
|
||||
},
|
||||
include: {
|
||||
sender: true,
|
||||
senderOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
receiverOrganization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Файл отправлен',
|
||||
messageData: message
|
||||
}
|
||||
message: "Файл отправлен",
|
||||
messageData: message,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending file:', error)
|
||||
console.error("Error sending file:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке файла'
|
||||
}
|
||||
message: "Ошибка при отправке файла",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Отметить сообщения как прочитанные
|
||||
markMessagesAsRead: async (_: unknown, args: { conversationId: string }, context: Context) => {
|
||||
markMessagesAsRead: async (
|
||||
_: unknown,
|
||||
args: { conversationId: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Здесь будет логика обновления статуса сообщений
|
||||
// Пока возвращаем успешный ответ
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
|
||||
// Создать услугу
|
||||
createService: async (_: unknown, args: { input: { name: string; description?: string; price: number; imageUrl?: string } }, context: Context) => {
|
||||
createService: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
imageUrl?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что это фулфилмент центр
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Услуги доступны только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Услуги доступны только для фулфилмент центров");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2257,52 +2455,64 @@ export const resolvers = {
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
imageUrl: args.input.imageUrl,
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Услуга успешно создана',
|
||||
service
|
||||
}
|
||||
message: "Услуга успешно создана",
|
||||
service,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating service:', error)
|
||||
console.error("Error creating service:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании услуги'
|
||||
}
|
||||
message: "Ошибка при создании услуги",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить услугу
|
||||
updateService: async (_: unknown, args: { id: string; input: { name: string; description?: string; price: number; imageUrl?: string } }, context: Context) => {
|
||||
updateService: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
id: string;
|
||||
input: {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
imageUrl?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что услуга принадлежит текущей организации
|
||||
const existingService = await prisma.service.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingService) {
|
||||
throw new GraphQLError('Услуга не найдена или нет доступа')
|
||||
throw new GraphQLError("Услуга не найдена или нет доступа");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2312,86 +2522,103 @@ export const resolvers = {
|
||||
name: args.input.name,
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
imageUrl: args.input.imageUrl
|
||||
imageUrl: args.input.imageUrl,
|
||||
},
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Услуга успешно обновлена',
|
||||
service
|
||||
}
|
||||
message: "Услуга успешно обновлена",
|
||||
service,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating service:', error)
|
||||
console.error("Error updating service:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении услуги'
|
||||
}
|
||||
message: "Ошибка при обновлении услуги",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить услугу
|
||||
deleteService: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
deleteService: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что услуга принадлежит текущей организации
|
||||
const existingService = await prisma.service.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingService) {
|
||||
throw new GraphQLError('Услуга не найдена или нет доступа')
|
||||
throw new GraphQLError("Услуга не найдена или нет доступа");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.service.delete({
|
||||
where: { id: args.id }
|
||||
})
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting service:', error)
|
||||
return false
|
||||
console.error("Error deleting service:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Создать расходник
|
||||
createSupply: async (_: unknown, args: { input: { name: string; description?: string; price: number; imageUrl?: string } }, context: Context) => {
|
||||
createSupply: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
imageUrl?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что это фулфилмент центр
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Расходники доступны только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError(
|
||||
"Расходники доступны только для фулфилмент центров"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2402,52 +2629,64 @@ export const resolvers = {
|
||||
price: args.input.price,
|
||||
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
|
||||
imageUrl: args.input.imageUrl,
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Расходник успешно создан',
|
||||
supply
|
||||
}
|
||||
message: "Расходник успешно создан",
|
||||
supply,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating supply:', error)
|
||||
console.error("Error creating supply:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании расходника'
|
||||
}
|
||||
message: "Ошибка при создании расходника",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить расходник
|
||||
updateSupply: async (_: unknown, args: { id: string; input: { name: string; description?: string; price: number; imageUrl?: string } }, context: Context) => {
|
||||
updateSupply: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
id: string;
|
||||
input: {
|
||||
name: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
imageUrl?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что расходник принадлежит текущей организации
|
||||
const existingSupply = await prisma.supply.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingSupply) {
|
||||
throw new GraphQLError('Расходник не найден или нет доступа')
|
||||
throw new GraphQLError("Расходник не найден или нет доступа");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2458,119 +2697,292 @@ export const resolvers = {
|
||||
description: args.input.description,
|
||||
price: args.input.price,
|
||||
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
|
||||
imageUrl: args.input.imageUrl
|
||||
imageUrl: args.input.imageUrl,
|
||||
},
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Расходник успешно обновлен',
|
||||
supply
|
||||
}
|
||||
message: "Расходник успешно обновлен",
|
||||
supply,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating supply:', error)
|
||||
console.error("Error updating supply:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении расходника'
|
||||
}
|
||||
message: "Ошибка при обновлении расходника",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить расходник
|
||||
deleteSupply: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
deleteSupply: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что расходник принадлежит текущей организации
|
||||
const existingSupply = await prisma.supply.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingSupply) {
|
||||
throw new GraphQLError('Расходник не найден или нет доступа')
|
||||
throw new GraphQLError("Расходник не найден или нет доступа");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.supply.delete({
|
||||
where: { id: args.id }
|
||||
})
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting supply:', error)
|
||||
return false
|
||||
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) => {
|
||||
// Создать заказ поставки расходников
|
||||
createSupplyOrder: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
partnerId: string;
|
||||
deliveryDate: string;
|
||||
items: Array<{ productId: string; quantity: number }>;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что это фулфилмент центр
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError(
|
||||
"Заказы поставок доступны только для фулфилмент центров"
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем, что партнер существует и является оптовиком
|
||||
const partner = await prisma.organization.findFirst({
|
||||
where: {
|
||||
id: args.input.partnerId,
|
||||
type: "WHOLESALE",
|
||||
},
|
||||
});
|
||||
|
||||
if (!partner) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Партнер не найден или не является оптовиком",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем, что партнер является контрагентом
|
||||
const counterparty = await prisma.counterparty.findFirst({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
counterpartyId: args.input.partnerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!counterparty) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Данная организация не является вашим партнером",
|
||||
};
|
||||
}
|
||||
|
||||
// Получаем товары для проверки наличия и цен
|
||||
const productIds = args.input.items.map((item) => item.productId);
|
||||
const products = await prisma.product.findMany({
|
||||
where: {
|
||||
id: { in: productIds },
|
||||
organizationId: args.input.partnerId,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (products.length !== productIds.length) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Некоторые товары не найдены или неактивны",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем наличие товаров
|
||||
for (const item of args.input.items) {
|
||||
const product = products.find((p) => p.id === item.productId);
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Товар ${item.productId} не найден`,
|
||||
};
|
||||
}
|
||||
if (product.quantity < item.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара "${product.name}". Доступно: ${product.quantity}, запрошено: ${item.quantity}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Рассчитываем общую сумму и количество
|
||||
let totalAmount = 0;
|
||||
let totalItems = 0;
|
||||
const orderItems = args.input.items.map((item) => {
|
||||
const product = products.find((p) => p.id === item.productId)!;
|
||||
const itemTotal = Number(product.price) * item.quantity;
|
||||
totalAmount += itemTotal;
|
||||
totalItems += item.quantity;
|
||||
|
||||
return {
|
||||
productId: item.productId,
|
||||
quantity: item.quantity,
|
||||
price: product.price,
|
||||
totalPrice: new Prisma.Decimal(itemTotal),
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
const supplyOrder = await prisma.supplyOrder.create({
|
||||
data: {
|
||||
partnerId: args.input.partnerId,
|
||||
deliveryDate: new Date(args.input.deliveryDate),
|
||||
totalAmount: new Prisma.Decimal(totalAmount),
|
||||
totalItems: totalItems,
|
||||
organizationId: currentUser.organization.id,
|
||||
items: {
|
||||
create: orderItems,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
partner: {
|
||||
include: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
organization: {
|
||||
include: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Заказ поставки создан успешно",
|
||||
order: supplyOrder,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating supply order:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Ошибка при создании заказа поставки",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Создать товар
|
||||
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('Товары доступны только для оптовиков')
|
||||
if (currentUser.organization.type !== "WHOLESALE") {
|
||||
throw new GraphQLError("Товары доступны только для оптовиков");
|
||||
}
|
||||
|
||||
// Проверяем уникальность артикула в рамках организации
|
||||
const existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
article: args.input.article,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingProduct) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар с таким артикулом уже существует'
|
||||
}
|
||||
message: "Товар с таким артикулом уже существует",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2591,74 +3003,78 @@ export const resolvers = {
|
||||
images: args.input.images || [],
|
||||
mainImage: args.input.mainImage,
|
||||
isActive: args.input.isActive ?? true,
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар успешно создан',
|
||||
product
|
||||
}
|
||||
message: "Товар успешно создан",
|
||||
product,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating product:', error)
|
||||
console.error("Error creating product:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании товара'
|
||||
}
|
||||
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) => {
|
||||
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' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что товар принадлежит текущей организации
|
||||
const existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingProduct) {
|
||||
throw new GraphQLError('Товар не найден или нет доступа')
|
||||
throw new GraphQLError("Товар не найден или нет доступа");
|
||||
}
|
||||
|
||||
// Проверяем уникальность артикула (если он изменился)
|
||||
@ -2667,15 +3083,15 @@ export const resolvers = {
|
||||
where: {
|
||||
article: args.input.article,
|
||||
organizationId: currentUser.organization.id,
|
||||
NOT: { id: args.id }
|
||||
}
|
||||
})
|
||||
NOT: { id: args.id },
|
||||
},
|
||||
});
|
||||
|
||||
if (duplicateProduct) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар с таким артикулом уже существует'
|
||||
}
|
||||
message: "Товар с таким артикулом уже существует",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2697,141 +3113,153 @@ export const resolvers = {
|
||||
material: args.input.material,
|
||||
images: args.input.images || [],
|
||||
mainImage: args.input.mainImage,
|
||||
isActive: args.input.isActive ?? true
|
||||
isActive: args.input.isActive ?? true,
|
||||
},
|
||||
include: {
|
||||
include: {
|
||||
category: true,
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар успешно обновлен',
|
||||
product
|
||||
}
|
||||
message: "Товар успешно обновлен",
|
||||
product,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating product:', error)
|
||||
console.error("Error updating product:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении товара'
|
||||
}
|
||||
message: "Ошибка при обновлении товара",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить товар
|
||||
deleteProduct: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
deleteProduct: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что товар принадлежит текущей организации
|
||||
const existingProduct = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingProduct) {
|
||||
throw new GraphQLError('Товар не найден или нет доступа')
|
||||
throw new GraphQLError("Товар не найден или нет доступа");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.product.delete({
|
||||
where: { id: args.id }
|
||||
})
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting product:', error)
|
||||
return false
|
||||
console.error("Error deleting product:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Создать категорию
|
||||
createCategory: async (_: unknown, args: { input: { name: string } }, context: Context) => {
|
||||
createCategory: async (
|
||||
_: unknown,
|
||||
args: { input: { name: string } },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user && !context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
// Проверяем уникальность названия категории
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { name: args.input.name }
|
||||
})
|
||||
where: { name: args.input.name },
|
||||
});
|
||||
|
||||
if (existingCategory) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Категория с таким названием уже существует'
|
||||
}
|
||||
message: "Категория с таким названием уже существует",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const category = await prisma.category.create({
|
||||
data: {
|
||||
name: args.input.name
|
||||
}
|
||||
})
|
||||
name: args.input.name,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Категория успешно создана',
|
||||
category
|
||||
}
|
||||
message: "Категория успешно создана",
|
||||
category,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating category:', error)
|
||||
console.error("Error creating category:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании категории'
|
||||
}
|
||||
message: "Ошибка при создании категории",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить категорию
|
||||
updateCategory: async (_: unknown, args: { id: string; input: { name: string } }, context: Context) => {
|
||||
updateCategory: async (
|
||||
_: unknown,
|
||||
args: { id: string; input: { name: string } },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user && !context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
// Проверяем существование категории
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { id: args.id }
|
||||
})
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
if (!existingCategory) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Категория не найдена'
|
||||
}
|
||||
message: "Категория не найдена",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем уникальность нового названия (если изменилось)
|
||||
if (args.input.name !== existingCategory.name) {
|
||||
const duplicateCategory = await prisma.category.findUnique({
|
||||
where: { name: args.input.name }
|
||||
})
|
||||
where: { name: args.input.name },
|
||||
});
|
||||
|
||||
if (duplicateCategory) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Категория с таким названием уже существует'
|
||||
}
|
||||
message: "Категория с таким названием уже существует",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2839,113 +3267,123 @@ export const resolvers = {
|
||||
const category = await prisma.category.update({
|
||||
where: { id: args.id },
|
||||
data: {
|
||||
name: args.input.name
|
||||
}
|
||||
})
|
||||
name: args.input.name,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Категория успешно обновлена',
|
||||
category
|
||||
}
|
||||
message: "Категория успешно обновлена",
|
||||
category,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating category:', error)
|
||||
console.error("Error updating category:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении категории'
|
||||
}
|
||||
message: "Ошибка при обновлении категории",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить категорию
|
||||
deleteCategory: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
deleteCategory: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user && !context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
// Проверяем существование категории
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { id: args.id },
|
||||
include: { products: true }
|
||||
})
|
||||
include: { products: true },
|
||||
});
|
||||
|
||||
if (!existingCategory) {
|
||||
throw new GraphQLError('Категория не найдена')
|
||||
throw new GraphQLError("Категория не найдена");
|
||||
}
|
||||
|
||||
// Проверяем, есть ли товары в этой категории
|
||||
if (existingCategory.products.length > 0) {
|
||||
throw new GraphQLError('Нельзя удалить категорию, в которой есть товары')
|
||||
throw new GraphQLError(
|
||||
"Нельзя удалить категорию, в которой есть товары"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.category.delete({
|
||||
where: { id: args.id }
|
||||
})
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting category:', error)
|
||||
return false
|
||||
console.error("Error deleting category:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Добавить товар в корзину
|
||||
addToCart: async (_: unknown, args: { productId: string; quantity: number }, context: Context) => {
|
||||
addToCart: async (
|
||||
_: unknown,
|
||||
args: { productId: string; quantity: number },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что товар существует и активен
|
||||
const product = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.productId,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар не найден или неактивен'
|
||||
}
|
||||
message: "Товар не найден или неактивен",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
||||
if (product.organizationId === currentUser.organization.id) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Нельзя добавлять собственные товары в корзину'
|
||||
}
|
||||
message: "Нельзя добавлять собственные товары в корзину",
|
||||
};
|
||||
}
|
||||
|
||||
// Найти или создать корзину
|
||||
let cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
});
|
||||
|
||||
if (!cart) {
|
||||
cart = await prisma.cart.create({
|
||||
data: {
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
@ -2954,42 +3392,42 @@ export const resolvers = {
|
||||
where: {
|
||||
cartId_productId: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId
|
||||
}
|
||||
}
|
||||
})
|
||||
productId: args.productId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingCartItem) {
|
||||
// Обновляем количество
|
||||
const newQuantity = existingCartItem.quantity + args.quantity
|
||||
|
||||
const newQuantity = existingCartItem.quantity + args.quantity;
|
||||
|
||||
if (newQuantity > product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`
|
||||
}
|
||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`,
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.cartItem.update({
|
||||
where: { id: existingCartItem.id },
|
||||
data: { quantity: newQuantity }
|
||||
})
|
||||
data: { quantity: newQuantity },
|
||||
});
|
||||
} else {
|
||||
// Создаем новый элемент корзины
|
||||
if (args.quantity > product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`
|
||||
}
|
||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`,
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.cartItem.create({
|
||||
data: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId,
|
||||
quantity: args.quantity
|
||||
}
|
||||
})
|
||||
quantity: args.quantity,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Возвращаем обновленную корзину
|
||||
@ -3003,57 +3441,61 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар добавлен в корзину',
|
||||
cart: updatedCart
|
||||
}
|
||||
message: "Товар добавлен в корзину",
|
||||
cart: updatedCart,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error adding to cart:', error)
|
||||
console.error("Error adding to cart:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при добавлении в корзину'
|
||||
}
|
||||
message: "Ошибка при добавлении в корзину",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить количество товара в корзине
|
||||
updateCartItem: async (_: unknown, args: { productId: string; quantity: number }, context: Context) => {
|
||||
updateCartItem: async (
|
||||
_: unknown,
|
||||
args: { productId: string; quantity: number },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
const cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
});
|
||||
|
||||
if (!cart) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Корзина не найдена'
|
||||
}
|
||||
message: "Корзина не найдена",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем, что товар существует в корзине
|
||||
@ -3061,40 +3503,40 @@ export const resolvers = {
|
||||
where: {
|
||||
cartId_productId: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId
|
||||
}
|
||||
productId: args.productId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
product: true
|
||||
}
|
||||
})
|
||||
product: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!cartItem) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар не найден в корзине'
|
||||
}
|
||||
message: "Товар не найден в корзине",
|
||||
};
|
||||
}
|
||||
|
||||
if (args.quantity <= 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Количество должно быть больше 0'
|
||||
}
|
||||
message: "Количество должно быть больше 0",
|
||||
};
|
||||
}
|
||||
|
||||
if (args.quantity > cartItem.product.quantity) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Недостаточно товара в наличии. Доступно: ${cartItem.product.quantity}`
|
||||
}
|
||||
message: `Недостаточно товара в наличии. Доступно: ${cartItem.product.quantity}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.cartItem.update({
|
||||
where: { id: cartItem.id },
|
||||
data: { quantity: args.quantity }
|
||||
})
|
||||
data: { quantity: args.quantity },
|
||||
});
|
||||
|
||||
// Возвращаем обновленную корзину
|
||||
const updatedCart = await prisma.cart.findUnique({
|
||||
@ -3107,57 +3549,61 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Количество товара обновлено',
|
||||
cart: updatedCart
|
||||
}
|
||||
message: "Количество товара обновлено",
|
||||
cart: updatedCart,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating cart item:', error)
|
||||
console.error("Error updating cart item:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении корзины'
|
||||
}
|
||||
message: "Ошибка при обновлении корзины",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить товар из корзины
|
||||
removeFromCart: async (_: unknown, args: { productId: string }, context: Context) => {
|
||||
removeFromCart: async (
|
||||
_: unknown,
|
||||
args: { productId: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
const cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
});
|
||||
|
||||
if (!cart) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Корзина не найдена'
|
||||
}
|
||||
message: "Корзина не найдена",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3165,10 +3611,10 @@ export const resolvers = {
|
||||
where: {
|
||||
cartId_productId: {
|
||||
cartId: cart.id,
|
||||
productId: args.productId
|
||||
}
|
||||
}
|
||||
})
|
||||
productId: args.productId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Возвращаем обновленную корзину
|
||||
const updatedCart = await prisma.cart.findUnique({
|
||||
@ -3181,109 +3627,113 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар удален из корзины',
|
||||
cart: updatedCart
|
||||
}
|
||||
message: "Товар удален из корзины",
|
||||
cart: updatedCart,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error removing from cart:', error)
|
||||
console.error("Error removing from cart:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при удалении из корзины'
|
||||
}
|
||||
message: "Ошибка при удалении из корзины",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Очистить корзину
|
||||
clearCart: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
const cart = await prisma.cart.findUnique({
|
||||
where: { organizationId: currentUser.organization.id }
|
||||
})
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
});
|
||||
|
||||
if (!cart) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.cartItem.deleteMany({
|
||||
where: { cartId: cart.id }
|
||||
})
|
||||
where: { cartId: cart.id },
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error clearing cart:', error)
|
||||
return false
|
||||
console.error("Error clearing cart:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Добавить товар в избранное
|
||||
addToFavorites: async (_: unknown, args: { productId: string }, context: Context) => {
|
||||
addToFavorites: async (
|
||||
_: unknown,
|
||||
args: { productId: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
// Проверяем, что товар существует и активен
|
||||
const product = await prisma.product.findFirst({
|
||||
where: {
|
||||
id: args.productId,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар не найден или неактивен'
|
||||
}
|
||||
message: "Товар не найден или неактивен",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
||||
if (product.organizationId === currentUser.organization.id) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Нельзя добавлять собственные товары в избранное'
|
||||
}
|
||||
message: "Нельзя добавлять собственные товары в избранное",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3292,25 +3742,25 @@ export const resolvers = {
|
||||
where: {
|
||||
organizationId_productId: {
|
||||
organizationId: currentUser.organization.id,
|
||||
productId: args.productId
|
||||
}
|
||||
}
|
||||
})
|
||||
productId: args.productId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingFavorite) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Товар уже в избранном'
|
||||
}
|
||||
message: "Товар уже в избранном",
|
||||
};
|
||||
}
|
||||
|
||||
// Добавляем товар в избранное
|
||||
await prisma.favorites.create({
|
||||
data: {
|
||||
organizationId: currentUser.organization.id,
|
||||
productId: args.productId
|
||||
}
|
||||
})
|
||||
productId: args.productId,
|
||||
},
|
||||
});
|
||||
|
||||
// Возвращаем обновленный список избранного
|
||||
const favorites = await prisma.favorites.findMany({
|
||||
@ -3321,44 +3771,48 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар добавлен в избранное',
|
||||
favorites: favorites.map(favorite => favorite.product)
|
||||
}
|
||||
message: "Товар добавлен в избранное",
|
||||
favorites: favorites.map((favorite) => favorite.product),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error adding to favorites:', error)
|
||||
console.error("Error adding to favorites:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при добавлении в избранное'
|
||||
}
|
||||
message: "Ошибка при добавлении в избранное",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить товар из избранного
|
||||
removeFromFavorites: async (_: unknown, args: { productId: string }, context: Context) => {
|
||||
removeFromFavorites: async (
|
||||
_: unknown,
|
||||
args: { productId: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3366,9 +3820,9 @@ export const resolvers = {
|
||||
await prisma.favorites.deleteMany({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id,
|
||||
productId: args.productId
|
||||
}
|
||||
})
|
||||
productId: args.productId,
|
||||
},
|
||||
});
|
||||
|
||||
// Возвращаем обновленный список избранного
|
||||
const favorites = await prisma.favorites.findMany({
|
||||
@ -3379,48 +3833,52 @@ export const resolvers = {
|
||||
category: true,
|
||||
organization: {
|
||||
include: {
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Товар удален из избранного',
|
||||
favorites: favorites.map(favorite => favorite.product)
|
||||
}
|
||||
message: "Товар удален из избранного",
|
||||
favorites: favorites.map((favorite) => favorite.product),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error removing from favorites:', error)
|
||||
console.error("Error removing from favorites:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при удалении из избранного'
|
||||
}
|
||||
message: "Ошибка при удалении из избранного",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Создать сотрудника
|
||||
createEmployee: async (_: unknown, args: { input: CreateEmployeeInput }, context: Context) => {
|
||||
createEmployee: async (
|
||||
_: unknown,
|
||||
args: { input: CreateEmployeeInput },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3428,136 +3886,158 @@ export const resolvers = {
|
||||
data: {
|
||||
...args.input,
|
||||
organizationId: currentUser.organization.id,
|
||||
birthDate: args.input.birthDate ? new Date(args.input.birthDate) : undefined,
|
||||
passportDate: args.input.passportDate ? new Date(args.input.passportDate) : undefined,
|
||||
hireDate: new Date(args.input.hireDate)
|
||||
birthDate: args.input.birthDate
|
||||
? new Date(args.input.birthDate)
|
||||
: undefined,
|
||||
passportDate: args.input.passportDate
|
||||
? new Date(args.input.passportDate)
|
||||
: undefined,
|
||||
hireDate: new Date(args.input.hireDate),
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Сотрудник успешно добавлен',
|
||||
employee
|
||||
}
|
||||
message: "Сотрудник успешно добавлен",
|
||||
employee,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating employee:', error)
|
||||
console.error("Error creating employee:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании сотрудника'
|
||||
}
|
||||
message: "Ошибка при создании сотрудника",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить сотрудника
|
||||
updateEmployee: async (_: unknown, args: { id: string; input: UpdateEmployeeInput }, context: Context) => {
|
||||
updateEmployee: async (
|
||||
_: unknown,
|
||||
args: { id: string; input: UpdateEmployeeInput },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
try {
|
||||
const employee = await prisma.employee.update({
|
||||
where: {
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
data: {
|
||||
...args.input,
|
||||
birthDate: args.input.birthDate ? new Date(args.input.birthDate) : undefined,
|
||||
passportDate: args.input.passportDate ? new Date(args.input.passportDate) : undefined,
|
||||
hireDate: args.input.hireDate ? new Date(args.input.hireDate) : undefined
|
||||
birthDate: args.input.birthDate
|
||||
? new Date(args.input.birthDate)
|
||||
: undefined,
|
||||
passportDate: args.input.passportDate
|
||||
? new Date(args.input.passportDate)
|
||||
: undefined,
|
||||
hireDate: args.input.hireDate
|
||||
? new Date(args.input.hireDate)
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Сотрудник успешно обновлен',
|
||||
employee
|
||||
}
|
||||
message: "Сотрудник успешно обновлен",
|
||||
employee,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating employee:', error)
|
||||
console.error("Error updating employee:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении сотрудника'
|
||||
}
|
||||
message: "Ошибка при обновлении сотрудника",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить сотрудника
|
||||
deleteEmployee: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
deleteEmployee: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.employee.delete({
|
||||
where: {
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting employee:', error)
|
||||
return false
|
||||
console.error("Error deleting employee:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить табель сотрудника
|
||||
updateEmployeeSchedule: async (_: unknown, args: { input: UpdateScheduleInput }, context: Context) => {
|
||||
updateEmployeeSchedule: async (
|
||||
_: unknown,
|
||||
args: { input: UpdateScheduleInput },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
||||
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3565,12 +4045,12 @@ export const resolvers = {
|
||||
const employee = await prisma.employee.findFirst({
|
||||
where: {
|
||||
id: args.input.employeeId,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!employee) {
|
||||
throw new GraphQLError('Сотрудник не найден')
|
||||
throw new GraphQLError("Сотрудник не найден");
|
||||
}
|
||||
|
||||
// Создаем или обновляем запись табеля
|
||||
@ -3578,73 +4058,89 @@ export const resolvers = {
|
||||
where: {
|
||||
employeeId_date: {
|
||||
employeeId: args.input.employeeId,
|
||||
date: new Date(args.input.date)
|
||||
}
|
||||
date: new Date(args.input.date),
|
||||
},
|
||||
},
|
||||
create: {
|
||||
employeeId: args.input.employeeId,
|
||||
date: new Date(args.input.date),
|
||||
status: args.input.status,
|
||||
hoursWorked: args.input.hoursWorked,
|
||||
notes: args.input.notes
|
||||
notes: args.input.notes,
|
||||
},
|
||||
update: {
|
||||
status: args.input.status,
|
||||
hoursWorked: args.input.hoursWorked,
|
||||
notes: args.input.notes
|
||||
}
|
||||
})
|
||||
notes: args.input.notes,
|
||||
},
|
||||
});
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error updating employee schedule:', error)
|
||||
return false
|
||||
console.error("Error updating employee schedule:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Создать поставку Wildberries
|
||||
createWildberriesSupply: async (_: unknown, args: { input: { cards: Array<{ price: number; discountedPrice?: number; selectedQuantity: number; selectedServices?: string[] }> } }, context: Context) => {
|
||||
createWildberriesSupply: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
cards: Array<{
|
||||
price: number;
|
||||
discountedPrice?: number;
|
||||
selectedQuantity: number;
|
||||
selectedServices?: string[];
|
||||
}>;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
// Пока что просто логируем данные, так как таблицы еще нет
|
||||
console.log('Создание поставки Wildberries с данными:', args.input)
|
||||
|
||||
const totalAmount = args.input.cards.reduce((sum: number, card) => {
|
||||
const cardPrice = card.discountedPrice || card.price
|
||||
const servicesPrice = (card.selectedServices?.length || 0) * 50
|
||||
return sum + (cardPrice + servicesPrice) * card.selectedQuantity
|
||||
}, 0)
|
||||
console.log("Создание поставки Wildberries с данными:", args.input);
|
||||
|
||||
const totalItems = args.input.cards.reduce((sum: number, card) => sum + card.selectedQuantity, 0)
|
||||
const totalAmount = args.input.cards.reduce((sum: number, card) => {
|
||||
const cardPrice = card.discountedPrice || card.price;
|
||||
const servicesPrice = (card.selectedServices?.length || 0) * 50;
|
||||
return sum + (cardPrice + servicesPrice) * card.selectedQuantity;
|
||||
}, 0);
|
||||
|
||||
const totalItems = args.input.cards.reduce(
|
||||
(sum: number, card) => sum + card.selectedQuantity,
|
||||
0
|
||||
);
|
||||
|
||||
// Временная заглушка - вернем success без создания в БД
|
||||
return {
|
||||
success: true,
|
||||
message: `Поставка создана успешно! Товаров: ${totalItems}, Сумма: ${totalAmount} руб.`,
|
||||
supply: null // Временно null
|
||||
}
|
||||
supply: null, // Временно null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error creating Wildberries supply:', error)
|
||||
console.error("Error creating Wildberries supply:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании поставки Wildberries'
|
||||
}
|
||||
message: "Ошибка при создании поставки Wildberries",
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Резолверы типов
|
||||
@ -3652,158 +4148,180 @@ export const resolvers = {
|
||||
users: async (parent: { id: string; users?: unknown[] }) => {
|
||||
// Если пользователи уже загружены через include, возвращаем их
|
||||
if (parent.users) {
|
||||
return parent.users
|
||||
return parent.users;
|
||||
}
|
||||
|
||||
|
||||
// Иначе загружаем отдельно
|
||||
return await prisma.user.findMany({
|
||||
where: { organizationId: parent.id }
|
||||
})
|
||||
}
|
||||
where: { organizationId: parent.id },
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
Cart: {
|
||||
totalPrice: (parent: { items: Array<{ product: { price: number }, quantity: number }> }) => {
|
||||
totalPrice: (parent: {
|
||||
items: Array<{ product: { price: number }; quantity: number }>;
|
||||
}) => {
|
||||
return parent.items.reduce((total, item) => {
|
||||
return total + (Number(item.product.price) * item.quantity)
|
||||
}, 0)
|
||||
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)
|
||||
}
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
return parent.product.quantity;
|
||||
},
|
||||
},
|
||||
|
||||
User: {
|
||||
organization: async (parent: { organizationId?: string; organization?: unknown }) => {
|
||||
organization: async (parent: {
|
||||
organizationId?: string;
|
||||
organization?: unknown;
|
||||
}) => {
|
||||
// Если организация уже загружена через include, возвращаем её
|
||||
if (parent.organization) {
|
||||
return parent.organization
|
||||
return parent.organization;
|
||||
}
|
||||
|
||||
|
||||
// Иначе загружаем отдельно если есть organizationId
|
||||
if (parent.organizationId) {
|
||||
return await prisma.organization.findUnique({
|
||||
where: { id: parent.organizationId },
|
||||
include: {
|
||||
apiKeys: true,
|
||||
users: true
|
||||
}
|
||||
})
|
||||
users: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
|
||||
Message: {
|
||||
type: (parent: { type?: string | null }) => {
|
||||
return parent.type || 'TEXT'
|
||||
return parent.type || "TEXT";
|
||||
},
|
||||
createdAt: (parent: { createdAt: Date | string }) => {
|
||||
if (parent.createdAt instanceof Date) {
|
||||
return parent.createdAt.toISOString()
|
||||
return parent.createdAt.toISOString();
|
||||
}
|
||||
return parent.createdAt
|
||||
return parent.createdAt;
|
||||
},
|
||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||
if (parent.updatedAt instanceof Date) {
|
||||
return parent.updatedAt.toISOString()
|
||||
return parent.updatedAt.toISOString();
|
||||
}
|
||||
return parent.updatedAt
|
||||
}
|
||||
return parent.updatedAt;
|
||||
},
|
||||
},
|
||||
|
||||
Employee: {
|
||||
birthDate: (parent: { birthDate?: Date | string | null }) => {
|
||||
if (!parent.birthDate) return null
|
||||
if (!parent.birthDate) return null;
|
||||
if (parent.birthDate instanceof Date) {
|
||||
return parent.birthDate.toISOString()
|
||||
return parent.birthDate.toISOString();
|
||||
}
|
||||
return parent.birthDate
|
||||
return parent.birthDate;
|
||||
},
|
||||
passportDate: (parent: { passportDate?: Date | string | null }) => {
|
||||
if (!parent.passportDate) return null
|
||||
if (!parent.passportDate) return null;
|
||||
if (parent.passportDate instanceof Date) {
|
||||
return parent.passportDate.toISOString()
|
||||
return parent.passportDate.toISOString();
|
||||
}
|
||||
return parent.passportDate
|
||||
return parent.passportDate;
|
||||
},
|
||||
hireDate: (parent: { hireDate: Date | string }) => {
|
||||
if (parent.hireDate instanceof Date) {
|
||||
return parent.hireDate.toISOString()
|
||||
return parent.hireDate.toISOString();
|
||||
}
|
||||
return parent.hireDate
|
||||
return parent.hireDate;
|
||||
},
|
||||
createdAt: (parent: { createdAt: Date | string }) => {
|
||||
if (parent.createdAt instanceof Date) {
|
||||
return parent.createdAt.toISOString()
|
||||
return parent.createdAt.toISOString();
|
||||
}
|
||||
return parent.createdAt
|
||||
return parent.createdAt;
|
||||
},
|
||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||
if (parent.updatedAt instanceof Date) {
|
||||
return parent.updatedAt.toISOString()
|
||||
return parent.updatedAt.toISOString();
|
||||
}
|
||||
return parent.updatedAt
|
||||
}
|
||||
return parent.updatedAt;
|
||||
},
|
||||
},
|
||||
|
||||
EmployeeSchedule: {
|
||||
date: (parent: { date: Date | string }) => {
|
||||
if (parent.date instanceof Date) {
|
||||
return parent.date.toISOString()
|
||||
return parent.date.toISOString();
|
||||
}
|
||||
return parent.date
|
||||
return parent.date;
|
||||
},
|
||||
createdAt: (parent: { createdAt: Date | string }) => {
|
||||
if (parent.createdAt instanceof Date) {
|
||||
return parent.createdAt.toISOString()
|
||||
return parent.createdAt.toISOString();
|
||||
}
|
||||
return parent.createdAt
|
||||
return parent.createdAt;
|
||||
},
|
||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||
if (parent.updatedAt instanceof Date) {
|
||||
return parent.updatedAt.toISOString()
|
||||
return parent.updatedAt.toISOString();
|
||||
}
|
||||
return parent.updatedAt
|
||||
return parent.updatedAt;
|
||||
},
|
||||
employee: async (parent: { employeeId: string }) => {
|
||||
return await prisma.employee.findUnique({
|
||||
where: { id: parent.employeeId }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
where: { id: parent.employeeId },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Логистические мутации
|
||||
const logisticsMutations = {
|
||||
// Создать логистический маршрут
|
||||
createLogistics: async (_: unknown, args: { input: { fromLocation: string; toLocation: string; priceUnder1m3: number; priceOver1m3: number; description?: string } }, context: Context) => {
|
||||
createLogistics: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
input: {
|
||||
fromLocation: string;
|
||||
toLocation: string;
|
||||
priceUnder1m3: number;
|
||||
priceOver1m3: number;
|
||||
description?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3814,44 +4332,57 @@ const logisticsMutations = {
|
||||
priceUnder1m3: args.input.priceUnder1m3,
|
||||
priceOver1m3: args.input.priceOver1m3,
|
||||
description: args.input.description,
|
||||
organizationId: currentUser.organization.id
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Logistics created:', logistics.id)
|
||||
console.log("✅ Logistics created:", logistics.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Логистический маршрут создан',
|
||||
logistics
|
||||
}
|
||||
message: "Логистический маршрут создан",
|
||||
logistics,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ Error creating logistics:', error)
|
||||
console.error("❌ Error creating logistics:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при создании логистического маршрута'
|
||||
}
|
||||
message: "Ошибка при создании логистического маршрута",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Обновить логистический маршрут
|
||||
updateLogistics: async (_: unknown, args: { id: string; input: { fromLocation: string; toLocation: string; priceUnder1m3: number; priceOver1m3: number; description?: string } }, context: Context) => {
|
||||
updateLogistics: async (
|
||||
_: unknown,
|
||||
args: {
|
||||
id: string;
|
||||
input: {
|
||||
fromLocation: string;
|
||||
toLocation: string;
|
||||
priceUnder1m3: number;
|
||||
priceOver1m3: number;
|
||||
description?: string;
|
||||
};
|
||||
},
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3859,12 +4390,12 @@ const logisticsMutations = {
|
||||
const existingLogistics = await prisma.logistics.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingLogistics) {
|
||||
throw new GraphQLError('Логистический маршрут не найден')
|
||||
throw new GraphQLError("Логистический маршрут не найден");
|
||||
}
|
||||
|
||||
const logistics = await prisma.logistics.update({
|
||||
@ -3874,44 +4405,48 @@ const logisticsMutations = {
|
||||
toLocation: args.input.toLocation,
|
||||
priceUnder1m3: args.input.priceUnder1m3,
|
||||
priceOver1m3: args.input.priceOver1m3,
|
||||
description: args.input.description
|
||||
description: args.input.description,
|
||||
},
|
||||
include: {
|
||||
organization: true
|
||||
}
|
||||
})
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Logistics updated:', logistics.id)
|
||||
console.log("✅ Logistics updated:", logistics.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Логистический маршрут обновлен',
|
||||
logistics
|
||||
}
|
||||
message: "Логистический маршрут обновлен",
|
||||
logistics,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ Error updating logistics:', error)
|
||||
console.error("❌ Error updating logistics:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при обновлении логистического маршрута'
|
||||
}
|
||||
message: "Ошибка при обновлении логистического маршрута",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Удалить логистический маршрут
|
||||
deleteLogistics: async (_: unknown, args: { id: string }, context: Context) => {
|
||||
deleteLogistics: async (
|
||||
_: unknown,
|
||||
args: { id: string },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new GraphQLError('Требуется авторизация', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
where: { id: context.user.id },
|
||||
include: { organization: true }
|
||||
})
|
||||
include: { organization: true },
|
||||
});
|
||||
|
||||
if (!currentUser?.organization) {
|
||||
throw new GraphQLError('У пользователя нет организации')
|
||||
throw new GraphQLError("У пользователя нет организации");
|
||||
}
|
||||
|
||||
try {
|
||||
@ -3919,192 +4454,202 @@ const logisticsMutations = {
|
||||
const existingLogistics = await prisma.logistics.findFirst({
|
||||
where: {
|
||||
id: args.id,
|
||||
organizationId: currentUser.organization.id
|
||||
}
|
||||
})
|
||||
organizationId: currentUser.organization.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingLogistics) {
|
||||
throw new GraphQLError('Логистический маршрут не найден')
|
||||
throw new GraphQLError("Логистический маршрут не найден");
|
||||
}
|
||||
|
||||
await prisma.logistics.delete({
|
||||
where: { id: args.id }
|
||||
})
|
||||
where: { id: args.id },
|
||||
});
|
||||
|
||||
console.log('✅ Logistics deleted:', args.id)
|
||||
return true
|
||||
console.log("✅ Logistics deleted:", args.id);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Error deleting logistics:', error)
|
||||
return false
|
||||
console.error("❌ Error deleting logistics:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Добавляем логистические мутации к основным резолверам
|
||||
resolvers.Mutation = {
|
||||
...resolvers.Mutation,
|
||||
...logisticsMutations
|
||||
}
|
||||
...logisticsMutations,
|
||||
};
|
||||
|
||||
// Админ резолверы
|
||||
const adminQueries = {
|
||||
adminMe: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация администратора', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация администратора", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const admin = await prisma.admin.findUnique({
|
||||
where: { id: context.admin.id }
|
||||
})
|
||||
where: { id: context.admin.id },
|
||||
});
|
||||
|
||||
if (!admin) {
|
||||
throw new GraphQLError('Администратор не найден')
|
||||
throw new GraphQLError("Администратор не найден");
|
||||
}
|
||||
|
||||
return admin
|
||||
return admin;
|
||||
},
|
||||
|
||||
allUsers: async (_: unknown, args: { search?: string; limit?: number; offset?: number }, context: Context) => {
|
||||
allUsers: async (
|
||||
_: unknown,
|
||||
args: { search?: string; limit?: number; offset?: number },
|
||||
context: Context
|
||||
) => {
|
||||
if (!context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация администратора', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация администратора", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
const limit = args.limit || 50
|
||||
const offset = args.offset || 0
|
||||
|
||||
const limit = args.limit || 50;
|
||||
const offset = args.offset || 0;
|
||||
|
||||
// Строим условие поиска
|
||||
const whereCondition: Prisma.UserWhereInput = args.search
|
||||
? {
|
||||
OR: [
|
||||
{ phone: { contains: args.search, mode: 'insensitive' } },
|
||||
{ managerName: { contains: args.search, mode: 'insensitive' } },
|
||||
{
|
||||
{ phone: { contains: args.search, mode: "insensitive" } },
|
||||
{ managerName: { contains: args.search, mode: "insensitive" } },
|
||||
{
|
||||
organization: {
|
||||
OR: [
|
||||
{ name: { contains: args.search, mode: 'insensitive' } },
|
||||
{ fullName: { contains: args.search, mode: 'insensitive' } },
|
||||
{ inn: { contains: args.search, mode: 'insensitive' } }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
{ name: { contains: args.search, mode: "insensitive" } },
|
||||
{ fullName: { contains: args.search, mode: "insensitive" } },
|
||||
{ inn: { contains: args.search, mode: "insensitive" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}
|
||||
: {};
|
||||
|
||||
// Получаем пользователей с пагинацией
|
||||
const [users, total] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
where: whereCondition,
|
||||
include: {
|
||||
organization: true
|
||||
organization: true,
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.user.count({
|
||||
where: whereCondition
|
||||
})
|
||||
])
|
||||
where: whereCondition,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
hasMore: offset + limit < total
|
||||
}
|
||||
}
|
||||
}
|
||||
hasMore: offset + limit < total,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const adminMutations = {
|
||||
adminLogin: async (_: unknown, args: { username: string; password: string }) => {
|
||||
adminLogin: async (
|
||||
_: unknown,
|
||||
args: { username: string; password: string }
|
||||
) => {
|
||||
try {
|
||||
// Найти администратора
|
||||
const admin = await prisma.admin.findUnique({
|
||||
where: { username: args.username }
|
||||
})
|
||||
where: { username: args.username },
|
||||
});
|
||||
|
||||
if (!admin) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверные учетные данные'
|
||||
}
|
||||
message: "Неверные учетные данные",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверить активность
|
||||
if (!admin.isActive) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Аккаунт заблокирован'
|
||||
}
|
||||
message: "Аккаунт заблокирован",
|
||||
};
|
||||
}
|
||||
|
||||
// Проверить пароль
|
||||
const isPasswordValid = await bcrypt.compare(args.password, admin.password)
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
args.password,
|
||||
admin.password
|
||||
);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверные учетные данные'
|
||||
}
|
||||
message: "Неверные учетные данные",
|
||||
};
|
||||
}
|
||||
|
||||
// Обновить время последнего входа
|
||||
await prisma.admin.update({
|
||||
where: { id: admin.id },
|
||||
data: { lastLogin: new Date() }
|
||||
})
|
||||
data: { lastLogin: new Date() },
|
||||
});
|
||||
|
||||
// Создать токен
|
||||
const token = jwt.sign(
|
||||
{
|
||||
{
|
||||
adminId: admin.id,
|
||||
username: admin.username,
|
||||
type: 'admin'
|
||||
type: "admin",
|
||||
},
|
||||
process.env.JWT_SECRET!,
|
||||
{ expiresIn: '24h' }
|
||||
)
|
||||
{ expiresIn: "24h" }
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Успешная авторизация',
|
||||
message: "Успешная авторизация",
|
||||
token,
|
||||
admin: {
|
||||
...admin,
|
||||
password: undefined // Не возвращаем пароль
|
||||
}
|
||||
}
|
||||
password: undefined, // Не возвращаем пароль
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error)
|
||||
console.error("Admin login error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка авторизации'
|
||||
}
|
||||
message: "Ошибка авторизации",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
adminLogout: async (_: unknown, __: unknown, context: Context) => {
|
||||
if (!context.admin) {
|
||||
throw new GraphQLError('Требуется авторизация администратора', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
throw new GraphQLError("Требуется авторизация администратора", {
|
||||
extensions: { code: "UNAUTHENTICATED" },
|
||||
});
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
// Добавляем админ запросы и мутации к основным резолверам
|
||||
resolvers.Query = {
|
||||
...resolvers.Query,
|
||||
...adminQueries
|
||||
}
|
||||
...adminQueries,
|
||||
};
|
||||
|
||||
resolvers.Mutation = {
|
||||
...resolvers.Mutation,
|
||||
...adminMutations
|
||||
}
|
||||
...adminMutations,
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { gql } from 'graphql-tag'
|
||||
import { gql } from "graphql-tag";
|
||||
|
||||
export const typeDefs = gql`
|
||||
scalar DateTime
|
||||
@ -6,31 +6,34 @@ export const typeDefs = gql`
|
||||
type Query {
|
||||
me: User
|
||||
organization(id: ID!): Organization
|
||||
|
||||
|
||||
# Поиск организаций по типу для добавления в контрагенты
|
||||
searchOrganizations(type: OrganizationType, search: String): [Organization!]!
|
||||
|
||||
searchOrganizations(
|
||||
type: OrganizationType
|
||||
search: String
|
||||
): [Organization!]!
|
||||
|
||||
# Мои контрагенты
|
||||
myCounterparties: [Organization!]!
|
||||
|
||||
|
||||
# Входящие заявки
|
||||
incomingRequests: [CounterpartyRequest!]!
|
||||
|
||||
|
||||
# Исходящие заявки
|
||||
outgoingRequests: [CounterpartyRequest!]!
|
||||
|
||||
|
||||
# Сообщения с контрагентом
|
||||
messages(counterpartyId: ID!, limit: Int, offset: Int): [Message!]!
|
||||
|
||||
|
||||
# Список чатов (последние сообщения с каждым контрагентом)
|
||||
conversations: [Conversation!]!
|
||||
|
||||
|
||||
# Услуги организации
|
||||
myServices: [Service!]!
|
||||
|
||||
|
||||
# Расходники организации
|
||||
mySupplies: [Supply!]!
|
||||
|
||||
|
||||
# Логистика организации
|
||||
myLogistics: [Logistics!]!
|
||||
|
||||
@ -39,26 +42,30 @@ export const typeDefs = gql`
|
||||
|
||||
# Товары оптовика
|
||||
myProducts: [Product!]!
|
||||
|
||||
|
||||
# Все товары всех оптовиков для маркета
|
||||
allProducts(search: String, category: String): [Product!]!
|
||||
|
||||
|
||||
# Все категории
|
||||
categories: [Category!]!
|
||||
|
||||
|
||||
# Корзина пользователя
|
||||
myCart: Cart
|
||||
|
||||
|
||||
# Избранные товары пользователя
|
||||
myFavorites: [Product!]!
|
||||
|
||||
|
||||
# Сотрудники организации
|
||||
myEmployees: [Employee!]!
|
||||
employee(id: ID!): Employee
|
||||
|
||||
|
||||
# Табель сотрудника за месяц
|
||||
employeeSchedule(employeeId: ID!, year: Int!, month: Int!): [EmployeeSchedule!]!
|
||||
|
||||
employeeSchedule(
|
||||
employeeId: ID!
|
||||
year: Int!
|
||||
month: Int!
|
||||
): [EmployeeSchedule!]!
|
||||
|
||||
# Админ запросы
|
||||
adminMe: Admin
|
||||
allUsers(search: String, limit: Int, offset: Int): UsersResponse!
|
||||
@ -68,75 +75,108 @@ export const typeDefs = gql`
|
||||
# Авторизация через SMS
|
||||
sendSmsCode(phone: String!): SmsResponse!
|
||||
verifySmsCode(phone: String!, code: String!): AuthResponse!
|
||||
|
||||
|
||||
# Валидация ИНН
|
||||
verifyInn(inn: String!): InnValidationResponse!
|
||||
|
||||
|
||||
# Обновление профиля пользователя
|
||||
updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResponse!
|
||||
|
||||
updateUserProfile(
|
||||
input: UpdateUserProfileInput!
|
||||
): UpdateUserProfileResponse!
|
||||
|
||||
# Обновление данных организации по ИНН
|
||||
updateOrganizationByInn(inn: String!): UpdateOrganizationResponse!
|
||||
|
||||
|
||||
# Регистрация организации
|
||||
registerFulfillmentOrganization(input: FulfillmentRegistrationInput!): AuthResponse!
|
||||
registerFulfillmentOrganization(
|
||||
input: FulfillmentRegistrationInput!
|
||||
): AuthResponse!
|
||||
registerSellerOrganization(input: SellerRegistrationInput!): AuthResponse!
|
||||
|
||||
|
||||
# Работа с API ключами
|
||||
addMarketplaceApiKey(input: MarketplaceApiKeyInput!): ApiKeyResponse!
|
||||
removeMarketplaceApiKey(marketplace: MarketplaceType!): Boolean!
|
||||
|
||||
|
||||
# Выход из системы
|
||||
logout: Boolean!
|
||||
|
||||
|
||||
# Работа с контрагентами
|
||||
sendCounterpartyRequest(organizationId: ID!, message: String): CounterpartyRequestResponse!
|
||||
respondToCounterpartyRequest(requestId: ID!, accept: Boolean!): CounterpartyRequestResponse!
|
||||
sendCounterpartyRequest(
|
||||
organizationId: ID!
|
||||
message: String
|
||||
): CounterpartyRequestResponse!
|
||||
respondToCounterpartyRequest(
|
||||
requestId: ID!
|
||||
accept: Boolean!
|
||||
): CounterpartyRequestResponse!
|
||||
cancelCounterpartyRequest(requestId: ID!): Boolean!
|
||||
removeCounterparty(organizationId: ID!): Boolean!
|
||||
|
||||
|
||||
# Работа с сообщениями
|
||||
sendMessage(receiverOrganizationId: ID!, content: String, type: MessageType = TEXT): MessageResponse!
|
||||
sendVoiceMessage(receiverOrganizationId: ID!, voiceUrl: String!, voiceDuration: Int!): MessageResponse!
|
||||
sendImageMessage(receiverOrganizationId: ID!, fileUrl: String!, fileName: String!, fileSize: Int!, fileType: String!): MessageResponse!
|
||||
sendFileMessage(receiverOrganizationId: ID!, fileUrl: String!, fileName: String!, fileSize: Int!, fileType: String!): MessageResponse!
|
||||
sendMessage(
|
||||
receiverOrganizationId: ID!
|
||||
content: String
|
||||
type: MessageType = TEXT
|
||||
): MessageResponse!
|
||||
sendVoiceMessage(
|
||||
receiverOrganizationId: ID!
|
||||
voiceUrl: String!
|
||||
voiceDuration: Int!
|
||||
): MessageResponse!
|
||||
sendImageMessage(
|
||||
receiverOrganizationId: ID!
|
||||
fileUrl: String!
|
||||
fileName: String!
|
||||
fileSize: Int!
|
||||
fileType: String!
|
||||
): MessageResponse!
|
||||
sendFileMessage(
|
||||
receiverOrganizationId: ID!
|
||||
fileUrl: String!
|
||||
fileName: String!
|
||||
fileSize: Int!
|
||||
fileType: String!
|
||||
): MessageResponse!
|
||||
markMessagesAsRead(conversationId: ID!): Boolean!
|
||||
|
||||
|
||||
# Работа с услугами
|
||||
createService(input: ServiceInput!): ServiceResponse!
|
||||
updateService(id: ID!, input: ServiceInput!): ServiceResponse!
|
||||
deleteService(id: ID!): Boolean!
|
||||
|
||||
|
||||
# Работа с расходниками
|
||||
createSupply(input: SupplyInput!): SupplyResponse!
|
||||
updateSupply(id: ID!, input: SupplyInput!): SupplyResponse!
|
||||
deleteSupply(id: ID!): Boolean!
|
||||
|
||||
|
||||
# Заказы поставок расходников
|
||||
createSupplyOrder(input: SupplyOrderInput!): SupplyOrderResponse!
|
||||
|
||||
# Работа с логистикой
|
||||
createLogistics(input: LogisticsInput!): LogisticsResponse!
|
||||
updateLogistics(id: ID!, input: LogisticsInput!): LogisticsResponse!
|
||||
deleteLogistics(id: ID!): Boolean!
|
||||
|
||||
|
||||
# Работа с товарами (для оптовиков)
|
||||
createProduct(input: ProductInput!): ProductResponse!
|
||||
updateProduct(id: ID!, input: ProductInput!): ProductResponse!
|
||||
deleteProduct(id: ID!): Boolean!
|
||||
|
||||
|
||||
# Работа с категориями
|
||||
createCategory(input: CategoryInput!): CategoryResponse!
|
||||
updateCategory(id: ID!, input: CategoryInput!): CategoryResponse!
|
||||
deleteCategory(id: ID!): Boolean!
|
||||
|
||||
|
||||
# Работа с корзиной
|
||||
addToCart(productId: ID!, quantity: Int = 1): CartResponse!
|
||||
updateCartItem(productId: ID!, quantity: Int!): CartResponse!
|
||||
removeFromCart(productId: ID!): CartResponse!
|
||||
clearCart: Boolean!
|
||||
|
||||
|
||||
# Работа с избранным
|
||||
addToFavorites(productId: ID!): FavoritesResponse!
|
||||
removeFromFavorites(productId: ID!): FavoritesResponse!
|
||||
|
||||
|
||||
# Работа с сотрудниками
|
||||
createEmployee(input: CreateEmployeeInput!): EmployeeResponse!
|
||||
updateEmployee(id: ID!, input: UpdateEmployeeInput!): EmployeeResponse!
|
||||
@ -216,14 +256,14 @@ export const typeDefs = gql`
|
||||
input UpdateUserProfileInput {
|
||||
# Аватар пользователя
|
||||
avatar: String
|
||||
|
||||
|
||||
# Контактные данные организации
|
||||
orgPhone: String
|
||||
managerName: String
|
||||
telegram: String
|
||||
whatsapp: String
|
||||
email: String
|
||||
|
||||
|
||||
# Банковские данные
|
||||
bankName: String
|
||||
bik: String
|
||||
@ -422,6 +462,55 @@ export const typeDefs = gql`
|
||||
supply: Supply
|
||||
}
|
||||
|
||||
# Типы для заказов поставок расходников
|
||||
type SupplyOrder {
|
||||
id: ID!
|
||||
partnerId: ID!
|
||||
partner: Organization!
|
||||
deliveryDate: DateTime!
|
||||
status: SupplyOrderStatus!
|
||||
totalAmount: Float!
|
||||
totalItems: Int!
|
||||
items: [SupplyOrderItem!]!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
organization: Organization!
|
||||
}
|
||||
|
||||
type SupplyOrderItem {
|
||||
id: ID!
|
||||
productId: ID!
|
||||
product: Product!
|
||||
quantity: Int!
|
||||
price: Float!
|
||||
totalPrice: Float!
|
||||
}
|
||||
|
||||
enum SupplyOrderStatus {
|
||||
PENDING
|
||||
CONFIRMED
|
||||
IN_TRANSIT
|
||||
DELIVERED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
input SupplyOrderInput {
|
||||
partnerId: ID!
|
||||
deliveryDate: DateTime!
|
||||
items: [SupplyOrderItemInput!]!
|
||||
}
|
||||
|
||||
input SupplyOrderItemInput {
|
||||
productId: ID!
|
||||
quantity: Int!
|
||||
}
|
||||
|
||||
type SupplyOrderResponse {
|
||||
success: Boolean!
|
||||
message: String!
|
||||
order: SupplyOrder
|
||||
}
|
||||
|
||||
# Типы для логистики
|
||||
type Logistics {
|
||||
id: ID!
|
||||
@ -778,4 +867,4 @@ export const typeDefs = gql`
|
||||
message: String!
|
||||
supply: WildberriesSupply
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
Reference in New Issue
Block a user