Обновлены модели и компоненты для управления поставками и расходниками. Добавлены новые поля в модели SupplyOrder и соответствующие резолверы для поддержки логистики. Реализованы компоненты уведомлений для отображения статуса логистических заявок и поставок. Оптимизирован интерфейс для улучшения пользовательского опыта, добавлены логи для диагностики запросов. Обновлены GraphQL схемы и мутации для поддержки новых функциональных возможностей.
This commit is contained in:
@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { useSidebar } from "@/hooks/useSidebar";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@ -77,6 +78,7 @@ interface SelectedConsumable {
|
||||
|
||||
export function CreateConsumablesSupplyPage() {
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const { getSidebarMargin } = useSidebar();
|
||||
const [selectedSupplier, setSelectedSupplier] =
|
||||
useState<ConsumableSupplier | null>(null);
|
||||
@ -88,6 +90,8 @@ export function CreateConsumablesSupplyPage() {
|
||||
const [deliveryDate, setDeliveryDate] = useState("");
|
||||
const [selectedFulfillmentCenter, setSelectedFulfillmentCenter] =
|
||||
useState<ConsumableSupplier | null>(null);
|
||||
const [selectedLogistics, setSelectedLogistics] =
|
||||
useState<ConsumableSupplier | null>(null);
|
||||
const [isCreatingSupply, setIsCreatingSupply] = useState(false);
|
||||
|
||||
// Загружаем контрагентов-поставщиков расходников
|
||||
@ -117,6 +121,11 @@ export function CreateConsumablesSupplyPage() {
|
||||
counterpartiesData?.myCounterparties || []
|
||||
).filter((org: ConsumableSupplier) => org.type === "FULFILLMENT");
|
||||
|
||||
// Фильтруем логистические компании
|
||||
const logisticsPartners = (counterpartiesData?.myCounterparties || []).filter(
|
||||
(org: ConsumableSupplier) => org.type === "LOGIST"
|
||||
);
|
||||
|
||||
// Фильтруем поставщиков по поисковому запросу
|
||||
const filteredSuppliers = consumableSuppliers.filter(
|
||||
(supplier: ConsumableSupplier) =>
|
||||
@ -218,19 +227,82 @@ export function CreateConsumablesSupplyPage() {
|
||||
selectedConsumables.length === 0 ||
|
||||
!deliveryDate
|
||||
) {
|
||||
toast.error("Заполните все обязательные поля");
|
||||
toast.error(
|
||||
"Заполните все обязательные поля: поставщик, расходники и дата доставки"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Для селлеров требуется выбор фулфилмент-центра
|
||||
// TODO: Добавить проверку типа текущей организации
|
||||
if (!selectedFulfillmentCenter) {
|
||||
toast.error("Выберите фулфилмент-центр для доставки");
|
||||
return;
|
||||
}
|
||||
|
||||
// Логистика опциональна - может выбрать селлер или оставить фулфилменту
|
||||
if (selectedLogistics && !selectedLogistics.id) {
|
||||
toast.error("Некорректно выбрана логистическая компания");
|
||||
return;
|
||||
}
|
||||
|
||||
// Дополнительные проверки
|
||||
if (!selectedFulfillmentCenter.id) {
|
||||
toast.error("ID фулфилмент-центра не найден");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedSupplier.id) {
|
||||
toast.error("ID поставщика не найден");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedConsumables.length === 0) {
|
||||
toast.error("Не выбраны расходники");
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем дату
|
||||
const deliveryDateObj = new Date(deliveryDate);
|
||||
if (isNaN(deliveryDateObj.getTime())) {
|
||||
toast.error("Некорректная дата поставки");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreatingSupply(true);
|
||||
|
||||
// 🔍 ОТЛАДКА: проверяем текущего пользователя
|
||||
console.log("👤 Текущий пользователь:", {
|
||||
userId: user?.id,
|
||||
phone: user?.phone,
|
||||
organizationId: user?.organization?.id,
|
||||
organizationType: user?.organization?.type,
|
||||
organizationName:
|
||||
user?.organization?.name || user?.organization?.fullName,
|
||||
});
|
||||
|
||||
console.log("🚀 Создаем поставку с данными:", {
|
||||
partnerId: selectedSupplier.id,
|
||||
deliveryDate: deliveryDate,
|
||||
fulfillmentCenterId: selectedFulfillmentCenter.id,
|
||||
logisticsPartnerId: selectedLogistics?.id,
|
||||
hasLogistics: !!selectedLogistics?.id,
|
||||
consumableType: "SELLER_CONSUMABLES",
|
||||
itemsCount: selectedConsumables.length,
|
||||
mutationInput: {
|
||||
partnerId: selectedSupplier.id,
|
||||
deliveryDate: deliveryDate,
|
||||
fulfillmentCenterId: selectedFulfillmentCenter.id,
|
||||
...(selectedLogistics?.id
|
||||
? { logisticsPartnerId: selectedLogistics.id }
|
||||
: {}),
|
||||
consumableType: "SELLER_CONSUMABLES",
|
||||
items: selectedConsumables.map((consumable) => ({
|
||||
productId: consumable.id,
|
||||
quantity: consumable.selectedQuantity,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await createSupplyOrder({
|
||||
variables: {
|
||||
@ -238,6 +310,12 @@ export function CreateConsumablesSupplyPage() {
|
||||
partnerId: selectedSupplier.id,
|
||||
deliveryDate: deliveryDate,
|
||||
fulfillmentCenterId: selectedFulfillmentCenter.id,
|
||||
// 🔄 ЛОГИСТИКА ОПЦИОНАЛЬНА: селлер может выбрать или оставить фулфилменту
|
||||
...(selectedLogistics?.id
|
||||
? { logisticsPartnerId: selectedLogistics.id }
|
||||
: {}),
|
||||
// 🏷️ КЛАССИФИКАЦИЯ согласно правилам (раздел 2.2)
|
||||
consumableType: "SELLER_CONSUMABLES", // Расходники селлеров
|
||||
items: selectedConsumables.map((consumable) => ({
|
||||
productId: consumable.id,
|
||||
quantity: consumable.selectedQuantity,
|
||||
@ -270,7 +348,21 @@ export function CreateConsumablesSupplyPage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating consumables supply:", error);
|
||||
toast.error("Ошибка при создании поставки расходников");
|
||||
|
||||
// Детальная диагностика ошибки
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
});
|
||||
|
||||
// Показываем конкретную ошибку пользователю
|
||||
toast.error(`Ошибка: ${error.message}`);
|
||||
} else {
|
||||
console.error("Unknown error:", error);
|
||||
toast.error("Ошибка при создании поставки расходников");
|
||||
}
|
||||
} finally {
|
||||
setIsCreatingSupply(false);
|
||||
}
|
||||
@ -764,7 +856,7 @@ export function CreateConsumablesSupplyPage() {
|
||||
);
|
||||
setSelectedFulfillmentCenter(center || null);
|
||||
}}
|
||||
className="w-full bg-white/10 border border-white/20 text-white h-8 text-sm rounded px-2 focus:ring-2 focus:ring-purple-400/50 focus:border-purple-400/50"
|
||||
className="w-full bg-white/10 border border-white/20 text-white h-8 text-sm rounded px-2 pr-8 focus:ring-2 focus:ring-purple-400/50 focus:border-purple-400/50 appearance-none"
|
||||
required
|
||||
>
|
||||
<option value="" className="bg-gray-800 text-white">
|
||||
@ -782,8 +874,73 @@ export function CreateConsumablesSupplyPage() {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg
|
||||
className="w-4 h-4 text-white/60"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* БЛОК ВЫБОРА ЛОГИСТИЧЕСКОЙ КОМПАНИИ */}
|
||||
<div className="mb-3">
|
||||
<label className="text-white/60 text-xs mb-1 block">
|
||||
Логистическая компания:
|
||||
<span className="text-white/40 ml-1">(опционально)</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedLogistics?.id || ""}
|
||||
onChange={(e) => {
|
||||
const logisticsId = e.target.value;
|
||||
const logistics = logisticsPartners.find(
|
||||
(p) => p.id === logisticsId
|
||||
);
|
||||
setSelectedLogistics(logistics || null);
|
||||
}}
|
||||
className="w-full bg-white/10 border border-white/20 text-white h-8 text-sm rounded px-2 pr-8 focus:ring-2 focus:ring-purple-400/50 focus:border-purple-400/50 appearance-none"
|
||||
>
|
||||
<option value="" className="bg-gray-800 text-white">
|
||||
Выберите логистику или оставьте фулфилменту
|
||||
</option>
|
||||
{logisticsPartners.map((partner) => (
|
||||
<option
|
||||
key={partner.id}
|
||||
value={partner.id}
|
||||
className="bg-gray-800 text-white"
|
||||
>
|
||||
{partner.name || partner.fullName || "Логистика"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg
|
||||
className="w-4 h-4 text-white/60"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="text-white/60 text-xs mb-1 block">
|
||||
Дата поставки:
|
||||
|
Reference in New Issue
Block a user