Создан единый источник истины 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:
@ -41,6 +41,7 @@ import { toast } from 'sonner'
|
||||
import { format } from 'date-fns'
|
||||
import { ru } from 'date-fns/locale'
|
||||
import { SelectedCard, FulfillmentService, ConsumableService, WildberriesCard } from '@/types/supplies'
|
||||
import { ProductCardSkeletonGrid } from '@/components/ui/product-card-skeleton'
|
||||
|
||||
|
||||
|
||||
@ -86,173 +87,27 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
const [selectedCardForDetails, setSelectedCardForDetails] = useState<WildberriesCard | null>(null)
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||
|
||||
// Моковые товары для демонстрации
|
||||
const getMockCards = (): WildberriesCard[] => [
|
||||
{
|
||||
nmID: 123456789,
|
||||
vendorCode: 'SKU001',
|
||||
title: 'Смартфон Samsung Galaxy A54',
|
||||
description: 'Современный смартфон с отличной камерой и долгим временем автономной работы',
|
||||
brand: 'Samsung',
|
||||
object: 'Смартфоны',
|
||||
parent: 'Электроника',
|
||||
countryProduction: 'Корея',
|
||||
supplierVendorCode: 'SUPPLIER-001',
|
||||
mediaFiles: ['/api/placeholder/400/400', '/api/placeholder/400/401', '/api/placeholder/400/402'],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 123456,
|
||||
techSize: '128GB',
|
||||
wbSize: '128GB Черный',
|
||||
price: 25990,
|
||||
discountedPrice: 22990,
|
||||
quantity: 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nmID: 987654321,
|
||||
vendorCode: 'SKU002',
|
||||
title: 'Наушники Apple AirPods Pro',
|
||||
description: 'Беспроводные наушники с активным шумоподавлением и пространственным звуком',
|
||||
brand: 'Apple',
|
||||
object: 'Наушники',
|
||||
parent: 'Электроника',
|
||||
countryProduction: 'Китай',
|
||||
supplierVendorCode: 'SUPPLIER-002',
|
||||
mediaFiles: ['/api/placeholder/400/403', '/api/placeholder/400/404'],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 987654,
|
||||
techSize: 'Standard',
|
||||
wbSize: 'Белый',
|
||||
price: 24990,
|
||||
discountedPrice: 19990,
|
||||
quantity: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nmID: 555666777,
|
||||
vendorCode: 'SKU003',
|
||||
title: 'Кроссовки Nike Air Max 270',
|
||||
description: 'Спортивные кроссовки с современным дизайном и комфортной посадкой',
|
||||
brand: 'Nike',
|
||||
object: 'Кроссовки',
|
||||
parent: 'Обувь',
|
||||
countryProduction: 'Вьетнам',
|
||||
supplierVendorCode: 'SUPPLIER-003',
|
||||
mediaFiles: ['/api/placeholder/400/405', '/api/placeholder/400/406', '/api/placeholder/400/407'],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 555666,
|
||||
techSize: '42',
|
||||
wbSize: '42 EU',
|
||||
price: 12990,
|
||||
discountedPrice: 9990,
|
||||
quantity: 25
|
||||
},
|
||||
{
|
||||
chrtID: 555667,
|
||||
techSize: '43',
|
||||
wbSize: '43 EU',
|
||||
price: 12990,
|
||||
discountedPrice: 9990,
|
||||
quantity: 20
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nmID: 444333222,
|
||||
vendorCode: 'SKU004',
|
||||
title: 'Футболка Adidas Originals',
|
||||
description: 'Классическая футболка из органического хлопка с логотипом бренда',
|
||||
brand: 'Adidas',
|
||||
object: 'Футболки',
|
||||
parent: 'Одежда',
|
||||
countryProduction: 'Бангладеш',
|
||||
supplierVendorCode: 'SUPPLIER-004',
|
||||
mediaFiles: ['/api/placeholder/400/408', '/api/placeholder/400/409'],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 444333,
|
||||
techSize: 'M',
|
||||
wbSize: 'M',
|
||||
price: 2990,
|
||||
discountedPrice: 2490,
|
||||
quantity: 50
|
||||
},
|
||||
{
|
||||
chrtID: 444334,
|
||||
techSize: 'L',
|
||||
wbSize: 'L',
|
||||
price: 2990,
|
||||
discountedPrice: 2490,
|
||||
quantity: 45
|
||||
},
|
||||
{
|
||||
chrtID: 444335,
|
||||
techSize: 'XL',
|
||||
wbSize: 'XL',
|
||||
price: 2990,
|
||||
discountedPrice: 2490,
|
||||
quantity: 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nmID: 111222333,
|
||||
vendorCode: 'SKU005',
|
||||
title: 'Рюкзак для ноутбука Xiaomi',
|
||||
description: 'Стильный и функциональный рюкзак для ноутбука до 15.6 дюймов',
|
||||
brand: 'Xiaomi',
|
||||
object: 'Рюкзаки',
|
||||
parent: 'Аксессуары',
|
||||
countryProduction: 'Китай',
|
||||
supplierVendorCode: 'SUPPLIER-005',
|
||||
mediaFiles: ['/api/placeholder/400/410'],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 111222,
|
||||
techSize: '15.6"',
|
||||
wbSize: 'Черный',
|
||||
price: 4990,
|
||||
discountedPrice: 3990,
|
||||
quantity: 35
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nmID: 777888999,
|
||||
vendorCode: 'SKU006',
|
||||
title: 'Умные часы Apple Watch Series 9',
|
||||
description: 'Новейшие умные часы с передовыми функциями здоровья и фитнеса',
|
||||
brand: 'Apple',
|
||||
object: 'Умные часы',
|
||||
parent: 'Электроника',
|
||||
countryProduction: 'Китай',
|
||||
supplierVendorCode: 'SUPPLIER-006',
|
||||
mediaFiles: ['/api/placeholder/400/411', '/api/placeholder/400/412', '/api/placeholder/400/413'],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 777888,
|
||||
techSize: '41mm',
|
||||
wbSize: '41mm GPS',
|
||||
price: 39990,
|
||||
discountedPrice: 35990,
|
||||
quantity: 12
|
||||
},
|
||||
{
|
||||
chrtID: 777889,
|
||||
techSize: '45mm',
|
||||
wbSize: '45mm GPS',
|
||||
price: 42990,
|
||||
discountedPrice: 38990,
|
||||
quantity: 8
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
// Загружаем реальные карточки WB
|
||||
const { data: wbCardsData, loading: wbCardsLoading } = useQuery(GET_MY_WILDBERRIES_SUPPLIES, {
|
||||
errorPolicy: 'all'
|
||||
});
|
||||
|
||||
// Используем реальные данные из GraphQL запроса
|
||||
const realWbCards: WildberriesCard[] = (wbCardsData?.myWildberriesSupplies || [])
|
||||
.flatMap((supply: any) => supply.cards || [])
|
||||
.map((card: any) => ({
|
||||
nmID: card.nmId || card.nmID,
|
||||
vendorCode: card.vendorCode || '',
|
||||
title: card.title || 'Без названия',
|
||||
description: card.description || '',
|
||||
brand: card.brand || '',
|
||||
object: card.object || '',
|
||||
parent: card.parent || '',
|
||||
countryProduction: card.countryProduction || '',
|
||||
supplierVendorCode: card.supplierVendorCode || '',
|
||||
mediaFiles: card.mediaFiles || [],
|
||||
sizes: card.sizes || []
|
||||
}));
|
||||
|
||||
// Загружаем контрагентов-фулфилментов
|
||||
const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
@ -327,7 +182,7 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
}
|
||||
})
|
||||
|
||||
// Моковые данные рынков
|
||||
// Данные рынков можно будет загружать через GraphQL в будущем
|
||||
const markets = [
|
||||
{ value: 'sadovod', label: 'Садовод' },
|
||||
{ value: 'luzhniki', label: 'Лужники' },
|
||||
@ -337,53 +192,13 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
|
||||
|
||||
|
||||
// Автоматически загружаем товары при открытии компонента
|
||||
// Загружаем карточки из GraphQL запроса
|
||||
useEffect(() => {
|
||||
const loadCards = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')
|
||||
|
||||
console.log('WB API Key found:', !!wbApiKey)
|
||||
console.log('WB API Key active:', wbApiKey?.isActive)
|
||||
console.log('WB API Key validationData:', wbApiKey?.validationData)
|
||||
|
||||
if (wbApiKey?.isActive) {
|
||||
// Попытка загрузить реальные данные из API Wildberries
|
||||
const validationData = wbApiKey.validationData as Record<string, string>
|
||||
|
||||
// API ключ может храниться в разных местах
|
||||
const apiToken = validationData?.token ||
|
||||
validationData?.apiKey ||
|
||||
validationData?.key ||
|
||||
(wbApiKey as { apiKey?: string }).apiKey // Прямое поле apiKey из базы
|
||||
|
||||
console.log('API Token extracted:', !!apiToken)
|
||||
console.log('API Token length:', apiToken?.length)
|
||||
|
||||
if (apiToken) {
|
||||
console.log('Загружаем карточки из WB API...')
|
||||
const cards = await WildberriesService.getAllCards(apiToken, 50)
|
||||
setWbCards(cards)
|
||||
console.log('Загружено карточек из WB API:', cards.length)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Если API ключ не настроен, оставляем пустое состояние
|
||||
console.log('API ключ WB не настроен, показываем пустое состояние')
|
||||
setWbCards([])
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки карточек WB:', error)
|
||||
// При ошибке API показываем пустое состояние
|
||||
setWbCards([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
if (!wbCardsLoading && wbCardsData) {
|
||||
setWbCards(realWbCards)
|
||||
console.log('Загружено карточек из GraphQL:', realWbCards.length)
|
||||
}
|
||||
|
||||
loadCards()
|
||||
}, [user])
|
||||
}, [wbCardsData, wbCardsLoading, realWbCards])
|
||||
|
||||
const loadAllCards = async () => {
|
||||
setLoading(true)
|
||||
@ -407,17 +222,15 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
}
|
||||
}
|
||||
|
||||
// Если API ключ не настроен, загружаем моковые данные
|
||||
console.log('API ключ WB не настроен, загружаем моковые данные')
|
||||
const allCards = getMockCards()
|
||||
setWbCards(allCards)
|
||||
console.log('Загружены моковые товары:', allCards.length)
|
||||
// Если API ключ не настроен, используем данные из GraphQL
|
||||
console.log('API ключ WB не настроен, используем данные из GraphQL')
|
||||
setWbCards(realWbCards)
|
||||
console.log('Используются данные из GraphQL:', realWbCards.length)
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки всех карточек WB:', error)
|
||||
// При ошибке загружаем моковые данные
|
||||
const allCards = getMockCards()
|
||||
setWbCards(allCards)
|
||||
console.log('Загружены моковые товары (fallback):', allCards.length)
|
||||
// При ошибке используем данные из GraphQL
|
||||
setWbCards(realWbCards)
|
||||
console.log('Используются данные из GraphQL (fallback):', realWbCards.length)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@ -450,12 +263,11 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
}
|
||||
}
|
||||
|
||||
// Если API ключ не настроен, ищем в моковых данных
|
||||
console.log('API ключ WB не настроен, поиск в моковых данных:', searchTerm)
|
||||
const mockCards = getMockCards()
|
||||
// Если API ключ не настроен, ищем в данных из GraphQL
|
||||
console.log('API ключ WB не настроен, поиск в данных GraphQL:', searchTerm)
|
||||
|
||||
// Фильтруем товары по поисковому запросу
|
||||
const filteredCards = mockCards.filter(card =>
|
||||
const filteredCards = realWbCards.filter(card =>
|
||||
card.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.nmID.toString().includes(searchTerm.toLowerCase()) ||
|
||||
@ -463,19 +275,18 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
)
|
||||
|
||||
setWbCards(filteredCards)
|
||||
console.log('Найдено моковых товаров:', filteredCards.length)
|
||||
console.log('Найдено товаров в GraphQL данных:', filteredCards.length)
|
||||
} catch (error) {
|
||||
console.error('Ошибка поиска карточек WB:', error)
|
||||
// При ошибке ищем в моковых данных
|
||||
const mockCards = getMockCards()
|
||||
const filteredCards = mockCards.filter(card =>
|
||||
// При ошибке ищем в данных из GraphQL
|
||||
const filteredCards = realWbCards.filter(card =>
|
||||
card.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.nmID.toString().includes(searchTerm.toLowerCase()) ||
|
||||
card.object?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
setWbCards(filteredCards)
|
||||
console.log('Найдено моковых товаров (fallback):', filteredCards.length)
|
||||
console.log('Найдено товаров в GraphQL данных (fallback):', filteredCards.length)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@ -1153,27 +964,13 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Состояние загрузки */}
|
||||
{loading && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<Card key={i} className="bg-white/10 backdrop-blur border-white/20 p-3 animate-pulse">
|
||||
<div className="space-y-3">
|
||||
<div className="bg-white/20 rounded-lg aspect-square w-full"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="bg-white/20 rounded h-3 w-3/4"></div>
|
||||
<div className="bg-white/20 rounded h-3 w-1/2"></div>
|
||||
<div className="bg-white/20 rounded h-4 w-2/3"></div>
|
||||
</div>
|
||||
<div className="bg-white/20 rounded h-7 w-full"></div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{/* Состояние загрузки с красивыми скелетонами */}
|
||||
{(loading || wbCardsLoading) && (
|
||||
<ProductCardSkeletonGrid count={12} />
|
||||
)}
|
||||
|
||||
{/* Карточки товаров */}
|
||||
{!loading && wbCards.length > 0 && (
|
||||
{!loading && !wbCardsLoading && wbCards.length > 0 && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
||||
{wbCards.map((card) => {
|
||||
const selectedQuantity = getSelectedQuantity(card)
|
||||
@ -1353,7 +1150,7 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
</div>
|
||||
)}
|
||||
|
||||
{wbCards.length === 0 && !loading && (
|
||||
{wbCards.length === 0 && !loading && !wbCardsLoading && (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
||||
<div className="text-center max-w-md mx-auto">
|
||||
<Package className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
||||
@ -1374,17 +1171,17 @@ export function WBProductCards({ onBack, onComplete, showSummary: externalShowSu
|
||||
) : (
|
||||
<>
|
||||
<p className="text-white/60 mb-3 text-sm">
|
||||
Для работы с реальными карточками необходимо настроить API ключ Wildberries
|
||||
Для работы с полным функционалом WB API необходимо настроить API ключ Wildberries
|
||||
</p>
|
||||
<p className="text-white/40 text-xs mb-4">
|
||||
Показаны демонстрационные товары для тестирования
|
||||
Загружены товары из вашего склада
|
||||
</p>
|
||||
<Button
|
||||
onClick={loadAllCards}
|
||||
className="bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white"
|
||||
>
|
||||
<Package className="h-4 w-4 mr-2" />
|
||||
Показать демо товары
|
||||
Загрузить товары
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
Reference in New Issue
Block a user