Создан единый источник истины rules-complete.md v9.1 с полной интеграцией всех правил системы. Консолидированы правила создания предметов по ролям, уточнен статус брака (НЕ РЕАЛИЗОВАНО), обновлен механизм учета ПЛАН/ФАКТ с заменой брака на потери при пересчете. Добавлен экономический учет расходников фулфилмента для селлера через рецептуру. Удалены дублирующие файлы правил (CLAUDE.md, development-checklist.md, work-protocols.md, violation-prevention-protocol.md, self-validation.md, description.md). Интегрированы UI структуры создания поставок и концепция многоуровневых таблиц.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-05 15:29:41 +03:00
parent ee72a9488b
commit d30e3f9666
23 changed files with 2038 additions and 6162 deletions

View File

@ -4,6 +4,8 @@ import React, { useState } from "react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { useQuery } from "@apollo/client";
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
import {
ChevronDown,
ChevronRight,
@ -75,119 +77,14 @@ interface Supply {
status: "planned" | "in-transit" | "delivered" | "completed";
}
// Моковые данные для товаров
const mockGoodsSupplies: Supply[] = [
{
id: "1",
number: 1,
deliveryDate: "2024-01-15",
createdDate: "2024-01-10",
status: "delivered",
plannedTotal: 180,
actualTotal: 173,
defectTotal: 2,
totalProductPrice: 3750000,
totalFulfillmentPrice: 43000,
totalLogisticsPrice: 27000,
grandTotal: 3820000,
routes: [
{
id: "r1",
from: "Садовод",
fromAddress: "Москва, 14-й км МКАД",
to: "SFERAV Logistics",
toAddress: "Москва, ул. Складская, 15",
totalProductPrice: 3600000,
fulfillmentServicePrice: 25000,
logisticsPrice: 15000,
totalAmount: 3640000,
wholesalers: [
{
id: "w1",
name: 'ООО "ТехноСнаб"',
inn: "7701234567",
contact: "+7 (495) 123-45-67",
address: "Москва, ул. Торговая, 1",
totalAmount: 3600000,
products: [
{
id: "p1",
name: "Смартфон iPhone 15",
sku: "APL-IP15-128",
category: "Электроника",
plannedQty: 50,
actualQty: 48,
defectQty: 2,
productPrice: 75000,
parameters: [
{ id: "param1", name: "Цвет", value: "Черный" },
{ id: "param2", name: "Память", value: "128", unit: "ГБ" },
{ id: "param3", name: "Гарантия", value: "12", unit: "мес" },
],
},
],
},
],
},
],
},
{
id: "2",
number: 2,
deliveryDate: "2024-01-20",
createdDate: "2024-01-12",
status: "in-transit",
plannedTotal: 30,
actualTotal: 30,
defectTotal: 0,
totalProductPrice: 750000,
totalFulfillmentPrice: 18000,
totalLogisticsPrice: 12000,
grandTotal: 780000,
routes: [
{
id: "r3",
from: "Садовод",
fromAddress: "Москва, 14-й км МКАД",
to: "WB Подольск",
toAddress: "Подольск, ул. Складская, 25",
totalProductPrice: 750000,
fulfillmentServicePrice: 18000,
logisticsPrice: 12000,
totalAmount: 780000,
wholesalers: [
{
id: "w3",
name: 'ООО "АудиоТех"',
inn: "7702345678",
contact: "+7 (495) 555-12-34",
address: "Москва, ул. Звуковая, 8",
totalAmount: 750000,
products: [
{
id: "p3",
name: "Наушники AirPods Pro",
sku: "APL-AP-PRO2",
category: "Аудио",
plannedQty: 30,
actualQty: 30,
defectQty: 0,
productPrice: 25000,
parameters: [
{ id: "param6", name: "Тип", value: "Беспроводные" },
{ id: "param7", name: "Шумоподавление", value: "Активное" },
{ id: "param8", name: "Время работы", value: "6", unit: "ч" },
],
},
],
},
],
},
],
},
];
// Данные поставок товаров из GraphQL
export function SuppliesGoodsTab() {
// Загружаем реальные данные поставок товаров
const { data: supplyOrdersData, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
errorPolicy: 'all'
});
const [expandedSupplies, setExpandedSupplies] = useState<Set<string>>(
new Set()
);
@ -199,6 +96,25 @@ export function SuppliesGoodsTab() {
new Set()
);
// Преобразуем данные из GraphQL в нужный формат
const goodsSupplies: Supply[] = (supplyOrdersData?.supplyOrders || [])
.filter((order: any) => order.status === 'CONFIRMED' || order.status === 'DELIVERED')
.map((order: any, index: number) => ({
id: order.id,
number: index + 1,
deliveryDate: order.deliveryDate || new Date().toISOString().split('T')[0],
createdDate: order.createdAt?.split('T')[0] || new Date().toISOString().split('T')[0],
status: order.status === 'DELIVERED' ? 'delivered' : 'in-transit',
plannedTotal: order.totalItems || 0,
actualTotal: order.totalItems || 0,
defectTotal: 0,
totalProductPrice: order.totalAmount || 0,
totalFulfillmentPrice: 0,
totalLogisticsPrice: 0,
grandTotal: order.totalAmount || 0,
routes: []
}));
const toggleSupplyExpansion = (supplyId: string) => {
const newExpanded = new Set(expandedSupplies);
if (newExpanded.has(supplyId)) {
@ -321,7 +237,7 @@ export function SuppliesGoodsTab() {
<div>
<p className="text-white/60 text-xs">Поставок товаров</p>
<p className="text-xl font-bold text-white">
{mockGoodsSupplies.length}
{loading ? '...' : goodsSupplies.length}
</p>
</div>
</div>
@ -335,8 +251,8 @@ export function SuppliesGoodsTab() {
<div>
<p className="text-white/60 text-xs">Сумма товаров</p>
<p className="text-xl font-bold text-white">
{formatCurrency(
mockGoodsSupplies.reduce(
{loading ? '...' : formatCurrency(
goodsSupplies.reduce(
(sum, supply) => sum + supply.grandTotal,
0
)
@ -354,8 +270,8 @@ export function SuppliesGoodsTab() {
<div>
<p className="text-white/60 text-xs">В пути</p>
<p className="text-xl font-bold text-white">
{
mockGoodsSupplies.filter(
{loading ? '...' :
goodsSupplies.filter(
(supply) => supply.status === "in-transit"
).length
}
@ -372,8 +288,8 @@ export function SuppliesGoodsTab() {
<div>
<p className="text-white/60 text-xs">С браком</p>
<p className="text-xl font-bold text-white">
{
mockGoodsSupplies.filter((supply) => supply.defectTotal > 0)
{loading ? '...' :
goodsSupplies.filter((supply) => supply.defectTotal > 0)
.length
}
</p>
@ -416,7 +332,25 @@ export function SuppliesGoodsTab() {
</tr>
</thead>
<tbody>
{mockGoodsSupplies.map((supply) => {
{loading && (
<tr>
<td colSpan={11} className="p-8 text-center">
<div className="text-white/60">Загрузка данных...</div>
</td>
</tr>
)}
{!loading && goodsSupplies.length === 0 && (
<tr>
<td colSpan={11} className="p-8 text-center">
<div className="text-white/60">
<Package className="h-12 w-12 mx-auto mb-4 text-white/20" />
<div className="text-lg font-semibold text-white mb-2">Поставки товаров не найдены</div>
<div>Создайте первую поставку товаров через карточки или поставщиков</div>
</div>
</td>
</tr>
)}
{!loading && goodsSupplies.map((supply) => {
const isSupplyExpanded = expandedSupplies.has(supply.id);
return (