Реализован функционал просмотра заявок покупателей на возврат от Wildberries API в фулфилмент-складе. Добавлена интеграция с WB API /api/v1/claims для получения заявок от всех партнеров-селлеров. Создан полнофункциональный интерфейс с поиском, фильтрацией по статусам, детальным просмотром заявок и отображением медиафайлов от покупателей.
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -25,6 +25,7 @@ import {
|
|||||||
GET_MY_FULFILLMENT_SUPPLIES, // Расходники фулфилмента
|
GET_MY_FULFILLMENT_SUPPLIES, // Расходники фулфилмента
|
||||||
GET_FULFILLMENT_WAREHOUSE_STATS, // Статистика склада с изменениями за сутки
|
GET_FULFILLMENT_WAREHOUSE_STATS, // Статистика склада с изменениями за сутки
|
||||||
} from "@/graphql/queries";
|
} from "@/graphql/queries";
|
||||||
|
import { WbReturnClaims } from "./wb-return-claims";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
Package,
|
Package,
|
||||||
@ -182,6 +183,7 @@ export function FulfillmentWarehouseDashboard() {
|
|||||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
|
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
|
||||||
const [expandedStores, setExpandedStores] = useState<Set<string>>(new Set());
|
const [expandedStores, setExpandedStores] = useState<Set<string>>(new Set());
|
||||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
||||||
|
const [showReturnClaims, setShowReturnClaims] = useState(false);
|
||||||
const [showAdditionalValues, setShowAdditionalValues] = useState(true);
|
const [showAdditionalValues, setShowAdditionalValues] = useState(true);
|
||||||
|
|
||||||
// Загружаем данные из GraphQL
|
// Загружаем данные из GraphQL
|
||||||
@ -1258,6 +1260,22 @@ export function FulfillmentWarehouseDashboard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если показываем заявки на возврат, отображаем соответствующий компонент
|
||||||
|
if (showReturnClaims) {
|
||||||
|
return (
|
||||||
|
<div className="h-screen flex overflow-hidden">
|
||||||
|
<Sidebar />
|
||||||
|
<main
|
||||||
|
className={`flex-1 ${getSidebarMargin()} px-2 py-2 overflow-hidden transition-all duration-300`}
|
||||||
|
>
|
||||||
|
<div className="h-full bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl">
|
||||||
|
<WbReturnClaims onBack={() => setShowReturnClaims(false)} />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex overflow-hidden">
|
<div className="h-screen flex overflow-hidden">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
@ -1354,6 +1372,7 @@ export function FulfillmentWarehouseDashboard() {
|
|||||||
?.percentChange
|
?.percentChange
|
||||||
}
|
}
|
||||||
description="К обработке"
|
description="К обработке"
|
||||||
|
onClick={() => setShowReturnClaims(true)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Расходники селлеров"
|
title="Расходники селлеров"
|
||||||
|
425
src/components/fulfillment-warehouse/wb-return-claims.tsx
Normal file
425
src/components/fulfillment-warehouse/wb-return-claims.tsx
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useQuery } 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 { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
Search,
|
||||||
|
Filter,
|
||||||
|
RefreshCw,
|
||||||
|
AlertTriangle,
|
||||||
|
Eye,
|
||||||
|
MessageSquare,
|
||||||
|
Clock,
|
||||||
|
CheckCircle,
|
||||||
|
XCircle,
|
||||||
|
Image as ImageIcon,
|
||||||
|
Play,
|
||||||
|
Calendar,
|
||||||
|
Package,
|
||||||
|
DollarSign,
|
||||||
|
Building2,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { GET_WB_RETURN_CLAIMS } from "@/graphql/queries";
|
||||||
|
import { formatDistanceToNow } from "date-fns";
|
||||||
|
import { ru } from "date-fns/locale";
|
||||||
|
|
||||||
|
// Типы данных
|
||||||
|
interface WbReturnClaim {
|
||||||
|
id: string;
|
||||||
|
claimType: number;
|
||||||
|
status: number;
|
||||||
|
statusEx: number;
|
||||||
|
nmId: number;
|
||||||
|
userComment: string;
|
||||||
|
wbComment?: string;
|
||||||
|
dt: string;
|
||||||
|
imtName: string;
|
||||||
|
orderDt: string;
|
||||||
|
dtUpdate: string;
|
||||||
|
photos: string[];
|
||||||
|
videoPaths: string[];
|
||||||
|
actions: string[];
|
||||||
|
price: number;
|
||||||
|
currencyCode: string;
|
||||||
|
srid: string;
|
||||||
|
sellerOrganization: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
inn: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WbReturnClaimsResponse {
|
||||||
|
claims: WbReturnClaim[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функции для форматирования
|
||||||
|
const getStatusText = (status: number, statusEx: number) => {
|
||||||
|
const statusMap: { [key: number]: string } = {
|
||||||
|
1: "Новая",
|
||||||
|
2: "Рассматривается",
|
||||||
|
3: "Одобрена",
|
||||||
|
4: "Отклонена",
|
||||||
|
5: "Возврат завершен",
|
||||||
|
};
|
||||||
|
return statusMap[status] || `Статус ${status}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: number) => {
|
||||||
|
const colorMap: { [key: number]: string } = {
|
||||||
|
1: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||||
|
2: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||||
|
3: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||||
|
4: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||||
|
5: "bg-emerald-500/20 text-emerald-300 border-emerald-500/30",
|
||||||
|
};
|
||||||
|
return colorMap[status] || "bg-gray-500/20 text-gray-300 border-gray-500/30";
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPrice = (price: number) => {
|
||||||
|
return new Intl.NumberFormat("ru-RU").format(price);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface WbReturnClaimsProps {
|
||||||
|
onBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WbReturnClaims({ onBack }: WbReturnClaimsProps) {
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [isArchive, setIsArchive] = useState(false);
|
||||||
|
const [selectedClaim, setSelectedClaim] = useState<WbReturnClaim | null>(null);
|
||||||
|
|
||||||
|
const { data, loading, error, refetch } = useQuery<{
|
||||||
|
wbReturnClaims: WbReturnClaimsResponse;
|
||||||
|
}>(GET_WB_RETURN_CLAIMS, {
|
||||||
|
variables: {
|
||||||
|
isArchive,
|
||||||
|
limit: 50,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
pollInterval: 30000, // Обновляем каждые 30 секунд
|
||||||
|
errorPolicy: "all",
|
||||||
|
notifyOnNetworkStatusChange: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const claims = data?.wbReturnClaims?.claims || [];
|
||||||
|
const total = data?.wbReturnClaims?.total || 0;
|
||||||
|
|
||||||
|
// Отладочный вывод
|
||||||
|
console.log("WB Claims Debug:", {
|
||||||
|
isArchive,
|
||||||
|
loading,
|
||||||
|
error: error?.message,
|
||||||
|
total,
|
||||||
|
claimsCount: claims.length,
|
||||||
|
hasData: !!data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Фильтрация заявок по поисковому запросу
|
||||||
|
const filteredClaims = claims.filter((claim) =>
|
||||||
|
claim.imtName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
claim.sellerOrganization.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
claim.nmId.toString().includes(searchQuery) ||
|
||||||
|
claim.userComment.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
{/* Заголовок */}
|
||||||
|
<div className="flex-shrink-0 p-6 border-b border-white/10">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={onBack}
|
||||||
|
className="text-white/70 hover:text-white"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
|
Назад
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-white">
|
||||||
|
Заявки покупателей на возврат
|
||||||
|
</h2>
|
||||||
|
<p className="text-white/70">
|
||||||
|
Всего заявок: {total} | Показано: {filteredClaims.length} | Режим: {isArchive ? "Архив" : "Активные"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
className="text-white/70 hover:text-white"
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
|
Обновить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Фильтры и поиск */}
|
||||||
|
<div className="flex-shrink-0 p-6 border-b border-white/10">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="relative flex-1 max-w-md">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40 h-4 w-4" />
|
||||||
|
<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 className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant={!isArchive ? "default" : "ghost"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsArchive(false)}
|
||||||
|
className={
|
||||||
|
!isArchive
|
||||||
|
? "bg-white/20 text-white"
|
||||||
|
: "text-white/70 hover:text-white"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Активные
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={isArchive ? "default" : "ghost"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsArchive(true)}
|
||||||
|
className={
|
||||||
|
isArchive
|
||||||
|
? "bg-white/20 text-white"
|
||||||
|
: "text-white/70 hover:text-white"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Архив
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Список заявок */}
|
||||||
|
<div className="flex-1 overflow-auto p-6">
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 p-4 bg-red-500/20 border border-red-500/30 rounded-lg">
|
||||||
|
<div className="flex items-center gap-2 text-red-300 mb-2">
|
||||||
|
<AlertTriangle className="h-4 w-4" />
|
||||||
|
<span className="font-medium">Ошибка загрузки данных</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-red-200 text-sm">
|
||||||
|
{error.message || "Не удалось получить заявки от Wildberries API"}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
className="mt-2 text-red-300 hover:text-red-200"
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-3 w-3 mr-1" />
|
||||||
|
Повторить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex items-center justify-center h-32">
|
||||||
|
<RefreshCw className="h-8 w-8 animate-spin text-white/50" />
|
||||||
|
<span className="ml-2 text-white/70">Загрузка заявок...</span>
|
||||||
|
</div>
|
||||||
|
) : filteredClaims.length === 0 ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<Package className="h-16 w-16 mx-auto text-white/30 mb-4" />
|
||||||
|
<h3 className="text-lg font-medium text-white mb-2">
|
||||||
|
Заявки не найдены
|
||||||
|
</h3>
|
||||||
|
<p className="text-white/60 mb-4">
|
||||||
|
{searchQuery
|
||||||
|
? "Попробуйте изменить критерии поиска"
|
||||||
|
: "Новых заявок на возврат пока нет"}
|
||||||
|
</p>
|
||||||
|
{!searchQuery && total === 0 && (
|
||||||
|
<div className="bg-blue-500/20 border border-blue-500/30 rounded-lg p-4 max-w-md mx-auto">
|
||||||
|
<p className="text-blue-300 text-sm">
|
||||||
|
💡 Заявки отображаются только от партнеров-селлеров с настроенными Wildberries API ключами
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{filteredClaims.map((claim) => (
|
||||||
|
<Card
|
||||||
|
key={claim.id}
|
||||||
|
className="bg-white/10 border-white/20 p-4 hover:bg-white/15 transition-colors cursor-pointer"
|
||||||
|
onClick={() => setSelectedClaim(claim)}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<Badge className={getStatusColor(claim.status)}>
|
||||||
|
{getStatusText(claim.status, claim.statusEx)}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-sm text-white/60">
|
||||||
|
№{claim.nmId}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-white/60">
|
||||||
|
{formatDistanceToNow(new Date(claim.dt), {
|
||||||
|
addSuffix: true,
|
||||||
|
locale: ru,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-white font-medium mb-2">
|
||||||
|
{claim.imtName}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-sm text-white/70 mb-2">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Building2 className="h-3 w-3" />
|
||||||
|
{claim.sellerOrganization.name}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<DollarSign className="h-3 w-3" />
|
||||||
|
{formatPrice(claim.price)} ₽
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-white/60 text-sm line-clamp-2">
|
||||||
|
{claim.userComment}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 ml-4">
|
||||||
|
{claim.photos.length > 0 && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-white/60">
|
||||||
|
<ImageIcon className="h-3 w-3" />
|
||||||
|
{claim.photos.length}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{claim.videoPaths.length > 0 && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-white/60">
|
||||||
|
<Play className="h-3 w-3" />
|
||||||
|
{claim.videoPaths.length}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button variant="ghost" size="sm" className="text-white/70 hover:text-white">
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Диалог детального просмотра */}
|
||||||
|
<Dialog open={selectedClaim !== null} onOpenChange={() => setSelectedClaim(null)}>
|
||||||
|
<DialogContent className="bg-gray-900/95 border-white/20 text-white max-w-2xl">
|
||||||
|
{selectedClaim && (
|
||||||
|
<>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-3">
|
||||||
|
<Badge className={getStatusColor(selectedClaim.status)}>
|
||||||
|
{getStatusText(selectedClaim.status, selectedClaim.statusEx)}
|
||||||
|
</Badge>
|
||||||
|
<span>Заявка №{selectedClaim.nmId}</span>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="text-white/70">
|
||||||
|
{selectedClaim.imtName}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60">Дата заявки:</span>
|
||||||
|
<p className="text-white">
|
||||||
|
{new Date(selectedClaim.dt).toLocaleString("ru-RU")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60">Дата заказа:</span>
|
||||||
|
<p className="text-white">
|
||||||
|
{new Date(selectedClaim.orderDt).toLocaleString("ru-RU")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60">Стоимость:</span>
|
||||||
|
<p className="text-white">{formatPrice(selectedClaim.price)} ₽</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60">SRID:</span>
|
||||||
|
<p className="text-white">{selectedClaim.srid}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60 text-sm">Продавец:</span>
|
||||||
|
<p className="text-white">
|
||||||
|
{selectedClaim.sellerOrganization.name} (ИНН: {selectedClaim.sellerOrganization.inn})
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60 text-sm">Комментарий покупателя:</span>
|
||||||
|
<p className="text-white bg-white/5 p-3 rounded-lg mt-1">
|
||||||
|
{selectedClaim.userComment}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedClaim.wbComment && (
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60 text-sm">Комментарий WB:</span>
|
||||||
|
<p className="text-white bg-blue-500/20 p-3 rounded-lg mt-1">
|
||||||
|
{selectedClaim.wbComment}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(selectedClaim.photos.length > 0 || selectedClaim.videoPaths.length > 0) && (
|
||||||
|
<div>
|
||||||
|
<span className="text-white/60 text-sm">Медиафайлы:</span>
|
||||||
|
<div className="flex gap-2 mt-2">
|
||||||
|
{selectedClaim.photos.map((photo, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-1 bg-white/10 px-2 py-1 rounded text-xs">
|
||||||
|
<ImageIcon className="h-3 w-3" />
|
||||||
|
Фото {index + 1}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{selectedClaim.videoPaths.map((video, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-1 bg-white/10 px-2 py-1 rounded text-xs">
|
||||||
|
<Play className="h-3 w-3" />
|
||||||
|
Видео {index + 1}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -78,9 +78,11 @@ export function SuppliesDashboard() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tab = searchParams.get("tab");
|
const tab = searchParams.get("tab");
|
||||||
if (tab === "consumables") {
|
if (tab === "consumables") {
|
||||||
setActiveTab("supplies");
|
setActiveTab("fulfillment");
|
||||||
|
setActiveSubTab("consumables");
|
||||||
} else if (tab === "goods") {
|
} else if (tab === "goods") {
|
||||||
setActiveTab("goods");
|
setActiveTab("fulfillment");
|
||||||
|
setActiveSubTab("goods");
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
@ -194,12 +196,13 @@ export function SuppliesDashboard() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveSubTab("consumables")}
|
onClick={() => setActiveSubTab("consumables")}
|
||||||
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 relative ${
|
className={`flex items-center justify-between text-xs font-medium transition-all duration-150 rounded-md px-2 relative ${
|
||||||
activeSubTab === "consumables"
|
activeSubTab === "consumables"
|
||||||
? "bg-white/15 text-white border-white/20"
|
? "bg-white/15 text-white border-white/20"
|
||||||
: "text-white/60 hover:text-white/80"
|
: "text-white/60 hover:text-white/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<Wrench className="h-3 w-3" />
|
<Wrench className="h-3 w-3" />
|
||||||
<span className="hidden sm:inline">
|
<span className="hidden sm:inline">
|
||||||
Расходники селлера
|
Расходники селлера
|
||||||
@ -208,22 +211,23 @@ export function SuppliesDashboard() {
|
|||||||
<NotificationBadge
|
<NotificationBadge
|
||||||
count={pendingCount?.supplyOrders || 0}
|
count={pendingCount?.supplyOrders || 0}
|
||||||
/>
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Кнопка создания для расходников селлера */}
|
{/* Кнопка создания внутри таба расходников */}
|
||||||
{activeSubTab === "consumables" && (
|
{activeSubTab === "consumables" && (
|
||||||
<button
|
<div
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
window.location.href = "/supplies/create-consumables";
|
window.location.href = "/supplies/create-consumables";
|
||||||
}}
|
}}
|
||||||
className="h-7 px-3 py-1 ml-2 bg-white/8 border border-white/20 hover:bg-white/12 text-xs font-medium text-white/80 hover:text-white rounded-lg transition-all duration-150 flex items-center gap-1"
|
className="h-6 px-2 py-1 bg-white/10 border border-white/20 hover:bg-white/20 text-xs font-medium text-white/80 hover:text-white rounded-md transition-all duration-150 flex items-center gap-1 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-2.5 w-2.5" />
|
||||||
<span className="hidden sm:inline">Создать поставку</span>
|
<span className="hidden lg:inline">Создать</span>
|
||||||
<span className="sm:hidden">Создать</span>
|
</div>
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -236,57 +240,61 @@ export function SuppliesDashboard() {
|
|||||||
<div className="grid grid-cols-2 flex-1">
|
<div className="grid grid-cols-2 flex-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveSubTab("wildberries")}
|
onClick={() => setActiveSubTab("wildberries")}
|
||||||
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 ${
|
className={`flex items-center justify-between text-xs font-medium transition-all duration-150 rounded-md px-2 ${
|
||||||
activeSubTab === "wildberries"
|
activeSubTab === "wildberries"
|
||||||
? "bg-white/15 text-white border-white/20"
|
? "bg-white/15 text-white border-white/20"
|
||||||
: "text-white/60 hover:text-white/80"
|
: "text-white/60 hover:text-white/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<ShoppingCart className="h-3 w-3" />
|
<ShoppingCart className="h-3 w-3" />
|
||||||
<span className="hidden sm:inline">Wildberries</span>
|
<span className="hidden sm:inline">Wildberries</span>
|
||||||
<span className="sm:hidden">W</span>
|
<span className="sm:hidden">W</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Кнопка создания внутри таба Wildberries */}
|
||||||
|
{activeSubTab === "wildberries" && (
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.location.href = "/supplies/create-wildberries";
|
||||||
|
}}
|
||||||
|
className="h-6 px-2 py-1 bg-white/10 border border-white/20 hover:bg-white/20 text-xs font-medium text-white/80 hover:text-white rounded-md transition-all duration-150 flex items-center gap-1 cursor-pointer"
|
||||||
|
>
|
||||||
|
<Plus className="h-2.5 w-2.5" />
|
||||||
|
<span className="hidden lg:inline">Создать</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveSubTab("ozon")}
|
onClick={() => setActiveSubTab("ozon")}
|
||||||
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 ${
|
className={`flex items-center justify-between text-xs font-medium transition-all duration-150 rounded-md px-2 ${
|
||||||
activeSubTab === "ozon"
|
activeSubTab === "ozon"
|
||||||
? "bg-white/15 text-white border-white/20"
|
? "bg-white/15 text-white border-white/20"
|
||||||
: "text-white/60 hover:text-white/80"
|
: "text-white/60 hover:text-white/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<ShoppingCart className="h-3 w-3" />
|
<ShoppingCart className="h-3 w-3" />
|
||||||
<span className="hidden sm:inline">Ozon</span>
|
<span className="hidden sm:inline">Ozon</span>
|
||||||
<span className="sm:hidden">O</span>
|
<span className="sm:hidden">O</span>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Кнопка создания для Wildberries */}
|
{/* Кнопка создания внутри таба Ozon */}
|
||||||
{activeSubTab === "wildberries" && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
window.location.href = "/supplies/create-wildberries";
|
|
||||||
}}
|
|
||||||
className="h-7 px-3 py-1 ml-2 bg-white/8 border border-white/20 hover:bg-white/12 text-xs font-medium text-white/80 hover:text-white rounded-lg transition-all duration-150 flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Plus className="h-3 w-3" />
|
|
||||||
<span className="hidden sm:inline">Создать поставку</span>
|
|
||||||
<span className="sm:hidden">Создать</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Кнопка создания для Ozon */}
|
|
||||||
{activeSubTab === "ozon" && (
|
{activeSubTab === "ozon" && (
|
||||||
<button
|
<div
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
window.location.href = "/supplies/create-ozon";
|
window.location.href = "/supplies/create-ozon";
|
||||||
}}
|
}}
|
||||||
className="h-7 px-3 py-1 ml-2 bg-white/8 border border-white/20 hover:bg-white/12 text-xs font-medium text-white/80 hover:text-white rounded-lg transition-all duration-150 flex items-center gap-1"
|
className="h-6 px-2 py-1 bg-white/10 border border-white/20 hover:bg-white/20 text-xs font-medium text-white/80 hover:text-white rounded-md transition-all duration-150 flex items-center gap-1 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-2.5 w-2.5" />
|
||||||
<span className="hidden sm:inline">Создать поставку</span>
|
<span className="hidden lg:inline">Создать</span>
|
||||||
<span className="sm:hidden">Создать</span>
|
</div>
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -299,57 +307,61 @@ export function SuppliesDashboard() {
|
|||||||
<div className="grid grid-cols-2 flex-1">
|
<div className="grid grid-cols-2 flex-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveThirdTab("cards")}
|
onClick={() => setActiveThirdTab("cards")}
|
||||||
className={`flex items-center gap-1 text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
|
className={`flex items-center justify-between text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
|
||||||
activeThirdTab === "cards"
|
activeThirdTab === "cards"
|
||||||
? "bg-white/10 text-white"
|
? "bg-white/10 text-white"
|
||||||
: "text-white/50 hover:text-white/70"
|
: "text-white/50 hover:text-white/70"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<FileText className="h-2.5 w-2.5" />
|
<FileText className="h-2.5 w-2.5" />
|
||||||
<span className="hidden sm:inline">Карточки</span>
|
<span className="hidden sm:inline">Карточки</span>
|
||||||
<span className="sm:hidden">К</span>
|
<span className="sm:hidden">К</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Кнопка создания внутри таба карточек */}
|
||||||
|
{activeThirdTab === "cards" && (
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.location.href = "/supplies/create-cards";
|
||||||
|
}}
|
||||||
|
className="h-5 px-1.5 py-0.5 bg-white/8 border border-white/15 hover:bg-white/15 text-xs font-normal text-white/60 hover:text-white/80 rounded-sm transition-all duration-150 flex items-center gap-0.5 cursor-pointer"
|
||||||
|
>
|
||||||
|
<Plus className="h-2 w-2" />
|
||||||
|
<span className="hidden xl:inline text-xs">Создать</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveThirdTab("suppliers")}
|
onClick={() => setActiveThirdTab("suppliers")}
|
||||||
className={`flex items-center gap-1 text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
|
className={`flex items-center justify-between text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
|
||||||
activeThirdTab === "suppliers"
|
activeThirdTab === "suppliers"
|
||||||
? "bg-white/10 text-white"
|
? "bg-white/10 text-white"
|
||||||
: "text-white/50 hover:text-white/70"
|
: "text-white/50 hover:text-white/70"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<Building2 className="h-2.5 w-2.5" />
|
<Building2 className="h-2.5 w-2.5" />
|
||||||
<span className="hidden sm:inline">Поставщики</span>
|
<span className="hidden sm:inline">Поставщики</span>
|
||||||
<span className="sm:hidden">П</span>
|
<span className="sm:hidden">П</span>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Кнопка создания для карточек */}
|
{/* Кнопка создания внутри таба поставщиков */}
|
||||||
{activeThirdTab === "cards" && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
window.location.href = "/supplies/create-cards";
|
|
||||||
}}
|
|
||||||
className="h-6 px-2 py-1 ml-2 bg-white/5 border border-white/15 hover:bg-white/8 text-xs font-normal text-white/60 hover:text-white/80 rounded-md transition-all duration-150 flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Plus className="h-3 w-3" />
|
|
||||||
<span className="hidden sm:inline">Создать поставку</span>
|
|
||||||
<span className="sm:hidden">Создать</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Кнопка создания для поставщиков */}
|
|
||||||
{activeThirdTab === "suppliers" && (
|
{activeThirdTab === "suppliers" && (
|
||||||
<button
|
<div
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
window.location.href = "/supplies/create-suppliers";
|
window.location.href = "/supplies/create-suppliers";
|
||||||
}}
|
}}
|
||||||
className="h-6 px-2 py-1 ml-2 bg-white/5 border border-white/15 hover:bg-white/8 text-xs font-normal text-white/60 hover:text-white/80 rounded-md transition-all duration-150 flex items-center gap-1"
|
className="h-5 px-1.5 py-0.5 bg-white/8 border border-white/15 hover:bg-white/15 text-xs font-normal text-white/60 hover:text-white/80 rounded-sm transition-all duration-150 flex items-center gap-0.5 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-2 w-2" />
|
||||||
<span className="hidden sm:inline">Создать поставку</span>
|
<span className="hidden xl:inline text-xs">Создать</span>
|
||||||
<span className="sm:hidden">Создать</span>
|
</div>
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,5 +1,38 @@
|
|||||||
import { gql } from "graphql-tag";
|
import { gql } from "graphql-tag";
|
||||||
|
|
||||||
|
// Запрос для получения заявок покупателей на возврат от Wildberries
|
||||||
|
export const GET_WB_RETURN_CLAIMS = gql`
|
||||||
|
query GetWbReturnClaims($isArchive: Boolean!, $limit: Int, $offset: Int) {
|
||||||
|
wbReturnClaims(isArchive: $isArchive, limit: $limit, offset: $offset) {
|
||||||
|
claims {
|
||||||
|
id
|
||||||
|
claimType
|
||||||
|
status
|
||||||
|
statusEx
|
||||||
|
nmId
|
||||||
|
userComment
|
||||||
|
wbComment
|
||||||
|
dt
|
||||||
|
imtName
|
||||||
|
orderDt
|
||||||
|
dtUpdate
|
||||||
|
photos
|
||||||
|
videoPaths
|
||||||
|
actions
|
||||||
|
price
|
||||||
|
currencyCode
|
||||||
|
srid
|
||||||
|
sellerOrganization {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
inn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const GET_ME = gql`
|
export const GET_ME = gql`
|
||||||
query GetMe {
|
query GetMe {
|
||||||
me {
|
me {
|
||||||
|
@ -8171,6 +8171,147 @@ const wildberriesQueries = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Получение заявок покупателей на возврат от Wildberries от всех партнеров-селлеров
|
||||||
|
wbReturnClaims: async (
|
||||||
|
_: unknown,
|
||||||
|
{ isArchive, limit, offset }: { isArchive: boolean; limit?: number; offset?: number },
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
if (!context.user) {
|
||||||
|
throw new GraphQLError("Требуется авторизация", {
|
||||||
|
extensions: { code: "UNAUTHENTICATED" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Получаем текущую организацию пользователя (фулфилмент)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: context.user.id },
|
||||||
|
include: {
|
||||||
|
organization: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user?.organization) {
|
||||||
|
throw new GraphQLError("У пользователя нет организации");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что это фулфилмент организация
|
||||||
|
if (user.organization.type !== "FULFILLMENT") {
|
||||||
|
throw new GraphQLError("Доступ только для фулфилмент организаций");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем всех партнеров-селлеров с активными WB API ключами
|
||||||
|
const partnerSellerOrgs = await prisma.counterparty.findMany({
|
||||||
|
where: {
|
||||||
|
organizationId: user.organization.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
counterparty: {
|
||||||
|
include: {
|
||||||
|
apiKeys: {
|
||||||
|
where: {
|
||||||
|
marketplace: "WILDBERRIES",
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Фильтруем только селлеров с WB API ключами
|
||||||
|
const sellersWithWbKeys = partnerSellerOrgs.filter(
|
||||||
|
(partner) =>
|
||||||
|
partner.counterparty.type === "SELLER" &&
|
||||||
|
partner.counterparty.apiKeys.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sellersWithWbKeys.length === 0) {
|
||||||
|
return {
|
||||||
|
claims: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${sellersWithWbKeys.length} seller partners with WB keys`);
|
||||||
|
|
||||||
|
// Получаем заявки от всех селлеров параллельно
|
||||||
|
const claimsPromises = sellersWithWbKeys.map(async (partner) => {
|
||||||
|
const wbApiKey = partner.counterparty.apiKeys[0].apiKey;
|
||||||
|
const wbService = new WildberriesService(wbApiKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const claimsResponse = await wbService.getClaims({
|
||||||
|
isArchive,
|
||||||
|
limit: Math.ceil((limit || 50) / sellersWithWbKeys.length), // Распределяем лимит между селлерами
|
||||||
|
offset: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем информацию о селлере к каждой заявке
|
||||||
|
const claimsWithSeller = claimsResponse.claims.map((claim) => ({
|
||||||
|
...claim,
|
||||||
|
sellerOrganization: {
|
||||||
|
id: partner.counterparty.id,
|
||||||
|
name: partner.counterparty.name || "Неизвестная организация",
|
||||||
|
inn: partner.counterparty.inn || "",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(`Got ${claimsWithSeller.length} claims from seller ${partner.counterparty.name}`);
|
||||||
|
return claimsWithSeller;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching claims for seller ${partner.counterparty.name}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const allClaims = (await Promise.all(claimsPromises)).flat();
|
||||||
|
console.log(`Total claims aggregated: ${allClaims.length}`);
|
||||||
|
|
||||||
|
// Сортируем по дате создания (новые первыми)
|
||||||
|
allClaims.sort((a, b) => new Date(b.dt).getTime() - new Date(a.dt).getTime());
|
||||||
|
|
||||||
|
// Применяем пагинацию
|
||||||
|
const paginatedClaims = allClaims.slice(offset || 0, (offset || 0) + (limit || 50));
|
||||||
|
console.log(`Paginated claims: ${paginatedClaims.length}`);
|
||||||
|
|
||||||
|
// Преобразуем в формат фронтенда
|
||||||
|
const transformedClaims = paginatedClaims.map((claim) => ({
|
||||||
|
id: claim.id,
|
||||||
|
claimType: claim.claim_type,
|
||||||
|
status: claim.status,
|
||||||
|
statusEx: claim.status_ex,
|
||||||
|
nmId: claim.nm_id,
|
||||||
|
userComment: claim.user_comment || "",
|
||||||
|
wbComment: claim.wb_comment || null,
|
||||||
|
dt: claim.dt,
|
||||||
|
imtName: claim.imt_name,
|
||||||
|
orderDt: claim.order_dt,
|
||||||
|
dtUpdate: claim.dt_update,
|
||||||
|
photos: claim.photos || [],
|
||||||
|
videoPaths: claim.video_paths || [],
|
||||||
|
actions: claim.actions || [],
|
||||||
|
price: claim.price,
|
||||||
|
currencyCode: claim.currency_code,
|
||||||
|
srid: claim.srid,
|
||||||
|
sellerOrganization: claim.sellerOrganization,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(`Returning ${transformedClaims.length} transformed claims to frontend`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
claims: transformedClaims,
|
||||||
|
total: allClaims.length,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching WB return claims:", error);
|
||||||
|
throw new GraphQLError(
|
||||||
|
error instanceof Error ? error.message : "Ошибка получения заявок на возврат"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Резолверы для внешней рекламы
|
// Резолверы для внешней рекламы
|
||||||
|
@ -118,6 +118,13 @@ export const typeDefs = gql`
|
|||||||
# Список кампаний Wildberries
|
# Список кампаний Wildberries
|
||||||
getWildberriesCampaignsList: WildberriesCampaignsListResponse!
|
getWildberriesCampaignsList: WildberriesCampaignsListResponse!
|
||||||
|
|
||||||
|
# Заявки покупателей на возврат от Wildberries (для фулфилмента)
|
||||||
|
wbReturnClaims(
|
||||||
|
isArchive: Boolean!
|
||||||
|
limit: Int
|
||||||
|
offset: Int
|
||||||
|
): WbReturnClaimsResponse!
|
||||||
|
|
||||||
# Типы для внешней рекламы
|
# Типы для внешней рекламы
|
||||||
getExternalAds(dateFrom: String!, dateTo: String!): ExternalAdsResponse!
|
getExternalAds(dateFrom: String!, dateTo: String!): ExternalAdsResponse!
|
||||||
|
|
||||||
@ -1338,6 +1345,39 @@ export const typeDefs = gql`
|
|||||||
): WBWarehouseCacheResponse!
|
): WBWarehouseCacheResponse!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Типы для заявок на возврат WB
|
||||||
|
type WbReturnClaim {
|
||||||
|
id: String!
|
||||||
|
claimType: Int!
|
||||||
|
status: Int!
|
||||||
|
statusEx: Int!
|
||||||
|
nmId: Int!
|
||||||
|
userComment: String!
|
||||||
|
wbComment: String
|
||||||
|
dt: String!
|
||||||
|
imtName: String!
|
||||||
|
orderDt: String!
|
||||||
|
dtUpdate: String!
|
||||||
|
photos: [String!]!
|
||||||
|
videoPaths: [String!]!
|
||||||
|
actions: [String!]!
|
||||||
|
price: Int!
|
||||||
|
currencyCode: String!
|
||||||
|
srid: String!
|
||||||
|
sellerOrganization: WbSellerOrganization!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WbSellerOrganization {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
inn: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WbReturnClaimsResponse {
|
||||||
|
claims: [WbReturnClaim!]!
|
||||||
|
total: Int!
|
||||||
|
}
|
||||||
|
|
||||||
# Типы для статистики склада фулфилмента
|
# Типы для статистики склада фулфилмента
|
||||||
type FulfillmentWarehouseStats {
|
type FulfillmentWarehouseStats {
|
||||||
products: WarehouseStatsItem!
|
products: WarehouseStatsItem!
|
||||||
|
Reference in New Issue
Block a user