Добавлены модели и мутации для управления заказами поставок расходников. Обновлены схемы GraphQL с новыми типами и полями для SupplyOrder и SupplyOrderItem. Реализована логика создания заказов поставок с соответствующими полями и статусами. Обновлены компоненты интерфейса для улучшения навигации и взаимодействия с новыми функциями.
This commit is contained in:
@ -97,6 +97,8 @@ model Organization {
|
|||||||
supplies Supply[]
|
supplies Supply[]
|
||||||
users User[]
|
users User[]
|
||||||
logistics Logistics[]
|
logistics Logistics[]
|
||||||
|
supplyOrders SupplyOrder[]
|
||||||
|
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
|
||||||
|
|
||||||
@@map("organizations")
|
@@map("organizations")
|
||||||
}
|
}
|
||||||
@ -229,6 +231,7 @@ model Product {
|
|||||||
organizationId String
|
organizationId String
|
||||||
cartItems CartItem[]
|
cartItems CartItem[]
|
||||||
favorites Favorites[]
|
favorites Favorites[]
|
||||||
|
supplyOrderItems SupplyOrderItem[]
|
||||||
category Category? @relation(fields: [categoryId], references: [id])
|
category Category? @relation(fields: [categoryId], references: [id])
|
||||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@ -363,6 +366,14 @@ enum ScheduleStatus {
|
|||||||
ABSENT
|
ABSENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SupplyOrderStatus {
|
||||||
|
PENDING
|
||||||
|
CONFIRMED
|
||||||
|
IN_TRANSIT
|
||||||
|
DELIVERED
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
model Logistics {
|
model Logistics {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
fromLocation String
|
fromLocation String
|
||||||
@ -377,3 +388,36 @@ model Logistics {
|
|||||||
|
|
||||||
@@map("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>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
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"
|
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" />
|
<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`
|
export const CREATE_LOGISTICS = gql`
|
||||||
mutation CreateLogistics($input: LogisticsInput!) {
|
mutation CreateLogistics($input: LogisticsInput!) {
|
||||||
|
@ -1,190 +1,198 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from "jsonwebtoken";
|
||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from "bcryptjs";
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from "graphql";
|
||||||
import { GraphQLScalarType, Kind } from 'graphql'
|
import { GraphQLScalarType, Kind } from "graphql";
|
||||||
import { prisma } from '@/lib/prisma'
|
import { prisma } from "@/lib/prisma";
|
||||||
import { SmsService } from '@/services/sms-service'
|
import { SmsService } from "@/services/sms-service";
|
||||||
import { DaDataService } from '@/services/dadata-service'
|
import { DaDataService } from "@/services/dadata-service";
|
||||||
import { MarketplaceService } from '@/services/marketplace-service'
|
import { MarketplaceService } from "@/services/marketplace-service";
|
||||||
import { Prisma } from '@prisma/client'
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
// Сервисы
|
// Сервисы
|
||||||
const smsService = new SmsService()
|
const smsService = new SmsService();
|
||||||
const dadataService = new DaDataService()
|
const dadataService = new DaDataService();
|
||||||
const marketplaceService = new MarketplaceService()
|
const marketplaceService = new MarketplaceService();
|
||||||
|
|
||||||
// Интерфейсы для типизации
|
// Интерфейсы для типизации
|
||||||
interface Context {
|
interface Context {
|
||||||
user?: {
|
user?: {
|
||||||
id: string
|
id: string;
|
||||||
phone: string
|
phone: string;
|
||||||
}
|
};
|
||||||
admin?: {
|
admin?: {
|
||||||
id: string
|
id: string;
|
||||||
username: string
|
username: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateEmployeeInput {
|
interface CreateEmployeeInput {
|
||||||
firstName: string
|
firstName: string;
|
||||||
lastName: string
|
lastName: string;
|
||||||
middleName?: string
|
middleName?: string;
|
||||||
birthDate?: string
|
birthDate?: string;
|
||||||
avatar?: string
|
avatar?: string;
|
||||||
passportPhoto?: string
|
passportPhoto?: string;
|
||||||
passportSeries?: string
|
passportSeries?: string;
|
||||||
passportNumber?: string
|
passportNumber?: string;
|
||||||
passportIssued?: string
|
passportIssued?: string;
|
||||||
passportDate?: string
|
passportDate?: string;
|
||||||
address?: string
|
address?: string;
|
||||||
position: string
|
position: string;
|
||||||
department?: string
|
department?: string;
|
||||||
hireDate: string
|
hireDate: string;
|
||||||
salary?: number
|
salary?: number;
|
||||||
phone: string
|
phone: string;
|
||||||
email?: string
|
email?: string;
|
||||||
telegram?: string
|
telegram?: string;
|
||||||
whatsapp?: string
|
whatsapp?: string;
|
||||||
emergencyContact?: string
|
emergencyContact?: string;
|
||||||
emergencyPhone?: string
|
emergencyPhone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateEmployeeInput {
|
interface UpdateEmployeeInput {
|
||||||
firstName?: string
|
firstName?: string;
|
||||||
lastName?: string
|
lastName?: string;
|
||||||
middleName?: string
|
middleName?: string;
|
||||||
birthDate?: string
|
birthDate?: string;
|
||||||
avatar?: string
|
avatar?: string;
|
||||||
passportPhoto?: string
|
passportPhoto?: string;
|
||||||
passportSeries?: string
|
passportSeries?: string;
|
||||||
passportNumber?: string
|
passportNumber?: string;
|
||||||
passportIssued?: string
|
passportIssued?: string;
|
||||||
passportDate?: string
|
passportDate?: string;
|
||||||
address?: string
|
address?: string;
|
||||||
position?: string
|
position?: string;
|
||||||
department?: string
|
department?: string;
|
||||||
hireDate?: string
|
hireDate?: string;
|
||||||
salary?: number
|
salary?: number;
|
||||||
status?: 'ACTIVE' | 'VACATION' | 'SICK' | 'FIRED'
|
status?: "ACTIVE" | "VACATION" | "SICK" | "FIRED";
|
||||||
phone?: string
|
phone?: string;
|
||||||
email?: string
|
email?: string;
|
||||||
telegram?: string
|
telegram?: string;
|
||||||
whatsapp?: string
|
whatsapp?: string;
|
||||||
emergencyContact?: string
|
emergencyContact?: string;
|
||||||
emergencyPhone?: string
|
emergencyPhone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateScheduleInput {
|
interface UpdateScheduleInput {
|
||||||
employeeId: string
|
employeeId: string;
|
||||||
date: string
|
date: string;
|
||||||
status: 'WORK' | 'WEEKEND' | 'VACATION' | 'SICK' | 'ABSENT'
|
status: "WORK" | "WEEKEND" | "VACATION" | "SICK" | "ABSENT";
|
||||||
hoursWorked?: number
|
hoursWorked?: number;
|
||||||
notes?: string
|
notes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthTokenPayload {
|
interface AuthTokenPayload {
|
||||||
userId: string
|
userId: string;
|
||||||
phone: string
|
phone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWT утилиты
|
// JWT утилиты
|
||||||
const generateToken = (payload: AuthTokenPayload): string => {
|
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
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const verifyToken = (token: string): AuthTokenPayload => {
|
const verifyToken = (token: string): AuthTokenPayload => {
|
||||||
try {
|
try {
|
||||||
return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload
|
return jwt.verify(token, process.env.JWT_SECRET!) as AuthTokenPayload;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new GraphQLError('Недействительный токен', {
|
throw new GraphQLError("Недействительный токен", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Скалярный тип для JSON
|
// Скалярный тип для JSON
|
||||||
const JSONScalar = new GraphQLScalarType({
|
const JSONScalar = new GraphQLScalarType({
|
||||||
name: 'JSON',
|
name: "JSON",
|
||||||
description: 'JSON custom scalar type',
|
description: "JSON custom scalar type",
|
||||||
serialize(value: unknown) {
|
serialize(value: unknown) {
|
||||||
return value // значение отправляется клиенту
|
return value; // значение отправляется клиенту
|
||||||
},
|
},
|
||||||
parseValue(value: unknown) {
|
parseValue(value: unknown) {
|
||||||
return value // значение получено от клиента
|
return value; // значение получено от клиента
|
||||||
},
|
},
|
||||||
parseLiteral(ast) {
|
parseLiteral(ast) {
|
||||||
switch (ast.kind) {
|
switch (ast.kind) {
|
||||||
case Kind.STRING:
|
case Kind.STRING:
|
||||||
case Kind.BOOLEAN:
|
case Kind.BOOLEAN:
|
||||||
return ast.value
|
return ast.value;
|
||||||
case Kind.INT:
|
case Kind.INT:
|
||||||
case Kind.FLOAT:
|
case Kind.FLOAT:
|
||||||
return parseFloat(ast.value)
|
return parseFloat(ast.value);
|
||||||
case Kind.OBJECT: {
|
case Kind.OBJECT: {
|
||||||
const value = Object.create(null)
|
const value = Object.create(null);
|
||||||
ast.fields.forEach(field => {
|
ast.fields.forEach((field) => {
|
||||||
value[field.name.value] = parseLiteral(field.value)
|
value[field.name.value] = parseLiteral(field.value);
|
||||||
})
|
});
|
||||||
return value
|
return value;
|
||||||
}
|
}
|
||||||
case Kind.LIST:
|
case Kind.LIST:
|
||||||
return ast.values.map(parseLiteral)
|
return ast.values.map(parseLiteral);
|
||||||
default:
|
default:
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Скалярный тип для DateTime
|
// Скалярный тип для DateTime
|
||||||
const DateTimeScalar = new GraphQLScalarType({
|
const DateTimeScalar = new GraphQLScalarType({
|
||||||
name: 'DateTime',
|
name: "DateTime",
|
||||||
description: 'DateTime custom scalar type',
|
description: "DateTime custom scalar type",
|
||||||
serialize(value: unknown) {
|
serialize(value: unknown) {
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return value.toISOString() // значение отправляется клиенту как ISO строка
|
return value.toISOString(); // значение отправляется клиенту как ISO строка
|
||||||
}
|
}
|
||||||
return value
|
return value;
|
||||||
},
|
},
|
||||||
parseValue(value: unknown) {
|
parseValue(value: unknown) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === "string") {
|
||||||
return new Date(value) // значение получено от клиента, парсим как дату
|
return new Date(value); // значение получено от клиента, парсим как дату
|
||||||
}
|
}
|
||||||
return value
|
return value;
|
||||||
},
|
},
|
||||||
parseLiteral(ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind === Kind.STRING) {
|
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 {
|
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) {
|
switch (astNode.kind) {
|
||||||
case Kind.STRING:
|
case Kind.STRING:
|
||||||
case Kind.BOOLEAN:
|
case Kind.BOOLEAN:
|
||||||
return astNode.value
|
return astNode.value;
|
||||||
case Kind.INT:
|
case Kind.INT:
|
||||||
case Kind.FLOAT:
|
case Kind.FLOAT:
|
||||||
return parseFloat(astNode.value as string)
|
return parseFloat(astNode.value as string);
|
||||||
case Kind.OBJECT: {
|
case Kind.OBJECT: {
|
||||||
const value = Object.create(null)
|
const value = Object.create(null);
|
||||||
if (astNode.fields) {
|
if (astNode.fields) {
|
||||||
astNode.fields.forEach((field: unknown) => {
|
astNode.fields.forEach((field: unknown) => {
|
||||||
const fieldNode = field as { name: { value: string }; value: unknown }
|
const fieldNode = field as {
|
||||||
value[fieldNode.name.value] = parseLiteral(fieldNode.value)
|
name: { value: string };
|
||||||
})
|
value: unknown;
|
||||||
|
};
|
||||||
|
value[fieldNode.name.value] = parseLiteral(fieldNode.value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return value
|
return value;
|
||||||
}
|
}
|
||||||
case Kind.LIST:
|
case Kind.LIST:
|
||||||
return (ast as { values: unknown[] }).values.map(parseLiteral)
|
return (ast as { values: unknown[] }).values.map(parseLiteral);
|
||||||
default:
|
default:
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,9 +203,9 @@ export const resolvers = {
|
|||||||
Query: {
|
Query: {
|
||||||
me: async (_: unknown, __: unknown, context: Context) => {
|
me: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.user.findUnique({
|
return await prisma.user.findUnique({
|
||||||
@ -205,142 +213,154 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
organization: async (_: unknown, args: { id: string }, context: Context) => {
|
organization: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { id: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const organization = await prisma.organization.findUnique({
|
const organization = await prisma.organization.findUnique({
|
||||||
where: { id: args.id },
|
where: { id: args.id },
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true,
|
apiKeys: true,
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!organization) {
|
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) {
|
if (!hasAccess) {
|
||||||
throw new GraphQLError('Нет доступа к этой организации', {
|
throw new GraphQLError("Нет доступа к этой организации", {
|
||||||
extensions: { code: 'FORBIDDEN' }
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем текущую организацию пользователя
|
// Получаем текущую организацию пользователя
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем уже существующих контрагентов для добавления флага
|
// Получаем уже существующих контрагентов для добавления флага
|
||||||
const existingCounterparties = await prisma.counterparty.findMany({
|
const existingCounterparties = await prisma.counterparty.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
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
|
// Получаем исходящие заявки для добавления флага hasOutgoingRequest
|
||||||
const outgoingRequests = await prisma.counterpartyRequest.findMany({
|
const outgoingRequests = await prisma.counterpartyRequest.findMany({
|
||||||
where: {
|
where: {
|
||||||
senderId: currentUser.organization.id,
|
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
|
// Получаем входящие заявки для добавления флага hasIncomingRequest
|
||||||
const incomingRequests = await prisma.counterpartyRequest.findMany({
|
const incomingRequests = await prisma.counterpartyRequest.findMany({
|
||||||
where: {
|
where: {
|
||||||
receiverId: currentUser.organization.id,
|
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> = {
|
const where: Record<string, unknown> = {
|
||||||
// Больше не исключаем собственную организацию
|
// Больше не исключаем собственную организацию
|
||||||
}
|
};
|
||||||
|
|
||||||
if (args.type) {
|
if (args.type) {
|
||||||
where.type = args.type
|
where.type = args.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.search) {
|
if (args.search) {
|
||||||
where.OR = [
|
where.OR = [
|
||||||
{ name: { contains: args.search, mode: 'insensitive' } },
|
{ name: { contains: args.search, mode: "insensitive" } },
|
||||||
{ fullName: { contains: args.search, mode: 'insensitive' } },
|
{ fullName: { contains: args.search, mode: "insensitive" } },
|
||||||
{ inn: { contains: args.search } }
|
{ inn: { contains: args.search } },
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const organizations = await prisma.organization.findMany({
|
const organizations = await prisma.organization.findMany({
|
||||||
where,
|
where,
|
||||||
take: 50, // Ограничиваем количество результатов
|
take: 50, // Ограничиваем количество результатов
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: "desc" },
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
users: true,
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Добавляем флаги isCounterparty, isCurrentUser, hasOutgoingRequest и hasIncomingRequest к каждой организации
|
// Добавляем флаги isCounterparty, isCurrentUser, hasOutgoingRequest и hasIncomingRequest к каждой организации
|
||||||
return organizations.map(org => ({
|
return organizations.map((org) => ({
|
||||||
...org,
|
...org,
|
||||||
isCounterparty: existingCounterpartyIds.includes(org.id),
|
isCounterparty: existingCounterpartyIds.includes(org.id),
|
||||||
isCurrentUser: org.id === currentUser.organization?.id,
|
isCurrentUser: org.id === currentUser.organization?.id,
|
||||||
hasOutgoingRequest: outgoingRequestIds.includes(org.id),
|
hasOutgoingRequest: outgoingRequestIds.includes(org.id),
|
||||||
hasIncomingRequest: incomingRequestIds.includes(org.id)
|
hasIncomingRequest: incomingRequestIds.includes(org.id),
|
||||||
}))
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
// Мои контрагенты
|
// Мои контрагенты
|
||||||
myCounterparties: async (_: unknown, __: unknown, context: Context) => {
|
myCounterparties: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
const counterparties = await prisma.counterparty.findMany({
|
const counterparties = await prisma.counterparty.findMany({
|
||||||
@ -349,306 +369,314 @@ export const resolvers = {
|
|||||||
counterparty: {
|
counterparty: {
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
users: true,
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return counterparties.map(c => c.counterparty)
|
return counterparties.map((c) => c.counterparty);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Входящие заявки
|
// Входящие заявки
|
||||||
incomingRequests: async (_: unknown, __: unknown, context: Context) => {
|
incomingRequests: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.counterpartyRequest.findMany({
|
return await prisma.counterpartyRequest.findMany({
|
||||||
where: {
|
where: {
|
||||||
receiverId: currentUser.organization.id,
|
receiverId: currentUser.organization.id,
|
||||||
status: 'PENDING'
|
status: "PENDING",
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: {
|
sender: {
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
users: true,
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiver: {
|
receiver: {
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
users: true,
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Исходящие заявки
|
// Исходящие заявки
|
||||||
outgoingRequests: async (_: unknown, __: unknown, context: Context) => {
|
outgoingRequests: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.counterpartyRequest.findMany({
|
return await prisma.counterpartyRequest.findMany({
|
||||||
where: {
|
where: {
|
||||||
senderId: currentUser.organization.id,
|
senderId: currentUser.organization.id,
|
||||||
status: { in: ['PENDING', 'REJECTED'] }
|
status: { in: ["PENDING", "REJECTED"] },
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: {
|
sender: {
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
users: true,
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiver: {
|
receiver: {
|
||||||
include: {
|
include: {
|
||||||
users: true,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = args.limit || 50
|
const limit = args.limit || 50;
|
||||||
const offset = args.offset || 0
|
const offset = args.offset || 0;
|
||||||
|
|
||||||
const messages = await prisma.message.findMany({
|
const messages = await prisma.message.findMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
senderOrganizationId: currentUser.organization.id,
|
senderOrganizationId: currentUser.organization.id,
|
||||||
receiverOrganizationId: args.counterpartyId
|
receiverOrganizationId: args.counterpartyId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
senderOrganizationId: args.counterpartyId,
|
senderOrganizationId: args.counterpartyId,
|
||||||
receiverOrganizationId: currentUser.organization.id
|
receiverOrganizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
senderOrganization: {
|
senderOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiverOrganization: {
|
receiverOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'asc' },
|
orderBy: { createdAt: "asc" },
|
||||||
take: limit,
|
take: limit,
|
||||||
skip: offset
|
skip: offset,
|
||||||
})
|
});
|
||||||
|
|
||||||
return messages
|
return messages;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Список чатов (последние сообщения с каждым контрагентом)
|
// Список чатов (последние сообщения с каждым контрагентом)
|
||||||
conversations: async (_: unknown, __: unknown, context: Context) => {
|
conversations: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Здесь будет логика получения списка чатов
|
// TODO: Здесь будет логика получения списка чатов
|
||||||
// Пока возвращаем пустой массив, так как таблица сообщений еще не создана
|
// Пока возвращаем пустой массив, так как таблица сообщений еще не создана
|
||||||
return []
|
return [];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Мои услуги
|
// Мои услуги
|
||||||
myServices: async (_: unknown, __: unknown, context: Context) => {
|
myServices: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что это фулфилмент центр
|
// Проверяем, что это фулфилмент центр
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Услуги доступны только для фулфилмент центров')
|
throw new GraphQLError("Услуги доступны только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.service.findMany({
|
return await prisma.service.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
where: { organizationId: currentUser.organization.id },
|
||||||
include: { organization: true },
|
include: { organization: true },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Мои расходники
|
// Мои расходники
|
||||||
mySupplies: async (_: unknown, __: unknown, context: Context) => {
|
mySupplies: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.supply.findMany({
|
return await prisma.supply.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
where: { organizationId: currentUser.organization.id },
|
||||||
include: { organization: true },
|
include: { organization: true },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Логистика организации
|
// Логистика организации
|
||||||
myLogistics: async (_: unknown, __: unknown, context: Context) => {
|
myLogistics: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.logistics.findMany({
|
return await prisma.logistics.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
where: { organizationId: currentUser.organization.id },
|
||||||
include: { organization: true },
|
include: { organization: true },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Мои товары (для оптовиков)
|
// Мои товары (для оптовиков)
|
||||||
myProducts: async (_: unknown, __: unknown, context: Context) => {
|
myProducts: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что это оптовик
|
// Проверяем, что это оптовик
|
||||||
if (currentUser.organization.type !== 'WHOLESALE') {
|
if (currentUser.organization.type !== "WHOLESALE") {
|
||||||
throw new GraphQLError('Товары доступны только для оптовиков')
|
throw new GraphQLError("Товары доступны только для оптовиков");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.product.findMany({
|
return await prisma.product.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
where: { organizationId: currentUser.organization.id },
|
||||||
include: {
|
include: {
|
||||||
category: true,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const where: Record<string, unknown> = {
|
const where: Record<string, unknown> = {
|
||||||
isActive: true, // Показываем только активные товары
|
isActive: true, // Показываем только активные товары
|
||||||
organization: {
|
organization: {
|
||||||
type: 'WHOLESALE' // Только товары оптовиков
|
type: "WHOLESALE", // Только товары оптовиков
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
if (args.search) {
|
if (args.search) {
|
||||||
where.OR = [
|
where.OR = [
|
||||||
{ name: { contains: args.search, mode: 'insensitive' } },
|
{ name: { contains: args.search, mode: "insensitive" } },
|
||||||
{ article: { contains: args.search, mode: 'insensitive' } },
|
{ article: { contains: args.search, mode: "insensitive" } },
|
||||||
{ description: { contains: args.search, mode: 'insensitive' } },
|
{ description: { contains: args.search, mode: "insensitive" } },
|
||||||
{ brand: { contains: args.search, mode: 'insensitive' } }
|
{ brand: { contains: args.search, mode: "insensitive" } },
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.category) {
|
if (args.category) {
|
||||||
where.categoryId = args.category
|
where.categoryId = args.category;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.product.findMany({
|
return await prisma.product.findMany({
|
||||||
@ -657,43 +685,43 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: "desc" },
|
||||||
take: 100 // Ограничиваем количество результатов
|
take: 100, // Ограничиваем количество результатов
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Все категории
|
// Все категории
|
||||||
categories: async (_: unknown, __: unknown, context: Context) => {
|
categories: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user && !context.admin) {
|
if (!context.user && !context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.category.findMany({
|
return await prisma.category.findMany({
|
||||||
orderBy: { name: 'asc' }
|
orderBy: { name: "asc" },
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Корзина пользователя
|
// Корзина пользователя
|
||||||
myCart: async (_: unknown, __: unknown, context: Context) => {
|
myCart: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Найти или создать корзину для организации
|
// Найти или создать корзину для организации
|
||||||
@ -707,21 +735,21 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
cart = await prisma.cart.create({
|
cart = await prisma.cart.create({
|
||||||
data: {
|
data: {
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
items: {
|
items: {
|
||||||
@ -731,36 +759,36 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return cart
|
return cart;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Избранные товары пользователя
|
// Избранные товары пользователя
|
||||||
myFavorites: async (_: unknown, __: unknown, context: Context) => {
|
myFavorites: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем избранные товары
|
// Получаем избранные товары
|
||||||
@ -772,210 +800,228 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
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) => {
|
myEmployees: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
const employees = await prisma.employee.findMany({
|
const employees = await prisma.employee.findMany({
|
||||||
where: { organizationId: currentUser.organization.id },
|
where: { organizationId: currentUser.organization.id },
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
|
|
||||||
return employees
|
return employees;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Получение сотрудника по ID
|
// Получение сотрудника по ID
|
||||||
employee: async (_: unknown, args: { id: string }, context: Context) => {
|
employee: async (_: unknown, args: { id: string }, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
const employee = await prisma.employee.findFirst({
|
const employee = await prisma.employee.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
include: {
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем что сотрудник принадлежит организации
|
// Проверяем что сотрудник принадлежит организации
|
||||||
const employee = await prisma.employee.findFirst({
|
const employee = await prisma.employee.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.employeeId,
|
id: args.employeeId,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!employee) {
|
if (!employee) {
|
||||||
throw new GraphQLError('Сотрудник не найден')
|
throw new GraphQLError("Сотрудник не найден");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем записи табеля за указанный месяц
|
// Получаем записи табеля за указанный месяц
|
||||||
const startDate = new Date(args.year, args.month, 1)
|
const startDate = new Date(args.year, args.month, 1);
|
||||||
const endDate = new Date(args.year, args.month + 1, 0)
|
const endDate = new Date(args.year, args.month + 1, 0);
|
||||||
|
|
||||||
const scheduleRecords = await prisma.employeeSchedule.findMany({
|
const scheduleRecords = await prisma.employeeSchedule.findMany({
|
||||||
where: {
|
where: {
|
||||||
employeeId: args.employeeId,
|
employeeId: args.employeeId,
|
||||||
date: {
|
date: {
|
||||||
gte: startDate,
|
gte: startDate,
|
||||||
lte: endDate
|
lte: endDate,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
date: 'asc'
|
date: "asc",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return scheduleRecords
|
return scheduleRecords;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Mutation: {
|
Mutation: {
|
||||||
sendSmsCode: async (_: unknown, args: { phone: string }) => {
|
sendSmsCode: async (_: unknown, args: { phone: string }) => {
|
||||||
const result = await smsService.sendSmsCode(args.phone)
|
const result = await smsService.sendSmsCode(args.phone);
|
||||||
return {
|
return {
|
||||||
success: result.success,
|
success: result.success,
|
||||||
message: result.message || 'SMS код отправлен'
|
message: result.message || "SMS код отправлен",
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
verifySmsCode: async (_: unknown, args: { phone: string; code: string }) => {
|
verifySmsCode: async (
|
||||||
const verificationResult = await smsService.verifySmsCode(args.phone, args.code)
|
_: unknown,
|
||||||
|
args: { phone: string; code: string }
|
||||||
|
) => {
|
||||||
|
const verificationResult = await smsService.verifySmsCode(
|
||||||
|
args.phone,
|
||||||
|
args.code
|
||||||
|
);
|
||||||
|
|
||||||
if (!verificationResult.success) {
|
if (!verificationResult.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
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({
|
let user = await prisma.user.findUnique({
|
||||||
where: { phone: formattedPhone },
|
where: { phone: formattedPhone },
|
||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await prisma.user.create({
|
user = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
phone: formattedPhone
|
phone: formattedPhone,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = generateToken({
|
const token = generateToken({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
phone: user.phone
|
phone: user.phone,
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log('verifySmsCode - Generated token:', token ? `${token.substring(0, 20)}...` : 'No token')
|
console.log(
|
||||||
console.log('verifySmsCode - Full token:', token)
|
"verifySmsCode - Generated token:",
|
||||||
console.log('verifySmsCode - User object:', { id: user.id, phone: user.phone })
|
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 = {
|
const result = {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Авторизация успешна',
|
message: "Авторизация успешна",
|
||||||
token,
|
token,
|
||||||
user
|
user,
|
||||||
}
|
};
|
||||||
|
|
||||||
console.log('verifySmsCode - Returning result:', {
|
console.log("verifySmsCode - Returning result:", {
|
||||||
success: result.success,
|
success: result.success,
|
||||||
hasToken: !!result.token,
|
hasToken: !!result.token,
|
||||||
hasUser: !!result.user,
|
hasUser: !!result.user,
|
||||||
message: result.message,
|
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 }) => {
|
verifyInn: async (_: unknown, args: { inn: string }) => {
|
||||||
@ -983,72 +1029,80 @@ export const resolvers = {
|
|||||||
if (!dadataService.validateInn(args.inn)) {
|
if (!dadataService.validateInn(args.inn)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Неверный формат ИНН'
|
message: "Неверный формат ИНН",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем данные организации из DaData
|
// Получаем данные организации из DaData
|
||||||
const organizationData = await dadataService.getOrganizationByInn(args.inn)
|
const organizationData = await dadataService.getOrganizationByInn(
|
||||||
|
args.inn
|
||||||
|
);
|
||||||
if (!organizationData) {
|
if (!organizationData) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Организация с указанным ИНН не найдена'
|
message: "Организация с указанным ИНН не найдена",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'ИНН найден',
|
message: "ИНН найден",
|
||||||
organization: {
|
organization: {
|
||||||
name: organizationData.name,
|
name: organizationData.name,
|
||||||
fullName: organizationData.fullName,
|
fullName: organizationData.fullName,
|
||||||
address: organizationData.address,
|
address: organizationData.address,
|
||||||
isActive: organizationData.isActive
|
isActive: organizationData.isActive,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
registerFulfillmentOrganization: async (
|
registerFulfillmentOrganization: async (
|
||||||
_: unknown,
|
_: unknown,
|
||||||
args: { input: { phone: string; inn: string; type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE' } },
|
args: {
|
||||||
|
input: {
|
||||||
|
phone: string;
|
||||||
|
inn: string;
|
||||||
|
type: "FULFILLMENT" | "LOGIST" | "WHOLESALE";
|
||||||
|
};
|
||||||
|
},
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { inn, type } = args.input
|
const { inn, type } = args.input;
|
||||||
|
|
||||||
// Валидируем ИНН
|
// Валидируем ИНН
|
||||||
if (!dadataService.validateInn(inn)) {
|
if (!dadataService.validateInn(inn)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Неверный формат ИНН'
|
message: "Неверный формат ИНН",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем данные организации из DaData
|
// Получаем данные организации из DaData
|
||||||
const organizationData = await dadataService.getOrganizationByInn(inn)
|
const organizationData = await dadataService.getOrganizationByInn(inn);
|
||||||
if (!organizationData) {
|
if (!organizationData) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Организация с указанным ИНН не найдена'
|
message: "Организация с указанным ИНН не найдена",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Проверяем, что организация еще не зарегистрирована
|
// Проверяем, что организация еще не зарегистрирована
|
||||||
const existingOrg = await prisma.organization.findUnique({
|
const existingOrg = await prisma.organization.findUnique({
|
||||||
where: { inn: organizationData.inn }
|
where: { inn: organizationData.inn },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (existingOrg) {
|
if (existingOrg) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Организация с таким ИНН уже зарегистрирована'
|
message: "Организация с таким ИНН уже зарегистрирована",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем организацию со всеми данными из DaData
|
// Создаем организацию со всеми данными из DaData
|
||||||
@ -1085,8 +1139,12 @@ export const resolvers = {
|
|||||||
okved: organizationData.okved,
|
okved: organizationData.okved,
|
||||||
|
|
||||||
// Контакты
|
// Контакты
|
||||||
phones: organizationData.phones ? JSON.parse(JSON.stringify(organizationData.phones)) : null,
|
phones: organizationData.phones
|
||||||
emails: organizationData.emails ? JSON.parse(JSON.stringify(organizationData.emails)) : null,
|
? JSON.parse(JSON.stringify(organizationData.phones))
|
||||||
|
: null,
|
||||||
|
emails: organizationData.emails
|
||||||
|
? JSON.parse(JSON.stringify(organizationData.emails))
|
||||||
|
: null,
|
||||||
|
|
||||||
// Финансовые данные
|
// Финансовые данные
|
||||||
employeeCount: organizationData.employeeCount,
|
employeeCount: organizationData.employeeCount,
|
||||||
@ -1094,9 +1152,9 @@ export const resolvers = {
|
|||||||
taxSystem: organizationData.taxSystem,
|
taxSystem: organizationData.taxSystem,
|
||||||
|
|
||||||
type: type,
|
type: type,
|
||||||
dadataData: JSON.parse(JSON.stringify(organizationData.rawData))
|
dadataData: JSON.parse(JSON.stringify(organizationData.rawData)),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Привязываем пользователя к организации
|
// Привязываем пользователя к организации
|
||||||
const updatedUser = await prisma.user.update({
|
const updatedUser = await prisma.user.update({
|
||||||
@ -1105,24 +1163,23 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Организация успешно зарегистрирована',
|
message: "Организация успешно зарегистрирована",
|
||||||
user: updatedUser
|
user: updatedUser,
|
||||||
}
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error registering fulfillment organization:', error)
|
console.error("Error registering fulfillment organization:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при регистрации организации'
|
message: "Ошибка при регистрации организации",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1130,87 +1187,96 @@ export const resolvers = {
|
|||||||
_: unknown,
|
_: unknown,
|
||||||
args: {
|
args: {
|
||||||
input: {
|
input: {
|
||||||
phone: string
|
phone: string;
|
||||||
wbApiKey?: string
|
wbApiKey?: string;
|
||||||
ozonApiKey?: string
|
ozonApiKey?: string;
|
||||||
ozonClientId?: string
|
ozonClientId?: string;
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wbApiKey, ozonApiKey, ozonClientId } = args.input
|
const { wbApiKey, ozonApiKey, ozonClientId } = args.input;
|
||||||
|
|
||||||
if (!wbApiKey && !ozonApiKey) {
|
if (!wbApiKey && !ozonApiKey) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Необходимо указать хотя бы один API ключ маркетплейса'
|
message: "Необходимо указать хотя бы один API ключ маркетплейса",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Валидируем API ключи
|
// Валидируем API ключи
|
||||||
const validationResults = []
|
const validationResults = [];
|
||||||
|
|
||||||
if (wbApiKey) {
|
if (wbApiKey) {
|
||||||
const wbResult = await marketplaceService.validateWildberriesApiKey(wbApiKey)
|
const wbResult = await marketplaceService.validateWildberriesApiKey(
|
||||||
|
wbApiKey
|
||||||
|
);
|
||||||
if (!wbResult.isValid) {
|
if (!wbResult.isValid) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Wildberries: ${wbResult.message}`
|
message: `Wildberries: ${wbResult.message}`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
validationResults.push({
|
validationResults.push({
|
||||||
marketplace: 'WILDBERRIES',
|
marketplace: "WILDBERRIES",
|
||||||
apiKey: wbApiKey,
|
apiKey: wbApiKey,
|
||||||
data: wbResult.data
|
data: wbResult.data,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ozonApiKey && ozonClientId) {
|
if (ozonApiKey && ozonClientId) {
|
||||||
const ozonResult = await marketplaceService.validateOzonApiKey(ozonApiKey, ozonClientId)
|
const ozonResult = await marketplaceService.validateOzonApiKey(
|
||||||
|
ozonApiKey,
|
||||||
|
ozonClientId
|
||||||
|
);
|
||||||
if (!ozonResult.isValid) {
|
if (!ozonResult.isValid) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Ozon: ${ozonResult.message}`
|
message: `Ozon: ${ozonResult.message}`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
validationResults.push({
|
validationResults.push({
|
||||||
marketplace: 'OZON',
|
marketplace: "OZON",
|
||||||
apiKey: ozonApiKey,
|
apiKey: ozonApiKey,
|
||||||
data: ozonResult.data
|
data: ozonResult.data,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем организацию селлера - используем tradeMark как основное имя
|
// Создаем организацию селлера - используем tradeMark как основное имя
|
||||||
const tradeMark = validationResults[0]?.data?.tradeMark
|
const tradeMark = validationResults[0]?.data?.tradeMark;
|
||||||
const sellerName = validationResults[0]?.data?.sellerName
|
const sellerName = validationResults[0]?.data?.sellerName;
|
||||||
const shopName = tradeMark || sellerName || 'Магазин'
|
const shopName = tradeMark || sellerName || "Магазин";
|
||||||
|
|
||||||
const organization = await prisma.organization.create({
|
const organization = await prisma.organization.create({
|
||||||
data: {
|
data: {
|
||||||
inn: (validationResults[0]?.data?.inn as string) || `SELLER_${Date.now()}`,
|
inn:
|
||||||
|
(validationResults[0]?.data?.inn as string) ||
|
||||||
|
`SELLER_${Date.now()}`,
|
||||||
name: shopName, // Используем tradeMark как основное название
|
name: shopName, // Используем tradeMark как основное название
|
||||||
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
|
fullName: sellerName
|
||||||
type: 'SELLER'
|
? `${sellerName} (${shopName})`
|
||||||
}
|
: `Интернет-магазин "${shopName}"`,
|
||||||
})
|
type: "SELLER",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Добавляем API ключи
|
// Добавляем API ключи
|
||||||
for (const validation of validationResults) {
|
for (const validation of validationResults) {
|
||||||
await prisma.apiKey.create({
|
await prisma.apiKey.create({
|
||||||
data: {
|
data: {
|
||||||
marketplace: validation.marketplace as 'WILDBERRIES' | 'OZON',
|
marketplace: validation.marketplace as "WILDBERRIES" | "OZON",
|
||||||
apiKey: validation.apiKey,
|
apiKey: validation.apiKey,
|
||||||
organizationId: organization.id,
|
organizationId: organization.id,
|
||||||
validationData: JSON.parse(JSON.stringify(validation.data))
|
validationData: JSON.parse(JSON.stringify(validation.data)),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Привязываем пользователя к организации
|
// Привязываем пользователя к организации
|
||||||
@ -1220,24 +1286,23 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Селлер организация успешно зарегистрирована',
|
message: "Селлер организация успешно зарегистрирована",
|
||||||
user: updatedUser
|
user: updatedUser,
|
||||||
}
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error registering seller organization:', error)
|
console.error("Error registering seller organization:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при регистрации организации'
|
message: "Ошибка при регистрации организации",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1245,84 +1310,85 @@ export const resolvers = {
|
|||||||
_: unknown,
|
_: unknown,
|
||||||
args: {
|
args: {
|
||||||
input: {
|
input: {
|
||||||
marketplace: 'WILDBERRIES' | 'OZON'
|
marketplace: "WILDBERRIES" | "OZON";
|
||||||
apiKey: string
|
apiKey: string;
|
||||||
clientId?: string
|
clientId?: string;
|
||||||
validateOnly?: boolean
|
validateOnly?: boolean;
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
// Разрешаем валидацию без авторизации
|
// Разрешаем валидацию без авторизации
|
||||||
if (!args.input.validateOnly && !context.user) {
|
if (!args.input.validateOnly && !context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { marketplace, apiKey, clientId, validateOnly } = args.input
|
const { marketplace, apiKey, clientId, validateOnly } = args.input;
|
||||||
|
|
||||||
// Валидируем API ключ
|
// Валидируем API ключ
|
||||||
const validationResult = await marketplaceService.validateApiKey(
|
const validationResult = await marketplaceService.validateApiKey(
|
||||||
marketplace,
|
marketplace,
|
||||||
apiKey,
|
apiKey,
|
||||||
clientId
|
clientId
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!validationResult.isValid) {
|
if (!validationResult.isValid) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: validationResult.message
|
message: validationResult.message,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если это только валидация, возвращаем результат без сохранения
|
// Если это только валидация, возвращаем результат без сохранения
|
||||||
if (validateOnly) {
|
if (validateOnly) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'API ключ действителен',
|
message: "API ключ действителен",
|
||||||
apiKey: {
|
apiKey: {
|
||||||
id: 'validate-only',
|
id: "validate-only",
|
||||||
marketplace,
|
marketplace,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
validationData: validationResult,
|
validationData: validationResult,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString()
|
updatedAt: new Date().toISOString(),
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Для сохранения API ключа нужна авторизация
|
// Для сохранения API ключа нужна авторизация
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация для сохранения API ключа', {
|
throw new GraphQLError(
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
"Требуется авторизация для сохранения API ключа",
|
||||||
})
|
{
|
||||||
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!user?.organization) {
|
if (!user?.organization) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Пользователь не привязан к организации'
|
message: "Пользователь не привязан к организации",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Проверяем, что такого ключа еще нет
|
// Проверяем, что такого ключа еще нет
|
||||||
const existingKey = await prisma.apiKey.findUnique({
|
const existingKey = await prisma.apiKey.findUnique({
|
||||||
where: {
|
where: {
|
||||||
organizationId_marketplace: {
|
organizationId_marketplace: {
|
||||||
organizationId: user.organization.id,
|
organizationId: user.organization.id,
|
||||||
marketplace
|
marketplace,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (existingKey) {
|
if (existingKey) {
|
||||||
// Обновляем существующий ключ
|
// Обновляем существующий ключ
|
||||||
@ -1330,16 +1396,16 @@ export const resolvers = {
|
|||||||
where: { id: existingKey.id },
|
where: { id: existingKey.id },
|
||||||
data: {
|
data: {
|
||||||
apiKey,
|
apiKey,
|
||||||
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||||
isActive: true
|
isActive: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'API ключ успешно обновлен',
|
message: "API ключ успешно обновлен",
|
||||||
apiKey: updatedKey
|
apiKey: updatedKey,
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
// Создаем новый ключ
|
// Создаем новый ключ
|
||||||
const newKey = await prisma.apiKey.create({
|
const newKey = await prisma.apiKey.create({
|
||||||
@ -1347,44 +1413,43 @@ export const resolvers = {
|
|||||||
marketplace,
|
marketplace,
|
||||||
apiKey,
|
apiKey,
|
||||||
organizationId: user.organization.id,
|
organizationId: user.organization.id,
|
||||||
validationData: JSON.parse(JSON.stringify(validationResult.data))
|
validationData: JSON.parse(JSON.stringify(validationResult.data)),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'API ключ успешно добавлен',
|
message: "API ключ успешно добавлен",
|
||||||
apiKey: newKey
|
apiKey: newKey,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding marketplace API key:', error)
|
console.error("Error adding marketplace API key:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при добавлении API ключа'
|
message: "Ошибка при добавлении API ключа",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeMarketplaceApiKey: async (
|
removeMarketplaceApiKey: async (
|
||||||
_: unknown,
|
_: unknown,
|
||||||
args: { marketplace: 'WILDBERRIES' | 'OZON' },
|
args: { marketplace: "WILDBERRIES" | "OZON" },
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!user?.organization) {
|
if (!user?.organization) {
|
||||||
throw new GraphQLError('Пользователь не привязан к организации')
|
throw new GraphQLError("Пользователь не привязан к организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1392,34 +1457,40 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
organizationId_marketplace: {
|
organizationId_marketplace: {
|
||||||
organizationId: user.organization.id,
|
organizationId: user.organization.id,
|
||||||
marketplace: args.marketplace
|
marketplace: args.marketplace,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing marketplace API key:', error)
|
console.error("Error removing marketplace API key:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateUserProfile: async (_: unknown, args: { input: {
|
updateUserProfile: async (
|
||||||
avatar?: string
|
_: unknown,
|
||||||
orgPhone?: string
|
args: {
|
||||||
managerName?: string
|
input: {
|
||||||
telegram?: string
|
avatar?: string;
|
||||||
whatsapp?: string
|
orgPhone?: string;
|
||||||
email?: string
|
managerName?: string;
|
||||||
bankName?: string
|
telegram?: string;
|
||||||
bik?: string
|
whatsapp?: string;
|
||||||
accountNumber?: string
|
email?: string;
|
||||||
corrAccount?: string
|
bankName?: string;
|
||||||
} }, context: Context) => {
|
bik?: string;
|
||||||
|
accountNumber?: string;
|
||||||
|
corrAccount?: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
@ -1427,93 +1498,98 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!user?.organization) {
|
if (!user?.organization) {
|
||||||
throw new GraphQLError('Пользователь не привязан к организации')
|
throw new GraphQLError("Пользователь не привязан к организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { input } = args
|
const { input } = args;
|
||||||
|
|
||||||
// Обновляем данные пользователя (аватар, имя управляющего)
|
// Обновляем данные пользователя (аватар, имя управляющего)
|
||||||
const userUpdateData: { avatar?: string; managerName?: string } = {}
|
const userUpdateData: { avatar?: string; managerName?: string } = {};
|
||||||
if (input.avatar) {
|
if (input.avatar) {
|
||||||
userUpdateData.avatar = input.avatar
|
userUpdateData.avatar = input.avatar;
|
||||||
}
|
}
|
||||||
if (input.managerName) {
|
if (input.managerName) {
|
||||||
userUpdateData.managerName = input.managerName
|
userUpdateData.managerName = input.managerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(userUpdateData).length > 0) {
|
if (Object.keys(userUpdateData).length > 0) {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
data: userUpdateData
|
data: userUpdateData,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подготавливаем данные для обновления организации
|
// Подготавливаем данные для обновления организации
|
||||||
const updateData: {
|
const updateData: {
|
||||||
phones?: object
|
phones?: object;
|
||||||
emails?: object
|
emails?: object;
|
||||||
managementName?: string
|
managementName?: string;
|
||||||
managementPost?: string
|
managementPost?: string;
|
||||||
} = {}
|
} = {};
|
||||||
|
|
||||||
// Название организации больше не обновляется через профиль
|
// Название организации больше не обновляется через профиль
|
||||||
// Для селлеров устанавливается при регистрации, для остальных - при смене ИНН
|
// Для селлеров устанавливается при регистрации, для остальных - при смене ИНН
|
||||||
|
|
||||||
// Обновляем контактные данные в JSON поле phones
|
// Обновляем контактные данные в JSON поле phones
|
||||||
if (input.orgPhone) {
|
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) {
|
if (input.email) {
|
||||||
updateData.emails = [{ value: input.email, type: 'main' }]
|
updateData.emails = [{ value: input.email, type: "main" }];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем дополнительные контакты в custom полях
|
// Сохраняем дополнительные контакты в custom полях
|
||||||
// Пока добавим их как дополнительные JSON поля
|
// Пока добавим их как дополнительные JSON поля
|
||||||
const customContacts: {
|
const customContacts: {
|
||||||
managerName?: string
|
managerName?: string;
|
||||||
telegram?: string
|
telegram?: string;
|
||||||
whatsapp?: string
|
whatsapp?: string;
|
||||||
bankDetails?: {
|
bankDetails?: {
|
||||||
bankName?: string
|
bankName?: string;
|
||||||
bik?: string
|
bik?: string;
|
||||||
accountNumber?: string
|
accountNumber?: string;
|
||||||
corrAccount?: string
|
corrAccount?: string;
|
||||||
}
|
};
|
||||||
} = {}
|
} = {};
|
||||||
|
|
||||||
// managerName теперь сохраняется в поле пользователя, а не в JSON
|
// managerName теперь сохраняется в поле пользователя, а не в JSON
|
||||||
|
|
||||||
if (input.telegram) {
|
if (input.telegram) {
|
||||||
customContacts.telegram = input.telegram
|
customContacts.telegram = input.telegram;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.whatsapp) {
|
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 = {
|
customContacts.bankDetails = {
|
||||||
bankName: input.bankName,
|
bankName: input.bankName,
|
||||||
bik: input.bik,
|
bik: input.bik,
|
||||||
accountNumber: input.accountNumber,
|
accountNumber: input.accountNumber,
|
||||||
corrAccount: input.corrAccount
|
corrAccount: input.corrAccount,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если есть дополнительные контакты, сохраним их в поле managementPost временно
|
// Если есть дополнительные контакты, сохраним их в поле managementPost временно
|
||||||
// В идеале нужно добавить отдельную таблицу для контактов
|
// В идеале нужно добавить отдельную таблицу для контактов
|
||||||
if (Object.keys(customContacts).length > 0) {
|
if (Object.keys(customContacts).length > 0) {
|
||||||
updateData.managementPost = JSON.stringify(customContacts)
|
updateData.managementPost = JSON.stringify(customContacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем организацию
|
// Обновляем организацию
|
||||||
@ -1521,9 +1597,9 @@ export const resolvers = {
|
|||||||
where: { id: user.organization.id },
|
where: { id: user.organization.id },
|
||||||
data: updateData,
|
data: updateData,
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Получаем обновленного пользователя
|
// Получаем обновленного пользователя
|
||||||
const updatedUser = await prisma.user.findUnique({
|
const updatedUser = await prisma.user.findUnique({
|
||||||
@ -1531,31 +1607,35 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Профиль успешно обновлен',
|
message: "Профиль успешно обновлен",
|
||||||
user: updatedUser
|
user: updatedUser,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating user profile:', error)
|
console.error("Error updating user profile:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении профиля'
|
message: "Ошибка при обновлении профиля",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateOrganizationByInn: async (_: unknown, args: { inn: string }, context: Context) => {
|
updateOrganizationByInn: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { inn: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
@ -1563,14 +1643,14 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!user?.organization) {
|
if (!user?.organization) {
|
||||||
throw new GraphQLError('Пользователь не привязан к организации')
|
throw new GraphQLError("Пользователь не привязан к организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1578,43 +1658,57 @@ export const resolvers = {
|
|||||||
if (!dadataService.validateInn(args.inn)) {
|
if (!dadataService.validateInn(args.inn)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Неверный формат ИНН'
|
message: "Неверный формат ИНН",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем данные организации из DaData
|
// Получаем данные организации из DaData
|
||||||
const organizationData = await dadataService.getOrganizationByInn(args.inn)
|
const organizationData = await dadataService.getOrganizationByInn(
|
||||||
|
args.inn
|
||||||
|
);
|
||||||
if (!organizationData) {
|
if (!organizationData) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Организация с указанным ИНН не найдена в федеральном реестре'
|
message:
|
||||||
}
|
"Организация с указанным ИНН не найдена в федеральном реестре",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, есть ли уже организация с таким ИНН в базе (кроме текущей)
|
// Проверяем, есть ли уже организация с таким ИНН в базе (кроме текущей)
|
||||||
const existingOrganization = await prisma.organization.findUnique({
|
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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Организация с ИНН ${organizationData.inn} уже существует в системе`
|
message: `Организация с ИНН ${organizationData.inn} уже существует в системе`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подготавливаем данные для обновления
|
// Подготавливаем данные для обновления
|
||||||
const updateData: Prisma.OrganizationUpdateInput = {
|
const updateData: Prisma.OrganizationUpdateInput = {
|
||||||
kpp: organizationData.kpp,
|
kpp: organizationData.kpp,
|
||||||
// Для селлеров не обновляем название организации (это название магазина)
|
// Для селлеров не обновляем название организации (это название магазина)
|
||||||
...(user.organization.type !== 'SELLER' && { name: organizationData.name }),
|
...(user.organization.type !== "SELLER" && {
|
||||||
|
name: organizationData.name,
|
||||||
|
}),
|
||||||
fullName: organizationData.fullName,
|
fullName: organizationData.fullName,
|
||||||
address: organizationData.address,
|
address: organizationData.address,
|
||||||
addressFull: organizationData.addressFull,
|
addressFull: organizationData.addressFull,
|
||||||
ogrn: organizationData.ogrn,
|
ogrn: organizationData.ogrn,
|
||||||
ogrnDate: organizationData.ogrnDate ? organizationData.ogrnDate.toISOString() : null,
|
ogrnDate: organizationData.ogrnDate
|
||||||
registrationDate: organizationData.registrationDate ? organizationData.registrationDate.toISOString() : null,
|
? organizationData.ogrnDate.toISOString()
|
||||||
liquidationDate: organizationData.liquidationDate ? organizationData.liquidationDate.toISOString() : null,
|
: null,
|
||||||
|
registrationDate: organizationData.registrationDate
|
||||||
|
? organizationData.registrationDate.toISOString()
|
||||||
|
: null,
|
||||||
|
liquidationDate: organizationData.liquidationDate
|
||||||
|
? organizationData.liquidationDate.toISOString()
|
||||||
|
: null,
|
||||||
managementName: organizationData.managementName, // Всегда перезаписываем данными из DaData (может быть null)
|
managementName: organizationData.managementName, // Всегда перезаписываем данными из DaData (может быть null)
|
||||||
managementPost: user.organization.managementPost, // Сохраняем кастомные данные пользователя
|
managementPost: user.organization.managementPost, // Сохраняем кастомные данные пользователя
|
||||||
opfCode: organizationData.opfCode,
|
opfCode: organizationData.opfCode,
|
||||||
@ -1624,12 +1718,12 @@ export const resolvers = {
|
|||||||
oktmo: organizationData.oktmo,
|
oktmo: organizationData.oktmo,
|
||||||
okpo: organizationData.okpo,
|
okpo: organizationData.okpo,
|
||||||
okved: organizationData.okved,
|
okved: organizationData.okved,
|
||||||
status: organizationData.status
|
status: organizationData.status,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Добавляем ИНН только если он отличается от текущего
|
// Добавляем ИНН только если он отличается от текущего
|
||||||
if (user.organization.inn !== organizationData.inn) {
|
if (user.organization.inn !== organizationData.inn) {
|
||||||
updateData.inn = organizationData.inn
|
updateData.inn = organizationData.inn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем организацию
|
// Обновляем организацию
|
||||||
@ -1637,9 +1731,9 @@ export const resolvers = {
|
|||||||
where: { id: user.organization.id },
|
where: { id: user.organization.id },
|
||||||
data: updateData,
|
data: updateData,
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Получаем обновленного пользователя
|
// Получаем обновленного пользователя
|
||||||
const updatedUser = await prisma.user.findUnique({
|
const updatedUser = await prisma.user.findUnique({
|
||||||
@ -1647,60 +1741,64 @@ export const resolvers = {
|
|||||||
include: {
|
include: {
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true
|
apiKeys: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Данные организации успешно обновлены',
|
message: "Данные организации успешно обновлены",
|
||||||
user: updatedUser
|
user: updatedUser,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating organization by INN:', error)
|
console.error("Error updating organization by INN:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении данных организации'
|
message: "Ошибка при обновлении данных организации",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
// В stateless JWT системе logout происходит на клиенте
|
// В stateless JWT системе logout происходит на клиенте
|
||||||
// Можно добавить blacklist токенов, если нужно
|
// Можно добавить 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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.id === args.organizationId) {
|
if (currentUser.organization.id === args.organizationId) {
|
||||||
throw new GraphQLError('Нельзя отправить заявку самому себе')
|
throw new GraphQLError("Нельзя отправить заявку самому себе");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что организация-получатель существует
|
// Проверяем, что организация-получатель существует
|
||||||
const receiverOrganization = await prisma.organization.findUnique({
|
const receiverOrganization = await prisma.organization.findUnique({
|
||||||
where: { id: args.organizationId }
|
where: { id: args.organizationId },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!receiverOrganization) {
|
if (!receiverOrganization) {
|
||||||
throw new GraphQLError('Организация не найдена')
|
throw new GraphQLError("Организация не найдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1709,55 +1807,59 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
senderId_receiverId: {
|
senderId_receiverId: {
|
||||||
senderId: currentUser.organization.id,
|
senderId: currentUser.organization.id,
|
||||||
receiverId: args.organizationId
|
receiverId: args.organizationId,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
status: 'PENDING',
|
status: "PENDING",
|
||||||
message: args.message,
|
message: args.message,
|
||||||
updatedAt: new Date()
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
senderId: currentUser.organization.id,
|
senderId: currentUser.organization.id,
|
||||||
receiverId: args.organizationId,
|
receiverId: args.organizationId,
|
||||||
message: args.message,
|
message: args.message,
|
||||||
status: 'PENDING'
|
status: "PENDING",
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
receiver: true
|
receiver: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Заявка отправлена',
|
message: "Заявка отправлена",
|
||||||
request
|
request,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending counterparty request:', error)
|
console.error("Error sending counterparty request:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1766,23 +1868,23 @@ export const resolvers = {
|
|||||||
where: { id: args.requestId },
|
where: { id: args.requestId },
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
receiver: true
|
receiver: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
throw new GraphQLError('Заявка не найдена')
|
throw new GraphQLError("Заявка не найдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.receiverId !== currentUser.organization.id) {
|
if (request.receiverId !== currentUser.organization.id) {
|
||||||
throw new GraphQLError('Нет прав на обработку этой заявки')
|
throw new GraphQLError("Нет прав на обработку этой заявки");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.status !== 'PENDING') {
|
if (request.status !== "PENDING") {
|
||||||
throw new GraphQLError('Заявка уже обработана')
|
throw new GraphQLError("Заявка уже обработана");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newStatus = args.accept ? 'ACCEPTED' : 'REJECTED'
|
const newStatus = args.accept ? "ACCEPTED" : "REJECTED";
|
||||||
|
|
||||||
// Обновляем статус заявки
|
// Обновляем статус заявки
|
||||||
const updatedRequest = await prisma.counterpartyRequest.update({
|
const updatedRequest = await prisma.counterpartyRequest.update({
|
||||||
@ -1790,9 +1892,9 @@ export const resolvers = {
|
|||||||
data: { status: newStatus },
|
data: { status: newStatus },
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
receiver: true
|
receiver: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Если заявка принята, создаем связи контрагентов в обе стороны
|
// Если заявка принята, создаем связи контрагентов в обе стороны
|
||||||
if (args.accept) {
|
if (args.accept) {
|
||||||
@ -1801,94 +1903,102 @@ export const resolvers = {
|
|||||||
prisma.counterparty.create({
|
prisma.counterparty.create({
|
||||||
data: {
|
data: {
|
||||||
organizationId: request.receiverId,
|
organizationId: request.receiverId,
|
||||||
counterpartyId: request.senderId
|
counterpartyId: request.senderId,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
// Добавляем получателя в контрагенты отправителя
|
// Добавляем получателя в контрагенты отправителя
|
||||||
prisma.counterparty.create({
|
prisma.counterparty.create({
|
||||||
data: {
|
data: {
|
||||||
organizationId: request.senderId,
|
organizationId: request.senderId,
|
||||||
counterpartyId: request.receiverId
|
counterpartyId: request.receiverId,
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: args.accept ? 'Заявка принята' : 'Заявка отклонена',
|
message: args.accept ? "Заявка принята" : "Заявка отклонена",
|
||||||
request: updatedRequest
|
request: updatedRequest,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error responding to counterparty request:', error)
|
console.error("Error responding to counterparty request:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обработке заявки'
|
message: "Ошибка при обработке заявки",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Отменить заявку
|
// Отменить заявку
|
||||||
cancelCounterpartyRequest: async (_: unknown, args: { requestId: string }, context: Context) => {
|
cancelCounterpartyRequest: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { requestId: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = await prisma.counterpartyRequest.findUnique({
|
const request = await prisma.counterpartyRequest.findUnique({
|
||||||
where: { id: args.requestId }
|
where: { id: args.requestId },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
throw new GraphQLError('Заявка не найдена')
|
throw new GraphQLError("Заявка не найдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.senderId !== currentUser.organization.id) {
|
if (request.senderId !== currentUser.organization.id) {
|
||||||
throw new GraphQLError('Можно отменить только свои заявки')
|
throw new GraphQLError("Можно отменить только свои заявки");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.status !== 'PENDING') {
|
if (request.status !== "PENDING") {
|
||||||
throw new GraphQLError('Можно отменить только ожидающие заявки')
|
throw new GraphQLError("Можно отменить только ожидающие заявки");
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.counterpartyRequest.update({
|
await prisma.counterpartyRequest.update({
|
||||||
where: { id: args.requestId },
|
where: { id: args.requestId },
|
||||||
data: { status: 'CANCELLED' }
|
data: { status: "CANCELLED" },
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error cancelling counterparty request:', error)
|
console.error("Error cancelling counterparty request:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить контрагента
|
// Удалить контрагента
|
||||||
removeCounterparty: async (_: unknown, args: { organizationId: string }, context: Context) => {
|
removeCounterparty: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { organizationId: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1897,60 +2007,70 @@ export const resolvers = {
|
|||||||
prisma.counterparty.deleteMany({
|
prisma.counterparty.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
counterpartyId: args.organizationId
|
counterpartyId: args.organizationId,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
prisma.counterparty.deleteMany({
|
prisma.counterparty.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
organizationId: args.organizationId,
|
organizationId: args.organizationId,
|
||||||
counterpartyId: currentUser.organization.id
|
counterpartyId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
])
|
]);
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing counterparty:', error)
|
console.error("Error removing counterparty:", error);
|
||||||
return false
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что получатель является контрагентом
|
// Проверяем, что получатель является контрагентом
|
||||||
const isCounterparty = await prisma.counterparty.findFirst({
|
const isCounterparty = await prisma.counterparty.findFirst({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
counterpartyId: args.receiverOrganizationId
|
counterpartyId: args.receiverOrganizationId,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!isCounterparty) {
|
if (!isCounterparty) {
|
||||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
throw new GraphQLError(
|
||||||
|
"Можно отправлять сообщения только контрагентам"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем организацию получателя
|
// Получаем организацию получателя
|
||||||
const receiverOrganization = await prisma.organization.findUnique({
|
const receiverOrganization = await prisma.organization.findUnique({
|
||||||
where: { id: args.receiverOrganizationId }
|
where: { id: args.receiverOrganizationId },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!receiverOrganization) {
|
if (!receiverOrganization) {
|
||||||
throw new GraphQLError('Организация получателя не найдена')
|
throw new GraphQLError("Организация получателя не найдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1958,76 +2078,86 @@ export const resolvers = {
|
|||||||
const message = await prisma.message.create({
|
const message = await prisma.message.create({
|
||||||
data: {
|
data: {
|
||||||
content: args.content?.trim() || null,
|
content: args.content?.trim() || null,
|
||||||
type: args.type || 'TEXT',
|
type: args.type || "TEXT",
|
||||||
senderId: context.user.id,
|
senderId: context.user.id,
|
||||||
senderOrganizationId: currentUser.organization.id,
|
senderOrganizationId: currentUser.organization.id,
|
||||||
receiverOrganizationId: args.receiverOrganizationId
|
receiverOrganizationId: args.receiverOrganizationId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
senderOrganization: {
|
senderOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiverOrganization: {
|
receiverOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Сообщение отправлено',
|
message: "Сообщение отправлено",
|
||||||
messageData: message
|
messageData: message,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending message:', error)
|
console.error("Error sending message:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что получатель является контрагентом
|
// Проверяем, что получатель является контрагентом
|
||||||
const isCounterparty = await prisma.counterparty.findFirst({
|
const isCounterparty = await prisma.counterparty.findFirst({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
counterpartyId: args.receiverOrganizationId
|
counterpartyId: args.receiverOrganizationId,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!isCounterparty) {
|
if (!isCounterparty) {
|
||||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
throw new GraphQLError(
|
||||||
|
"Можно отправлять сообщения только контрагентам"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем организацию получателя
|
// Получаем организацию получателя
|
||||||
const receiverOrganization = await prisma.organization.findUnique({
|
const receiverOrganization = await prisma.organization.findUnique({
|
||||||
where: { id: args.receiverOrganizationId }
|
where: { id: args.receiverOrganizationId },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!receiverOrganization) {
|
if (!receiverOrganization) {
|
||||||
throw new GraphQLError('Организация получателя не найдена')
|
throw new GraphQLError("Организация получателя не найдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2035,217 +2165,256 @@ export const resolvers = {
|
|||||||
const message = await prisma.message.create({
|
const message = await prisma.message.create({
|
||||||
data: {
|
data: {
|
||||||
content: null,
|
content: null,
|
||||||
type: 'VOICE',
|
type: "VOICE",
|
||||||
voiceUrl: args.voiceUrl,
|
voiceUrl: args.voiceUrl,
|
||||||
voiceDuration: args.voiceDuration,
|
voiceDuration: args.voiceDuration,
|
||||||
senderId: context.user.id,
|
senderId: context.user.id,
|
||||||
senderOrganizationId: currentUser.organization.id,
|
senderOrganizationId: currentUser.organization.id,
|
||||||
receiverOrganizationId: args.receiverOrganizationId
|
receiverOrganizationId: args.receiverOrganizationId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
senderOrganization: {
|
senderOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiverOrganization: {
|
receiverOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Голосовое сообщение отправлено',
|
message: "Голосовое сообщение отправлено",
|
||||||
messageData: message
|
messageData: message,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending voice message:', error)
|
console.error("Error sending voice message:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что получатель является контрагентом
|
// Проверяем, что получатель является контрагентом
|
||||||
const isCounterparty = await prisma.counterparty.findFirst({
|
const isCounterparty = await prisma.counterparty.findFirst({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
counterpartyId: args.receiverOrganizationId
|
counterpartyId: args.receiverOrganizationId,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!isCounterparty) {
|
if (!isCounterparty) {
|
||||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
throw new GraphQLError(
|
||||||
|
"Можно отправлять сообщения только контрагентам"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const message = await prisma.message.create({
|
const message = await prisma.message.create({
|
||||||
data: {
|
data: {
|
||||||
content: null,
|
content: null,
|
||||||
type: 'IMAGE',
|
type: "IMAGE",
|
||||||
fileUrl: args.fileUrl,
|
fileUrl: args.fileUrl,
|
||||||
fileName: args.fileName,
|
fileName: args.fileName,
|
||||||
fileSize: args.fileSize,
|
fileSize: args.fileSize,
|
||||||
fileType: args.fileType,
|
fileType: args.fileType,
|
||||||
senderId: context.user.id,
|
senderId: context.user.id,
|
||||||
senderOrganizationId: currentUser.organization.id,
|
senderOrganizationId: currentUser.organization.id,
|
||||||
receiverOrganizationId: args.receiverOrganizationId
|
receiverOrganizationId: args.receiverOrganizationId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
senderOrganization: {
|
senderOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiverOrganization: {
|
receiverOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Изображение отправлено',
|
message: "Изображение отправлено",
|
||||||
messageData: message
|
messageData: message,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending image:', error)
|
console.error("Error sending image:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что получатель является контрагентом
|
// Проверяем, что получатель является контрагентом
|
||||||
const isCounterparty = await prisma.counterparty.findFirst({
|
const isCounterparty = await prisma.counterparty.findFirst({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
counterpartyId: args.receiverOrganizationId
|
counterpartyId: args.receiverOrganizationId,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!isCounterparty) {
|
if (!isCounterparty) {
|
||||||
throw new GraphQLError('Можно отправлять сообщения только контрагентам')
|
throw new GraphQLError(
|
||||||
|
"Можно отправлять сообщения только контрагентам"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const message = await prisma.message.create({
|
const message = await prisma.message.create({
|
||||||
data: {
|
data: {
|
||||||
content: null,
|
content: null,
|
||||||
type: 'FILE',
|
type: "FILE",
|
||||||
fileUrl: args.fileUrl,
|
fileUrl: args.fileUrl,
|
||||||
fileName: args.fileName,
|
fileName: args.fileName,
|
||||||
fileSize: args.fileSize,
|
fileSize: args.fileSize,
|
||||||
fileType: args.fileType,
|
fileType: args.fileType,
|
||||||
senderId: context.user.id,
|
senderId: context.user.id,
|
||||||
senderOrganizationId: currentUser.organization.id,
|
senderOrganizationId: currentUser.organization.id,
|
||||||
receiverOrganizationId: args.receiverOrganizationId
|
receiverOrganizationId: args.receiverOrganizationId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
sender: true,
|
sender: true,
|
||||||
senderOrganization: {
|
senderOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
receiverOrganization: {
|
receiverOrganization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Файл отправлен',
|
message: "Файл отправлен",
|
||||||
messageData: message
|
messageData: message,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending file:', error)
|
console.error("Error sending file:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при отправке файла'
|
message: "Ошибка при отправке файла",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Отметить сообщения как прочитанные
|
// Отметить сообщения как прочитанные
|
||||||
markMessagesAsRead: async (_: unknown, args: { conversationId: string }, context: Context) => {
|
markMessagesAsRead: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { conversationId: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Здесь будет логика обновления статуса сообщений
|
// 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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что это фулфилмент центр
|
// Проверяем, что это фулфилмент центр
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Услуги доступны только для фулфилмент центров')
|
throw new GraphQLError("Услуги доступны только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2255,52 +2424,64 @@ export const resolvers = {
|
|||||||
description: args.input.description,
|
description: args.input.description,
|
||||||
price: args.input.price,
|
price: args.input.price,
|
||||||
imageUrl: args.input.imageUrl,
|
imageUrl: args.input.imageUrl,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Услуга успешно создана',
|
message: "Услуга успешно создана",
|
||||||
service
|
service,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating service:', error)
|
console.error("Error creating service:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что услуга принадлежит текущей организации
|
// Проверяем, что услуга принадлежит текущей организации
|
||||||
const existingService = await prisma.service.findFirst({
|
const existingService = await prisma.service.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingService) {
|
if (!existingService) {
|
||||||
throw new GraphQLError('Услуга не найдена или нет доступа')
|
throw new GraphQLError("Услуга не найдена или нет доступа");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2310,86 +2491,103 @@ export const resolvers = {
|
|||||||
name: args.input.name,
|
name: args.input.name,
|
||||||
description: args.input.description,
|
description: args.input.description,
|
||||||
price: args.input.price,
|
price: args.input.price,
|
||||||
imageUrl: args.input.imageUrl
|
imageUrl: args.input.imageUrl,
|
||||||
},
|
},
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Услуга успешно обновлена',
|
message: "Услуга успешно обновлена",
|
||||||
service
|
service,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating service:', error)
|
console.error("Error updating service:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении услуги'
|
message: "Ошибка при обновлении услуги",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить услугу
|
// Удалить услугу
|
||||||
deleteService: async (_: unknown, args: { id: string }, context: Context) => {
|
deleteService: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { id: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что услуга принадлежит текущей организации
|
// Проверяем, что услуга принадлежит текущей организации
|
||||||
const existingService = await prisma.service.findFirst({
|
const existingService = await prisma.service.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingService) {
|
if (!existingService) {
|
||||||
throw new GraphQLError('Услуга не найдена или нет доступа')
|
throw new GraphQLError("Услуга не найдена или нет доступа");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.service.delete({
|
await prisma.service.delete({
|
||||||
where: { id: args.id }
|
where: { id: args.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting service:', error)
|
console.error("Error deleting service:", error);
|
||||||
return false
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что это фулфилмент центр
|
// Проверяем, что это фулфилмент центр
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Расходники доступны только для фулфилмент центров')
|
throw new GraphQLError(
|
||||||
|
"Расходники доступны только для фулфилмент центров"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2400,52 +2598,64 @@ export const resolvers = {
|
|||||||
price: args.input.price,
|
price: args.input.price,
|
||||||
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
|
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
|
||||||
imageUrl: args.input.imageUrl,
|
imageUrl: args.input.imageUrl,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Расходник успешно создан',
|
message: "Расходник успешно создан",
|
||||||
supply
|
supply,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating supply:', error)
|
console.error("Error creating supply:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что расходник принадлежит текущей организации
|
// Проверяем, что расходник принадлежит текущей организации
|
||||||
const existingSupply = await prisma.supply.findFirst({
|
const existingSupply = await prisma.supply.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingSupply) {
|
if (!existingSupply) {
|
||||||
throw new GraphQLError('Расходник не найден или нет доступа')
|
throw new GraphQLError("Расходник не найден или нет доступа");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2456,119 +2666,292 @@ export const resolvers = {
|
|||||||
description: args.input.description,
|
description: args.input.description,
|
||||||
price: args.input.price,
|
price: args.input.price,
|
||||||
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
|
quantity: 0, // Временно устанавливаем 0, так как поле убрано из интерфейса
|
||||||
imageUrl: args.input.imageUrl
|
imageUrl: args.input.imageUrl,
|
||||||
},
|
},
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Расходник успешно обновлен',
|
message: "Расходник успешно обновлен",
|
||||||
supply
|
supply,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating supply:', error)
|
console.error("Error updating supply:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении расходника'
|
message: "Ошибка при обновлении расходника",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить расходник
|
// Удалить расходник
|
||||||
deleteSupply: async (_: unknown, args: { id: string }, context: Context) => {
|
deleteSupply: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { id: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что расходник принадлежит текущей организации
|
// Проверяем, что расходник принадлежит текущей организации
|
||||||
const existingSupply = await prisma.supply.findFirst({
|
const existingSupply = await prisma.supply.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingSupply) {
|
if (!existingSupply) {
|
||||||
throw new GraphQLError('Расходник не найден или нет доступа')
|
throw new GraphQLError("Расходник не найден или нет доступа");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.supply.delete({
|
await prisma.supply.delete({
|
||||||
where: { id: args.id }
|
where: { id: args.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting supply:', error)
|
console.error("Error deleting supply:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Создать товар
|
// Создать заказ поставки расходников
|
||||||
createProduct: async (_: unknown, args: {
|
createSupplyOrder: async (
|
||||||
input: {
|
_: unknown,
|
||||||
name: string;
|
args: {
|
||||||
article: string;
|
input: {
|
||||||
description?: string;
|
partnerId: string;
|
||||||
price: number;
|
deliveryDate: string;
|
||||||
quantity: number;
|
items: Array<{ productId: string; quantity: number }>;
|
||||||
categoryId?: string;
|
};
|
||||||
brand?: string;
|
},
|
||||||
color?: string;
|
context: Context
|
||||||
size?: string;
|
) => {
|
||||||
weight?: number;
|
|
||||||
dimensions?: string;
|
|
||||||
material?: string;
|
|
||||||
images?: string[];
|
|
||||||
mainImage?: string;
|
|
||||||
isActive?: boolean;
|
|
||||||
}
|
|
||||||
}, context: Context) => {
|
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
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') {
|
if (currentUser.organization.type !== "WHOLESALE") {
|
||||||
throw new GraphQLError('Товары доступны только для оптовиков')
|
throw new GraphQLError("Товары доступны только для оптовиков");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем уникальность артикула в рамках организации
|
// Проверяем уникальность артикула в рамках организации
|
||||||
const existingProduct = await prisma.product.findFirst({
|
const existingProduct = await prisma.product.findFirst({
|
||||||
where: {
|
where: {
|
||||||
article: args.input.article,
|
article: args.input.article,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (existingProduct) {
|
if (existingProduct) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Товар с таким артикулом уже существует'
|
message: "Товар с таким артикулом уже существует",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2589,74 +2972,78 @@ export const resolvers = {
|
|||||||
images: args.input.images || [],
|
images: args.input.images || [],
|
||||||
mainImage: args.input.mainImage,
|
mainImage: args.input.mainImage,
|
||||||
isActive: args.input.isActive ?? true,
|
isActive: args.input.isActive ?? true,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
category: true,
|
category: true,
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Товар успешно создан',
|
message: "Товар успешно создан",
|
||||||
product
|
product,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating product:', error)
|
console.error("Error creating product:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при создании товара'
|
message: "Ошибка при создании товара",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Обновить товар
|
// Обновить товар
|
||||||
updateProduct: async (_: unknown, args: {
|
updateProduct: async (
|
||||||
id: string;
|
_: unknown,
|
||||||
input: {
|
args: {
|
||||||
name: string;
|
id: string;
|
||||||
article: string;
|
input: {
|
||||||
description?: string;
|
name: string;
|
||||||
price: number;
|
article: string;
|
||||||
quantity: number;
|
description?: string;
|
||||||
categoryId?: string;
|
price: number;
|
||||||
brand?: string;
|
quantity: number;
|
||||||
color?: string;
|
categoryId?: string;
|
||||||
size?: string;
|
brand?: string;
|
||||||
weight?: number;
|
color?: string;
|
||||||
dimensions?: string;
|
size?: string;
|
||||||
material?: string;
|
weight?: number;
|
||||||
images?: string[];
|
dimensions?: string;
|
||||||
mainImage?: string;
|
material?: string;
|
||||||
isActive?: boolean;
|
images?: string[];
|
||||||
}
|
mainImage?: string;
|
||||||
}, context: Context) => {
|
isActive?: boolean;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что товар принадлежит текущей организации
|
// Проверяем, что товар принадлежит текущей организации
|
||||||
const existingProduct = await prisma.product.findFirst({
|
const existingProduct = await prisma.product.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingProduct) {
|
if (!existingProduct) {
|
||||||
throw new GraphQLError('Товар не найден или нет доступа')
|
throw new GraphQLError("Товар не найден или нет доступа");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем уникальность артикула (если он изменился)
|
// Проверяем уникальность артикула (если он изменился)
|
||||||
@ -2665,15 +3052,15 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
article: args.input.article,
|
article: args.input.article,
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
NOT: { id: args.id }
|
NOT: { id: args.id },
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (duplicateProduct) {
|
if (duplicateProduct) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Товар с таким артикулом уже существует'
|
message: "Товар с таким артикулом уже существует",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2695,141 +3082,153 @@ export const resolvers = {
|
|||||||
material: args.input.material,
|
material: args.input.material,
|
||||||
images: args.input.images || [],
|
images: args.input.images || [],
|
||||||
mainImage: args.input.mainImage,
|
mainImage: args.input.mainImage,
|
||||||
isActive: args.input.isActive ?? true
|
isActive: args.input.isActive ?? true,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
category: true,
|
category: true,
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Товар успешно обновлен',
|
message: "Товар успешно обновлен",
|
||||||
product
|
product,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating product:', error)
|
console.error("Error updating product:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении товара'
|
message: "Ошибка при обновлении товара",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить товар
|
// Удалить товар
|
||||||
deleteProduct: async (_: unknown, args: { id: string }, context: Context) => {
|
deleteProduct: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { id: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что товар принадлежит текущей организации
|
// Проверяем, что товар принадлежит текущей организации
|
||||||
const existingProduct = await prisma.product.findFirst({
|
const existingProduct = await prisma.product.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingProduct) {
|
if (!existingProduct) {
|
||||||
throw new GraphQLError('Товар не найден или нет доступа')
|
throw new GraphQLError("Товар не найден или нет доступа");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.product.delete({
|
await prisma.product.delete({
|
||||||
where: { id: args.id }
|
where: { id: args.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting product:', error)
|
console.error("Error deleting product:", error);
|
||||||
return false
|
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) {
|
if (!context.user && !context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем уникальность названия категории
|
// Проверяем уникальность названия категории
|
||||||
const existingCategory = await prisma.category.findUnique({
|
const existingCategory = await prisma.category.findUnique({
|
||||||
where: { name: args.input.name }
|
where: { name: args.input.name },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (existingCategory) {
|
if (existingCategory) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Категория с таким названием уже существует'
|
message: "Категория с таким названием уже существует",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const category = await prisma.category.create({
|
const category = await prisma.category.create({
|
||||||
data: {
|
data: {
|
||||||
name: args.input.name
|
name: args.input.name,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Категория успешно создана',
|
message: "Категория успешно создана",
|
||||||
category
|
category,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating category:', error)
|
console.error("Error creating category:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user && !context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем существование категории
|
// Проверяем существование категории
|
||||||
const existingCategory = await prisma.category.findUnique({
|
const existingCategory = await prisma.category.findUnique({
|
||||||
where: { id: args.id }
|
where: { id: args.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingCategory) {
|
if (!existingCategory) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Категория не найдена'
|
message: "Категория не найдена",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем уникальность нового названия (если изменилось)
|
// Проверяем уникальность нового названия (если изменилось)
|
||||||
if (args.input.name !== existingCategory.name) {
|
if (args.input.name !== existingCategory.name) {
|
||||||
const duplicateCategory = await prisma.category.findUnique({
|
const duplicateCategory = await prisma.category.findUnique({
|
||||||
where: { name: args.input.name }
|
where: { name: args.input.name },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (duplicateCategory) {
|
if (duplicateCategory) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Категория с таким названием уже существует'
|
message: "Категория с таким названием уже существует",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2837,113 +3236,123 @@ export const resolvers = {
|
|||||||
const category = await prisma.category.update({
|
const category = await prisma.category.update({
|
||||||
where: { id: args.id },
|
where: { id: args.id },
|
||||||
data: {
|
data: {
|
||||||
name: args.input.name
|
name: args.input.name,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Категория успешно обновлена',
|
message: "Категория успешно обновлена",
|
||||||
category
|
category,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating category:', error)
|
console.error("Error updating category:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user && !context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем существование категории
|
// Проверяем существование категории
|
||||||
const existingCategory = await prisma.category.findUnique({
|
const existingCategory = await prisma.category.findUnique({
|
||||||
where: { id: args.id },
|
where: { id: args.id },
|
||||||
include: { products: true }
|
include: { products: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingCategory) {
|
if (!existingCategory) {
|
||||||
throw new GraphQLError('Категория не найдена')
|
throw new GraphQLError("Категория не найдена");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, есть ли товары в этой категории
|
// Проверяем, есть ли товары в этой категории
|
||||||
if (existingCategory.products.length > 0) {
|
if (existingCategory.products.length > 0) {
|
||||||
throw new GraphQLError('Нельзя удалить категорию, в которой есть товары')
|
throw new GraphQLError(
|
||||||
|
"Нельзя удалить категорию, в которой есть товары"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.category.delete({
|
await prisma.category.delete({
|
||||||
where: { id: args.id }
|
where: { id: args.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting category:', error)
|
console.error("Error deleting category:", error);
|
||||||
return false
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что товар существует и активен
|
// Проверяем, что товар существует и активен
|
||||||
const product = await prisma.product.findFirst({
|
const product = await prisma.product.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.productId,
|
id: args.productId,
|
||||||
isActive: true
|
isActive: true,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Товар не найден или неактивен'
|
message: "Товар не найден или неактивен",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
||||||
if (product.organizationId === currentUser.organization.id) {
|
if (product.organizationId === currentUser.organization.id) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Нельзя добавлять собственные товары в корзину'
|
message: "Нельзя добавлять собственные товары в корзину",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Найти или создать корзину
|
// Найти или создать корзину
|
||||||
let cart = await prisma.cart.findUnique({
|
let cart = await prisma.cart.findUnique({
|
||||||
where: { organizationId: currentUser.organization.id }
|
where: { organizationId: currentUser.organization.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
cart = await prisma.cart.create({
|
cart = await prisma.cart.create({
|
||||||
data: {
|
data: {
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2952,42 +3361,42 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
cartId_productId: {
|
cartId_productId: {
|
||||||
cartId: cart.id,
|
cartId: cart.id,
|
||||||
productId: args.productId
|
productId: args.productId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (existingCartItem) {
|
if (existingCartItem) {
|
||||||
// Обновляем количество
|
// Обновляем количество
|
||||||
const newQuantity = existingCartItem.quantity + args.quantity
|
const newQuantity = existingCartItem.quantity + args.quantity;
|
||||||
|
|
||||||
if (newQuantity > product.quantity) {
|
if (newQuantity > product.quantity) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`
|
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.cartItem.update({
|
await prisma.cartItem.update({
|
||||||
where: { id: existingCartItem.id },
|
where: { id: existingCartItem.id },
|
||||||
data: { quantity: newQuantity }
|
data: { quantity: newQuantity },
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
// Создаем новый элемент корзины
|
// Создаем новый элемент корзины
|
||||||
if (args.quantity > product.quantity) {
|
if (args.quantity > product.quantity) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`
|
message: `Недостаточно товара в наличии. Доступно: ${product.quantity}`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.cartItem.create({
|
await prisma.cartItem.create({
|
||||||
data: {
|
data: {
|
||||||
cartId: cart.id,
|
cartId: cart.id,
|
||||||
productId: args.productId,
|
productId: args.productId,
|
||||||
quantity: args.quantity
|
quantity: args.quantity,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращаем обновленную корзину
|
// Возвращаем обновленную корзину
|
||||||
@ -3001,57 +3410,61 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Товар добавлен в корзину',
|
message: "Товар добавлен в корзину",
|
||||||
cart: updatedCart
|
cart: updatedCart,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding to cart:', error)
|
console.error("Error adding to cart:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
const cart = await prisma.cart.findUnique({
|
const cart = await prisma.cart.findUnique({
|
||||||
where: { organizationId: currentUser.organization.id }
|
where: { organizationId: currentUser.organization.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Корзина не найдена'
|
message: "Корзина не найдена",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что товар существует в корзине
|
// Проверяем, что товар существует в корзине
|
||||||
@ -3059,40 +3472,40 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
cartId_productId: {
|
cartId_productId: {
|
||||||
cartId: cart.id,
|
cartId: cart.id,
|
||||||
productId: args.productId
|
productId: args.productId,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
product: true
|
product: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!cartItem) {
|
if (!cartItem) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Товар не найден в корзине'
|
message: "Товар не найден в корзине",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.quantity <= 0) {
|
if (args.quantity <= 0) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Количество должно быть больше 0'
|
message: "Количество должно быть больше 0",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.quantity > cartItem.product.quantity) {
|
if (args.quantity > cartItem.product.quantity) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Недостаточно товара в наличии. Доступно: ${cartItem.product.quantity}`
|
message: `Недостаточно товара в наличии. Доступно: ${cartItem.product.quantity}`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.cartItem.update({
|
await prisma.cartItem.update({
|
||||||
where: { id: cartItem.id },
|
where: { id: cartItem.id },
|
||||||
data: { quantity: args.quantity }
|
data: { quantity: args.quantity },
|
||||||
})
|
});
|
||||||
|
|
||||||
// Возвращаем обновленную корзину
|
// Возвращаем обновленную корзину
|
||||||
const updatedCart = await prisma.cart.findUnique({
|
const updatedCart = await prisma.cart.findUnique({
|
||||||
@ -3105,57 +3518,61 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Количество товара обновлено',
|
message: "Количество товара обновлено",
|
||||||
cart: updatedCart
|
cart: updatedCart,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating cart item:', error)
|
console.error("Error updating cart item:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении корзины'
|
message: "Ошибка при обновлении корзины",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить товар из корзины
|
// Удалить товар из корзины
|
||||||
removeFromCart: async (_: unknown, args: { productId: string }, context: Context) => {
|
removeFromCart: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { productId: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
const cart = await prisma.cart.findUnique({
|
const cart = await prisma.cart.findUnique({
|
||||||
where: { organizationId: currentUser.organization.id }
|
where: { organizationId: currentUser.organization.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Корзина не найдена'
|
message: "Корзина не найдена",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3163,10 +3580,10 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
cartId_productId: {
|
cartId_productId: {
|
||||||
cartId: cart.id,
|
cartId: cart.id,
|
||||||
productId: args.productId
|
productId: args.productId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Возвращаем обновленную корзину
|
// Возвращаем обновленную корзину
|
||||||
const updatedCart = await prisma.cart.findUnique({
|
const updatedCart = await prisma.cart.findUnique({
|
||||||
@ -3179,109 +3596,113 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Товар удален из корзины',
|
message: "Товар удален из корзины",
|
||||||
cart: updatedCart
|
cart: updatedCart,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing from cart:', error)
|
console.error("Error removing from cart:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при удалении из корзины'
|
message: "Ошибка при удалении из корзины",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Очистить корзину
|
// Очистить корзину
|
||||||
clearCart: async (_: unknown, __: unknown, context: Context) => {
|
clearCart: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
const cart = await prisma.cart.findUnique({
|
const cart = await prisma.cart.findUnique({
|
||||||
where: { organizationId: currentUser.organization.id }
|
where: { organizationId: currentUser.organization.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.cartItem.deleteMany({
|
await prisma.cartItem.deleteMany({
|
||||||
where: { cartId: cart.id }
|
where: { cartId: cart.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error clearing cart:', error)
|
console.error("Error clearing cart:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Добавить товар в избранное
|
// Добавить товар в избранное
|
||||||
addToFavorites: async (_: unknown, args: { productId: string }, context: Context) => {
|
addToFavorites: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { productId: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что товар существует и активен
|
// Проверяем, что товар существует и активен
|
||||||
const product = await prisma.product.findFirst({
|
const product = await prisma.product.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.productId,
|
id: args.productId,
|
||||||
isActive: true
|
isActive: true,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Товар не найден или неактивен'
|
message: "Товар не найден или неактивен",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
// Проверяем, что пользователь не пытается добавить свой собственный товар
|
||||||
if (product.organizationId === currentUser.organization.id) {
|
if (product.organizationId === currentUser.organization.id) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Нельзя добавлять собственные товары в избранное'
|
message: "Нельзя добавлять собственные товары в избранное",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3290,25 +3711,25 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
organizationId_productId: {
|
organizationId_productId: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
productId: args.productId
|
productId: args.productId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (existingFavorite) {
|
if (existingFavorite) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Товар уже в избранном'
|
message: "Товар уже в избранном",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем товар в избранное
|
// Добавляем товар в избранное
|
||||||
await prisma.favorites.create({
|
await prisma.favorites.create({
|
||||||
data: {
|
data: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
productId: args.productId
|
productId: args.productId,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Возвращаем обновленный список избранного
|
// Возвращаем обновленный список избранного
|
||||||
const favorites = await prisma.favorites.findMany({
|
const favorites = await prisma.favorites.findMany({
|
||||||
@ -3319,44 +3740,48 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Товар добавлен в избранное',
|
message: "Товар добавлен в избранное",
|
||||||
favorites: favorites.map(favorite => favorite.product)
|
favorites: favorites.map((favorite) => favorite.product),
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding to favorites:', error)
|
console.error("Error adding to favorites:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при добавлении в избранное'
|
message: "Ошибка при добавлении в избранное",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить товар из избранного
|
// Удалить товар из избранного
|
||||||
removeFromFavorites: async (_: unknown, args: { productId: string }, context: Context) => {
|
removeFromFavorites: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { productId: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3364,9 +3789,9 @@ export const resolvers = {
|
|||||||
await prisma.favorites.deleteMany({
|
await prisma.favorites.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
productId: args.productId
|
productId: args.productId,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Возвращаем обновленный список избранного
|
// Возвращаем обновленный список избранного
|
||||||
const favorites = await prisma.favorites.findMany({
|
const favorites = await prisma.favorites.findMany({
|
||||||
@ -3377,48 +3802,52 @@ export const resolvers = {
|
|||||||
category: true,
|
category: true,
|
||||||
organization: {
|
organization: {
|
||||||
include: {
|
include: {
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Товар удален из избранного',
|
message: "Товар удален из избранного",
|
||||||
favorites: favorites.map(favorite => favorite.product)
|
favorites: favorites.map((favorite) => favorite.product),
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing from favorites:', error)
|
console.error("Error removing from favorites:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при удалении из избранного'
|
message: "Ошибка при удалении из избранного",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Создать сотрудника
|
// Создать сотрудника
|
||||||
createEmployee: async (_: unknown, args: { input: CreateEmployeeInput }, context: Context) => {
|
createEmployee: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { input: CreateEmployeeInput },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3426,136 +3855,158 @@ export const resolvers = {
|
|||||||
data: {
|
data: {
|
||||||
...args.input,
|
...args.input,
|
||||||
organizationId: currentUser.organization.id,
|
organizationId: currentUser.organization.id,
|
||||||
birthDate: args.input.birthDate ? new Date(args.input.birthDate) : undefined,
|
birthDate: args.input.birthDate
|
||||||
passportDate: args.input.passportDate ? new Date(args.input.passportDate) : undefined,
|
? new Date(args.input.birthDate)
|
||||||
hireDate: new Date(args.input.hireDate)
|
: undefined,
|
||||||
|
passportDate: args.input.passportDate
|
||||||
|
? new Date(args.input.passportDate)
|
||||||
|
: undefined,
|
||||||
|
hireDate: new Date(args.input.hireDate),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Сотрудник успешно добавлен',
|
message: "Сотрудник успешно добавлен",
|
||||||
employee
|
employee,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating employee:', error)
|
console.error("Error creating employee:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const employee = await prisma.employee.update({
|
const employee = await prisma.employee.update({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
...args.input,
|
...args.input,
|
||||||
birthDate: args.input.birthDate ? new Date(args.input.birthDate) : undefined,
|
birthDate: args.input.birthDate
|
||||||
passportDate: args.input.passportDate ? new Date(args.input.passportDate) : undefined,
|
? new Date(args.input.birthDate)
|
||||||
hireDate: args.input.hireDate ? new Date(args.input.hireDate) : undefined
|
: undefined,
|
||||||
|
passportDate: args.input.passportDate
|
||||||
|
? new Date(args.input.passportDate)
|
||||||
|
: undefined,
|
||||||
|
hireDate: args.input.hireDate
|
||||||
|
? new Date(args.input.hireDate)
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Сотрудник успешно обновлен',
|
message: "Сотрудник успешно обновлен",
|
||||||
employee
|
employee,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating employee:', error)
|
console.error("Error updating employee:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении сотрудника'
|
message: "Ошибка при обновлении сотрудника",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить сотрудника
|
// Удалить сотрудника
|
||||||
deleteEmployee: async (_: unknown, args: { id: string }, context: Context) => {
|
deleteEmployee: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { id: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.employee.delete({
|
await prisma.employee.delete({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting employee:', error)
|
console.error("Error deleting employee:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Обновить табель сотрудника
|
// Обновить табель сотрудника
|
||||||
updateEmployeeSchedule: async (_: unknown, args: { input: UpdateScheduleInput }, context: Context) => {
|
updateEmployeeSchedule: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { input: UpdateScheduleInput },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser.organization.type !== 'FULFILLMENT') {
|
if (currentUser.organization.type !== "FULFILLMENT") {
|
||||||
throw new GraphQLError('Доступно только для фулфилмент центров')
|
throw new GraphQLError("Доступно только для фулфилмент центров");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3563,12 +4014,12 @@ export const resolvers = {
|
|||||||
const employee = await prisma.employee.findFirst({
|
const employee = await prisma.employee.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.input.employeeId,
|
id: args.input.employeeId,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!employee) {
|
if (!employee) {
|
||||||
throw new GraphQLError('Сотрудник не найден')
|
throw new GraphQLError("Сотрудник не найден");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем или обновляем запись табеля
|
// Создаем или обновляем запись табеля
|
||||||
@ -3576,29 +4027,29 @@ export const resolvers = {
|
|||||||
where: {
|
where: {
|
||||||
employeeId_date: {
|
employeeId_date: {
|
||||||
employeeId: args.input.employeeId,
|
employeeId: args.input.employeeId,
|
||||||
date: new Date(args.input.date)
|
date: new Date(args.input.date),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
employeeId: args.input.employeeId,
|
employeeId: args.input.employeeId,
|
||||||
date: new Date(args.input.date),
|
date: new Date(args.input.date),
|
||||||
status: args.input.status,
|
status: args.input.status,
|
||||||
hoursWorked: args.input.hoursWorked,
|
hoursWorked: args.input.hoursWorked,
|
||||||
notes: args.input.notes
|
notes: args.input.notes,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
status: args.input.status,
|
status: args.input.status,
|
||||||
hoursWorked: args.input.hoursWorked,
|
hoursWorked: args.input.hoursWorked,
|
||||||
notes: args.input.notes
|
notes: args.input.notes,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating employee schedule:', error)
|
console.error("Error updating employee schedule:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Резолверы типов
|
// Резолверы типов
|
||||||
@ -3606,44 +4057,54 @@ export const resolvers = {
|
|||||||
users: async (parent: { id: string; users?: unknown[] }) => {
|
users: async (parent: { id: string; users?: unknown[] }) => {
|
||||||
// Если пользователи уже загружены через include, возвращаем их
|
// Если пользователи уже загружены через include, возвращаем их
|
||||||
if (parent.users) {
|
if (parent.users) {
|
||||||
return parent.users
|
return parent.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Иначе загружаем отдельно
|
// Иначе загружаем отдельно
|
||||||
return await prisma.user.findMany({
|
return await prisma.user.findMany({
|
||||||
where: { organizationId: parent.id }
|
where: { organizationId: parent.id },
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Cart: {
|
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 parent.items.reduce((total, item) => {
|
||||||
return total + (Number(item.product.price) * item.quantity)
|
return total + Number(item.product.price) * item.quantity;
|
||||||
}, 0)
|
}, 0);
|
||||||
},
|
},
|
||||||
totalItems: (parent: { items: Array<{ quantity: number }> }) => {
|
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: {
|
CartItem: {
|
||||||
totalPrice: (parent: { product: { price: number }, quantity: number }) => {
|
totalPrice: (parent: { product: { price: number }; quantity: number }) => {
|
||||||
return Number(parent.product.price) * parent.quantity
|
return Number(parent.product.price) * parent.quantity;
|
||||||
},
|
},
|
||||||
isAvailable: (parent: { product: { quantity: number, isActive: boolean }, quantity: number }) => {
|
isAvailable: (parent: {
|
||||||
return parent.product.isActive && parent.product.quantity >= parent.quantity
|
product: { quantity: number; isActive: boolean };
|
||||||
|
quantity: number;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
parent.product.isActive && parent.product.quantity >= parent.quantity
|
||||||
|
);
|
||||||
},
|
},
|
||||||
availableQuantity: (parent: { product: { quantity: number } }) => {
|
availableQuantity: (parent: { product: { quantity: number } }) => {
|
||||||
return parent.product.quantity
|
return parent.product.quantity;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
User: {
|
User: {
|
||||||
organization: async (parent: { organizationId?: string; organization?: unknown }) => {
|
organization: async (parent: {
|
||||||
|
organizationId?: string;
|
||||||
|
organization?: unknown;
|
||||||
|
}) => {
|
||||||
// Если организация уже загружена через include, возвращаем её
|
// Если организация уже загружена через include, возвращаем её
|
||||||
if (parent.organization) {
|
if (parent.organization) {
|
||||||
return parent.organization
|
return parent.organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Иначе загружаем отдельно если есть organizationId
|
// Иначе загружаем отдельно если есть organizationId
|
||||||
@ -3652,112 +4113,124 @@ export const resolvers = {
|
|||||||
where: { id: parent.organizationId },
|
where: { id: parent.organizationId },
|
||||||
include: {
|
include: {
|
||||||
apiKeys: true,
|
apiKeys: true,
|
||||||
users: true
|
users: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Message: {
|
Message: {
|
||||||
type: (parent: { type?: string | null }) => {
|
type: (parent: { type?: string | null }) => {
|
||||||
return parent.type || 'TEXT'
|
return parent.type || "TEXT";
|
||||||
},
|
},
|
||||||
createdAt: (parent: { createdAt: Date | string }) => {
|
createdAt: (parent: { createdAt: Date | string }) => {
|
||||||
if (parent.createdAt instanceof Date) {
|
if (parent.createdAt instanceof Date) {
|
||||||
return parent.createdAt.toISOString()
|
return parent.createdAt.toISOString();
|
||||||
}
|
}
|
||||||
return parent.createdAt
|
return parent.createdAt;
|
||||||
},
|
},
|
||||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||||
if (parent.updatedAt instanceof Date) {
|
if (parent.updatedAt instanceof Date) {
|
||||||
return parent.updatedAt.toISOString()
|
return parent.updatedAt.toISOString();
|
||||||
}
|
}
|
||||||
return parent.updatedAt
|
return parent.updatedAt;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Employee: {
|
Employee: {
|
||||||
birthDate: (parent: { birthDate?: Date | string | null }) => {
|
birthDate: (parent: { birthDate?: Date | string | null }) => {
|
||||||
if (!parent.birthDate) return null
|
if (!parent.birthDate) return null;
|
||||||
if (parent.birthDate instanceof Date) {
|
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 }) => {
|
passportDate: (parent: { passportDate?: Date | string | null }) => {
|
||||||
if (!parent.passportDate) return null
|
if (!parent.passportDate) return null;
|
||||||
if (parent.passportDate instanceof Date) {
|
if (parent.passportDate instanceof Date) {
|
||||||
return parent.passportDate.toISOString()
|
return parent.passportDate.toISOString();
|
||||||
}
|
}
|
||||||
return parent.passportDate
|
return parent.passportDate;
|
||||||
},
|
},
|
||||||
hireDate: (parent: { hireDate: Date | string }) => {
|
hireDate: (parent: { hireDate: Date | string }) => {
|
||||||
if (parent.hireDate instanceof Date) {
|
if (parent.hireDate instanceof Date) {
|
||||||
return parent.hireDate.toISOString()
|
return parent.hireDate.toISOString();
|
||||||
}
|
}
|
||||||
return parent.hireDate
|
return parent.hireDate;
|
||||||
},
|
},
|
||||||
createdAt: (parent: { createdAt: Date | string }) => {
|
createdAt: (parent: { createdAt: Date | string }) => {
|
||||||
if (parent.createdAt instanceof Date) {
|
if (parent.createdAt instanceof Date) {
|
||||||
return parent.createdAt.toISOString()
|
return parent.createdAt.toISOString();
|
||||||
}
|
}
|
||||||
return parent.createdAt
|
return parent.createdAt;
|
||||||
},
|
},
|
||||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||||
if (parent.updatedAt instanceof Date) {
|
if (parent.updatedAt instanceof Date) {
|
||||||
return parent.updatedAt.toISOString()
|
return parent.updatedAt.toISOString();
|
||||||
}
|
}
|
||||||
return parent.updatedAt
|
return parent.updatedAt;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
EmployeeSchedule: {
|
EmployeeSchedule: {
|
||||||
date: (parent: { date: Date | string }) => {
|
date: (parent: { date: Date | string }) => {
|
||||||
if (parent.date instanceof Date) {
|
if (parent.date instanceof Date) {
|
||||||
return parent.date.toISOString()
|
return parent.date.toISOString();
|
||||||
}
|
}
|
||||||
return parent.date
|
return parent.date;
|
||||||
},
|
},
|
||||||
createdAt: (parent: { createdAt: Date | string }) => {
|
createdAt: (parent: { createdAt: Date | string }) => {
|
||||||
if (parent.createdAt instanceof Date) {
|
if (parent.createdAt instanceof Date) {
|
||||||
return parent.createdAt.toISOString()
|
return parent.createdAt.toISOString();
|
||||||
}
|
}
|
||||||
return parent.createdAt
|
return parent.createdAt;
|
||||||
},
|
},
|
||||||
updatedAt: (parent: { updatedAt: Date | string }) => {
|
updatedAt: (parent: { updatedAt: Date | string }) => {
|
||||||
if (parent.updatedAt instanceof Date) {
|
if (parent.updatedAt instanceof Date) {
|
||||||
return parent.updatedAt.toISOString()
|
return parent.updatedAt.toISOString();
|
||||||
}
|
}
|
||||||
return parent.updatedAt
|
return parent.updatedAt;
|
||||||
},
|
},
|
||||||
employee: async (parent: { employeeId: string }) => {
|
employee: async (parent: { employeeId: string }) => {
|
||||||
return await prisma.employee.findUnique({
|
return await prisma.employee.findUnique({
|
||||||
where: { id: parent.employeeId }
|
where: { id: parent.employeeId },
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// Логистические мутации
|
// Логистические мутации
|
||||||
const logisticsMutations = {
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3768,44 +4241,57 @@ const logisticsMutations = {
|
|||||||
priceUnder1m3: args.input.priceUnder1m3,
|
priceUnder1m3: args.input.priceUnder1m3,
|
||||||
priceOver1m3: args.input.priceOver1m3,
|
priceOver1m3: args.input.priceOver1m3,
|
||||||
description: args.input.description,
|
description: args.input.description,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log('✅ Logistics created:', logistics.id)
|
console.log("✅ Logistics created:", logistics.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Логистический маршрут создан',
|
message: "Логистический маршрут создан",
|
||||||
logistics
|
logistics,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error creating logistics:', error)
|
console.error("❌ Error creating logistics:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3813,12 +4299,12 @@ const logisticsMutations = {
|
|||||||
const existingLogistics = await prisma.logistics.findFirst({
|
const existingLogistics = await prisma.logistics.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingLogistics) {
|
if (!existingLogistics) {
|
||||||
throw new GraphQLError('Логистический маршрут не найден')
|
throw new GraphQLError("Логистический маршрут не найден");
|
||||||
}
|
}
|
||||||
|
|
||||||
const logistics = await prisma.logistics.update({
|
const logistics = await prisma.logistics.update({
|
||||||
@ -3828,44 +4314,48 @@ const logisticsMutations = {
|
|||||||
toLocation: args.input.toLocation,
|
toLocation: args.input.toLocation,
|
||||||
priceUnder1m3: args.input.priceUnder1m3,
|
priceUnder1m3: args.input.priceUnder1m3,
|
||||||
priceOver1m3: args.input.priceOver1m3,
|
priceOver1m3: args.input.priceOver1m3,
|
||||||
description: args.input.description
|
description: args.input.description,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log('✅ Logistics updated:', logistics.id)
|
console.log("✅ Logistics updated:", logistics.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Логистический маршрут обновлен',
|
message: "Логистический маршрут обновлен",
|
||||||
logistics
|
logistics,
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error updating logistics:', error)
|
console.error("❌ Error updating logistics:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка при обновлении логистического маршрута'
|
message: "Ошибка при обновлении логистического маршрута",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Удалить логистический маршрут
|
// Удалить логистический маршрут
|
||||||
deleteLogistics: async (_: unknown, args: { id: string }, context: Context) => {
|
deleteLogistics: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { id: string },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
throw new GraphQLError('Требуется авторизация', {
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUser = await prisma.user.findUnique({
|
const currentUser = await prisma.user.findUnique({
|
||||||
where: { id: context.user.id },
|
where: { id: context.user.id },
|
||||||
include: { organization: true }
|
include: { organization: true },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!currentUser?.organization) {
|
if (!currentUser?.organization) {
|
||||||
throw new GraphQLError('У пользователя нет организации')
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -3873,192 +4363,202 @@ const logisticsMutations = {
|
|||||||
const existingLogistics = await prisma.logistics.findFirst({
|
const existingLogistics = await prisma.logistics.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: args.id,
|
id: args.id,
|
||||||
organizationId: currentUser.organization.id
|
organizationId: currentUser.organization.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!existingLogistics) {
|
if (!existingLogistics) {
|
||||||
throw new GraphQLError('Логистический маршрут не найден')
|
throw new GraphQLError("Логистический маршрут не найден");
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.logistics.delete({
|
await prisma.logistics.delete({
|
||||||
where: { id: args.id }
|
where: { id: args.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
console.log('✅ Logistics deleted:', args.id)
|
console.log("✅ Logistics deleted:", args.id);
|
||||||
return true
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error deleting logistics:', error)
|
console.error("❌ Error deleting logistics:", error);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// Добавляем логистические мутации к основным резолверам
|
// Добавляем логистические мутации к основным резолверам
|
||||||
resolvers.Mutation = {
|
resolvers.Mutation = {
|
||||||
...resolvers.Mutation,
|
...resolvers.Mutation,
|
||||||
...logisticsMutations
|
...logisticsMutations,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Админ резолверы
|
// Админ резолверы
|
||||||
const adminQueries = {
|
const adminQueries = {
|
||||||
adminMe: async (_: unknown, __: unknown, context: Context) => {
|
adminMe: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.admin) {
|
if (!context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация администратора', {
|
throw new GraphQLError("Требуется авторизация администратора", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const admin = await prisma.admin.findUnique({
|
const admin = await prisma.admin.findUnique({
|
||||||
where: { id: context.admin.id }
|
where: { id: context.admin.id },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!admin) {
|
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) {
|
if (!context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация администратора', {
|
throw new GraphQLError("Требуется авторизация администратора", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = args.limit || 50
|
const limit = args.limit || 50;
|
||||||
const offset = args.offset || 0
|
const offset = args.offset || 0;
|
||||||
|
|
||||||
// Строим условие поиска
|
// Строим условие поиска
|
||||||
const whereCondition: Prisma.UserWhereInput = args.search
|
const whereCondition: Prisma.UserWhereInput = args.search
|
||||||
? {
|
? {
|
||||||
OR: [
|
OR: [
|
||||||
{ phone: { contains: args.search, mode: 'insensitive' } },
|
{ phone: { contains: args.search, mode: "insensitive" } },
|
||||||
{ managerName: { contains: args.search, mode: 'insensitive' } },
|
{ managerName: { contains: args.search, mode: "insensitive" } },
|
||||||
{
|
{
|
||||||
organization: {
|
organization: {
|
||||||
OR: [
|
OR: [
|
||||||
{ name: { contains: args.search, mode: 'insensitive' } },
|
{ name: { contains: args.search, mode: "insensitive" } },
|
||||||
{ fullName: { contains: args.search, mode: 'insensitive' } },
|
{ fullName: { contains: args.search, mode: "insensitive" } },
|
||||||
{ inn: { contains: args.search, mode: 'insensitive' } }
|
{ inn: { contains: args.search, mode: "insensitive" } },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
: {}
|
: {};
|
||||||
|
|
||||||
// Получаем пользователей с пагинацией
|
// Получаем пользователей с пагинацией
|
||||||
const [users, total] = await Promise.all([
|
const [users, total] = await Promise.all([
|
||||||
prisma.user.findMany({
|
prisma.user.findMany({
|
||||||
where: whereCondition,
|
where: whereCondition,
|
||||||
include: {
|
include: {
|
||||||
organization: true
|
organization: true,
|
||||||
},
|
},
|
||||||
take: limit,
|
take: limit,
|
||||||
skip: offset,
|
skip: offset,
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: "desc" },
|
||||||
}),
|
}),
|
||||||
prisma.user.count({
|
prisma.user.count({
|
||||||
where: whereCondition
|
where: whereCondition,
|
||||||
})
|
}),
|
||||||
])
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users,
|
users,
|
||||||
total,
|
total,
|
||||||
hasMore: offset + limit < total
|
hasMore: offset + limit < total,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const adminMutations = {
|
const adminMutations = {
|
||||||
adminLogin: async (_: unknown, args: { username: string; password: string }) => {
|
adminLogin: async (
|
||||||
|
_: unknown,
|
||||||
|
args: { username: string; password: string }
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
// Найти администратора
|
// Найти администратора
|
||||||
const admin = await prisma.admin.findUnique({
|
const admin = await prisma.admin.findUnique({
|
||||||
where: { username: args.username }
|
where: { username: args.username },
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!admin) {
|
if (!admin) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Неверные учетные данные'
|
message: "Неверные учетные данные",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверить активность
|
// Проверить активность
|
||||||
if (!admin.isActive) {
|
if (!admin.isActive) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Аккаунт заблокирован'
|
message: "Аккаунт заблокирован",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверить пароль
|
// Проверить пароль
|
||||||
const isPasswordValid = await bcrypt.compare(args.password, admin.password)
|
const isPasswordValid = await bcrypt.compare(
|
||||||
|
args.password,
|
||||||
|
admin.password
|
||||||
|
);
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Неверные учетные данные'
|
message: "Неверные учетные данные",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновить время последнего входа
|
// Обновить время последнего входа
|
||||||
await prisma.admin.update({
|
await prisma.admin.update({
|
||||||
where: { id: admin.id },
|
where: { id: admin.id },
|
||||||
data: { lastLogin: new Date() }
|
data: { lastLogin: new Date() },
|
||||||
})
|
});
|
||||||
|
|
||||||
// Создать токен
|
// Создать токен
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{
|
||||||
adminId: admin.id,
|
adminId: admin.id,
|
||||||
username: admin.username,
|
username: admin.username,
|
||||||
type: 'admin'
|
type: "admin",
|
||||||
},
|
},
|
||||||
process.env.JWT_SECRET!,
|
process.env.JWT_SECRET!,
|
||||||
{ expiresIn: '24h' }
|
{ expiresIn: "24h" }
|
||||||
)
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Успешная авторизация',
|
message: "Успешная авторизация",
|
||||||
token,
|
token,
|
||||||
admin: {
|
admin: {
|
||||||
...admin,
|
...admin,
|
||||||
password: undefined // Не возвращаем пароль
|
password: undefined, // Не возвращаем пароль
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Admin login error:', error)
|
console.error("Admin login error:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Ошибка авторизации'
|
message: "Ошибка авторизации",
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
adminLogout: async (_: unknown, __: unknown, context: Context) => {
|
adminLogout: async (_: unknown, __: unknown, context: Context) => {
|
||||||
if (!context.admin) {
|
if (!context.admin) {
|
||||||
throw new GraphQLError('Требуется авторизация администратора', {
|
throw new GraphQLError("Требуется авторизация администратора", {
|
||||||
extensions: { code: 'UNAUTHENTICATED' }
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// Добавляем админ запросы и мутации к основным резолверам
|
// Добавляем админ запросы и мутации к основным резолверам
|
||||||
resolvers.Query = {
|
resolvers.Query = {
|
||||||
...resolvers.Query,
|
...resolvers.Query,
|
||||||
...adminQueries
|
...adminQueries,
|
||||||
}
|
};
|
||||||
|
|
||||||
resolvers.Mutation = {
|
resolvers.Mutation = {
|
||||||
...resolvers.Mutation,
|
...resolvers.Mutation,
|
||||||
...adminMutations
|
...adminMutations,
|
||||||
}
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { gql } from 'graphql-tag'
|
import { gql } from "graphql-tag";
|
||||||
|
|
||||||
export const typeDefs = gql`
|
export const typeDefs = gql`
|
||||||
scalar DateTime
|
scalar DateTime
|
||||||
@ -8,7 +8,10 @@ export const typeDefs = gql`
|
|||||||
organization(id: ID!): Organization
|
organization(id: ID!): Organization
|
||||||
|
|
||||||
# Поиск организаций по типу для добавления в контрагенты
|
# Поиск организаций по типу для добавления в контрагенты
|
||||||
searchOrganizations(type: OrganizationType, search: String): [Organization!]!
|
searchOrganizations(
|
||||||
|
type: OrganizationType
|
||||||
|
search: String
|
||||||
|
): [Organization!]!
|
||||||
|
|
||||||
# Мои контрагенты
|
# Мои контрагенты
|
||||||
myCounterparties: [Organization!]!
|
myCounterparties: [Organization!]!
|
||||||
@ -54,7 +57,11 @@ export const typeDefs = gql`
|
|||||||
employee(id: ID!): Employee
|
employee(id: ID!): Employee
|
||||||
|
|
||||||
# Табель сотрудника за месяц
|
# Табель сотрудника за месяц
|
||||||
employeeSchedule(employeeId: ID!, year: Int!, month: Int!): [EmployeeSchedule!]!
|
employeeSchedule(
|
||||||
|
employeeId: ID!
|
||||||
|
year: Int!
|
||||||
|
month: Int!
|
||||||
|
): [EmployeeSchedule!]!
|
||||||
|
|
||||||
# Админ запросы
|
# Админ запросы
|
||||||
adminMe: Admin
|
adminMe: Admin
|
||||||
@ -70,13 +77,17 @@ export const typeDefs = gql`
|
|||||||
verifyInn(inn: String!): InnValidationResponse!
|
verifyInn(inn: String!): InnValidationResponse!
|
||||||
|
|
||||||
# Обновление профиля пользователя
|
# Обновление профиля пользователя
|
||||||
updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResponse!
|
updateUserProfile(
|
||||||
|
input: UpdateUserProfileInput!
|
||||||
|
): UpdateUserProfileResponse!
|
||||||
|
|
||||||
# Обновление данных организации по ИНН
|
# Обновление данных организации по ИНН
|
||||||
updateOrganizationByInn(inn: String!): UpdateOrganizationResponse!
|
updateOrganizationByInn(inn: String!): UpdateOrganizationResponse!
|
||||||
|
|
||||||
# Регистрация организации
|
# Регистрация организации
|
||||||
registerFulfillmentOrganization(input: FulfillmentRegistrationInput!): AuthResponse!
|
registerFulfillmentOrganization(
|
||||||
|
input: FulfillmentRegistrationInput!
|
||||||
|
): AuthResponse!
|
||||||
registerSellerOrganization(input: SellerRegistrationInput!): AuthResponse!
|
registerSellerOrganization(input: SellerRegistrationInput!): AuthResponse!
|
||||||
|
|
||||||
# Работа с API ключами
|
# Работа с API ключами
|
||||||
@ -87,16 +98,42 @@ export const typeDefs = gql`
|
|||||||
logout: Boolean!
|
logout: Boolean!
|
||||||
|
|
||||||
# Работа с контрагентами
|
# Работа с контрагентами
|
||||||
sendCounterpartyRequest(organizationId: ID!, message: String): CounterpartyRequestResponse!
|
sendCounterpartyRequest(
|
||||||
respondToCounterpartyRequest(requestId: ID!, accept: Boolean!): CounterpartyRequestResponse!
|
organizationId: ID!
|
||||||
|
message: String
|
||||||
|
): CounterpartyRequestResponse!
|
||||||
|
respondToCounterpartyRequest(
|
||||||
|
requestId: ID!
|
||||||
|
accept: Boolean!
|
||||||
|
): CounterpartyRequestResponse!
|
||||||
cancelCounterpartyRequest(requestId: ID!): Boolean!
|
cancelCounterpartyRequest(requestId: ID!): Boolean!
|
||||||
removeCounterparty(organizationId: ID!): Boolean!
|
removeCounterparty(organizationId: ID!): Boolean!
|
||||||
|
|
||||||
# Работа с сообщениями
|
# Работа с сообщениями
|
||||||
sendMessage(receiverOrganizationId: ID!, content: String, type: MessageType = TEXT): MessageResponse!
|
sendMessage(
|
||||||
sendVoiceMessage(receiverOrganizationId: ID!, voiceUrl: String!, voiceDuration: Int!): MessageResponse!
|
receiverOrganizationId: ID!
|
||||||
sendImageMessage(receiverOrganizationId: ID!, fileUrl: String!, fileName: String!, fileSize: Int!, fileType: String!): MessageResponse!
|
content: String
|
||||||
sendFileMessage(receiverOrganizationId: ID!, fileUrl: String!, fileName: String!, fileSize: Int!, fileType: String!): MessageResponse!
|
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!
|
markMessagesAsRead(conversationId: ID!): Boolean!
|
||||||
|
|
||||||
# Работа с услугами
|
# Работа с услугами
|
||||||
@ -109,6 +146,9 @@ export const typeDefs = gql`
|
|||||||
updateSupply(id: ID!, input: SupplyInput!): SupplyResponse!
|
updateSupply(id: ID!, input: SupplyInput!): SupplyResponse!
|
||||||
deleteSupply(id: ID!): Boolean!
|
deleteSupply(id: ID!): Boolean!
|
||||||
|
|
||||||
|
# Заказы поставок расходников
|
||||||
|
createSupplyOrder(input: SupplyOrderInput!): SupplyOrderResponse!
|
||||||
|
|
||||||
# Работа с логистикой
|
# Работа с логистикой
|
||||||
createLogistics(input: LogisticsInput!): LogisticsResponse!
|
createLogistics(input: LogisticsInput!): LogisticsResponse!
|
||||||
updateLogistics(id: ID!, input: LogisticsInput!): LogisticsResponse!
|
updateLogistics(id: ID!, input: LogisticsInput!): LogisticsResponse!
|
||||||
@ -414,6 +454,55 @@ export const typeDefs = gql`
|
|||||||
supply: Supply
|
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 {
|
type Logistics {
|
||||||
id: ID!
|
id: ID!
|
||||||
@ -693,4 +782,4 @@ export const typeDefs = gql`
|
|||||||
total: Int!
|
total: Int!
|
||||||
hasMore: Boolean!
|
hasMore: Boolean!
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
Reference in New Issue
Block a user