Унификация дизайна корзины и обновление правил

- Убран текст "(с рецептурой)" из названий товаров в корзине
- Добавлен раздел 9.2.6 в rules-complete.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-07 19:50:00 +03:00
parent eb5c520438
commit d6a26f91da
9 changed files with 831 additions and 2855 deletions

1985
dev.log
View File

@ -2,1987 +2,16 @@
> sferav@0.1.0 dev > sferav@0.1.0 dev
> next dev --turbopack > next dev --turbopack
⚠ Port 3000 is in use by process 1405
1913
2223, using available port 3001 instead.
▲ Next.js 15.4.1 (Turbopack) ▲ Next.js 15.4.1 (Turbopack)
- Local: http://localhost:3000 - Local: http://localhost:3001
- Network: http://192.168.0.104:3000 - Network: http://192.168.0.104:3001
- Environments: .env - Environments: .env
- Experiments (use with caution): - Experiments (use with caution):
· optimizePackageImports · optimizePackageImports
✓ Starting... ✓ Starting...
✓ Ready in 834ms ✓ Ready in 923ms
○ Compiling /api/graphql ... [?25h
✓ Compiled /api/graphql in 1205ms
🚀 Проверка инициализации базы данных...
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
✨ Инициализация базы данных завершена
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 3994ms
POST /api/graphql 200 in 1067ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 742ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 813ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 1032ms
POST /api/graphql 200 in 539ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 1335ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 973ms
POST /api/graphql 200 in 696ms
○ Compiling /fulfillment-supplies ...
✓ Compiled /fulfillment-supplies in 1797ms
GET /fulfillment-supplies 200 in 1883ms
GET /fulfillment-supplies 200 in 79ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetMe',
query: 'query GetMe {\n' +
' me {\n' +
' id\n' +
' phone\n' +
' avatar\n' +
' managerName\n' +
' createdAt\n' +
' organization {\n' +
' id\n' +
' inn\n' +
' kpp\n' +
' name\n' +
' fullName\n' +
' address\n' +
' addressFull\n' +
' ogrn\n' +
' ...'
}
✓ Compiled /favicon.ico in 478ms
POST /api/graphql 200 in 656ms
GET /favicon.ico?favicon.45db1c09.ico 200 in 750ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetMe',
query: 'query GetMe {\n' +
' me {\n' +
' id\n' +
' phone\n' +
' avatar\n' +
' managerName\n' +
' createdAt\n' +
' organization {\n' +
' id\n' +
' inn\n' +
' kpp\n' +
' name\n' +
' fullName\n' +
' address\n' +
' addressFull\n' +
' ogrn\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 320ms
POST /api/graphql 200 in 556ms
○ Compiling /fulfillment-warehouse ...
✓ Compiled /fulfillment-warehouse in 807ms
GET /fulfillment-warehouse 200 in 869ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetMe',
query: 'query GetMe {\n' +
' me {\n' +
' id\n' +
' phone\n' +
' avatar\n' +
' managerName\n' +
' createdAt\n' +
' organization {\n' +
' id\n' +
' inn\n' +
' kpp\n' +
' name\n' +
' fullName\n' +
' address\n' +
' addressFull\n' +
' ogrn\n' +
' ...'
}
GET /favicon.ico?favicon.45db1c09.ico 200 in 258ms
POST /api/graphql 200 in 1231ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetMe',
query: 'query GetMe {\n' +
' me {\n' +
' id\n' +
' phone\n' +
' avatar\n' +
' managerName\n' +
' createdAt\n' +
' organization {\n' +
' id\n' +
' inn\n' +
' kpp\n' +
' name\n' +
' fullName\n' +
' address\n' +
' addressFull\n' +
' ogrn\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetWarehouseProducts',
query: 'query GetWarehouseProducts {\n' +
' warehouseProducts {\n' +
' id\n' +
' name\n' +
' article\n' +
' description\n' +
' price\n' +
' quantity\n' +
' type\n' +
' category {\n' +
' id\n' +
' name\n' +
' __typename\n' +
' }\n' +
' brand\n' +
' c...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetMyCounterparties',
query: 'query GetMyCounterparties {\n' +
' myCounterparties {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' managementName\n' +
' type\n' +
' address\n' +
' market\n' +
' phones\n' +
' emails\n' +
' createdAt\n' +
' users {\n' +
' id\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
❌ GraphQL Errors: [
{
message: 'Cannot query field "price" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "quantity" on type "Supply". Did you mean "unit"?',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "category" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "status" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "date" on type "Supply". Did you mean "name"?',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "supplier" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "minStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "currentStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "usedStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
}
]
POST /api/graphql 400 in 90ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetSupplyOrders',
query: 'query GetSupplyOrders {\n' +
' supplyOrders {\n' +
' id\n' +
' organizationId\n' +
' partnerId\n' +
' deliveryDate\n' +
' status\n' +
' totalAmount\n' +
' totalItems\n' +
' fulfillmentCenterId\n' +
' createdAt\n' +
' updatedAt\n' +
' part...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
❌ GraphQL Errors: [
{
message: 'Cannot query field "price" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "quantity" on type "Supply". Did you mean "unit"?',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "category" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "status" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "date" on type "Supply". Did you mean "name"?',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "supplier" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "minStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "currentStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "usedStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "type" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "shopLocation" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "sellerOwner" on type "Supply".',
locations: [ [Object] ],
path: undefined
}
]
POST /api/graphql 400 in 130ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetFulfillmentWarehouseStats',
query: 'query GetFulfillmentWarehouseStats {\n' +
' fulfillmentWarehouseStats {\n' +
' products {\n' +
' current\n' +
' change\n' +
' percentChange\n' +
' __typename\n' +
' }\n' +
' goods {\n' +
' current\n' +
' change\n' +
' per...'
}
🔥 FULFILLMENT WAREHOUSE STATS RESOLVER CALLED
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 3124ms
🏢 Organization ID: cmdzn23nl0002y5i4tytjh0ni, Date 24h ago: 2025-08-05T20:02:43.822Z
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
🔍 Резолвер warehouseProducts (доставленные заказы): {
currentUserId: 'cmdzn1sof0001y5i4h6fyp5jw',
organizationId: 'cmdzn23nl0002y5i4tytjh0ni',
organizationType: 'FULFILLMENT',
deliveredOrdersCount: 1,
orders: [
{
id: 'cme0dt4i80009y52fe5r82wr9',
sellerName: 'ФУЛФИЛМЕНТ РУ',
supplierName: 'ПОСТАВЩИК-ЭК',
status: 'DELIVERED',
itemsCount: 1,
deliveryDate: 2025-08-07T00:00:00.000Z
}
]
}
📦 Заказ от селлера ФУЛФИЛМЕНТ РУ у поставщика ПОСТАВЩИК-ЭК: [
{
productId: 'cmdztzlbn0001y5gr4pg0j0df',
productName: 'Пакет',
article: 'SF-C-446739-386',
orderedQuantity: 100,
price: 10
}
]
🚫 Исключен расходник из основного склада фулфилмента: {
name: 'Пакет',
type: 'CONSUMABLE',
orderId: 'cme0dt4i80009y52fe5r82wr9'
}
✅ Итого товаров на складе фулфилмента (из доставленных заказов): 0
POST /api/graphql 200 in 5813ms
POST /api/graphql 200 in 6353ms
📦 ALL DELIVERED ORDERS: 1
Order cme0dt4i80009y52fe5r82wr9: org=cmdzn23nl0002y5i4tytjh0ni (ФУЛФИЛМЕНТ РУ), fulfillment=cmdzn23nl0002y5i4tytjh0ni, items=1
🛒 SELLER ORDERS TO FULFILLMENT: 0
POST /api/graphql 200 in 3597ms
POST /api/graphql 200 in 7417ms
POST /api/graphql 200 in 2433ms
POST /api/graphql 200 in 6048ms
🏭 FULFILLMENT SUPPLY ORDERS: 1
🔥 FULFILLMENT SUPPLIES DEBUG: organizationId=cmdzn23nl0002y5i4tytjh0ni, ordersCount=1, warehouseCount=2, totalStock=100
📦 FULFILLMENT SUPPLIES BREAKDOWN: [
{
name: 'скотч10',
currentStock: 0,
supplier: 'Внутренний поставщик'
},
{ name: 'Пакет', currentStock: 100, supplier: 'ПОСТАВЩИК-ЭК' }
]
POST /api/graphql 200 in 7609ms
📊 FULFILLMENT SUPPLIES RECEIVED TODAY (ПРИБЫЛО): 1 orders, 100 items
💼 SELLER SUPPLIES DEBUG: totalCount=0 (from Supply warehouse)
📊 SELLER SUPPLIES RECEIVED TODAY: 0 supplies, 0 items
🏁 FINAL WAREHOUSE STATS RESULT: {
"products": {
"current": 0,
"change": 0,
"percentChange": 0
},
"goods": {
"current": 0,
"change": 0,
"percentChange": 0
},
"defects": {
"current": 0,
"change": 0,
"percentChange": 0
},
"pvzReturns": {
"current": 0,
"change": 0,
"percentChange": 0
},
"fulfillmentSupplies": {
"current": 100,
"change": 100,
"percentChange": 100
},
"sellerSupplies": {
"current": 0,
"change": 0,
"percentChange": 0
}
}
POST /api/graphql 200 in 8453ms
○ Compiling /fulfillment-warehouse/supplies ...
✓ Compiled /fulfillment-warehouse/supplies in 1604ms
GET /fulfillment-warehouse/supplies 200 in 1691ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
❌ GraphQL Errors: [
{
message: 'Cannot query field "price" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "quantity" on type "Supply". Did you mean "unit"?',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "category" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "status" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "date" on type "Supply". Did you mean "name"?',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "supplier" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "minStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "currentStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
},
{
message: 'Cannot query field "usedStock" on type "Supply".',
locations: [ [Object] ],
path: undefined
}
]
POST /api/graphql 400 in 117ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetMe',
query: 'query GetMe {\n' +
' me {\n' +
' id\n' +
' phone\n' +
' avatar\n' +
' managerName\n' +
' createdAt\n' +
' organization {\n' +
' id\n' +
' inn\n' +
' kpp\n' +
' name\n' +
' fullName\n' +
' address\n' +
' addressFull\n' +
' ogrn\n' +
' ...'
}
GET /favicon.ico?favicon.45db1c09.ico 200 in 356ms
POST /api/graphql 200 in 561ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 1471ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 1015ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 607ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 610ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 925ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 951ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 365ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 457ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 1215ms
POST /api/graphql 200 in 610ms
POST /api/graphql 200 in 693ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 296ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 1596ms
POST /api/graphql 200 in 625ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 974ms
🚀 Проверка инициализации базы данных...
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 1304ms
✨ Инициализация базы данных завершена
POST /api/graphql 200 in 1651ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 691ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 982ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 1055ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 283ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 596ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 1552ms
POST /api/graphql 200 in 313ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 597ms
POST /api/graphql 200 in 650ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 1584ms
POST /api/graphql 200 in 701ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 702ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 729ms
POST /api/graphql 200 in 1124ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 603ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 948ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 801ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 306ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 409ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 307ms
POST /api/graphql 200 in 1266ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 833ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 658ms
POST /api/graphql 200 in 627ms
POST /api/graphql 200 in 1508ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 1040ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 1117ms
POST /api/graphql 200 in 692ms
POST /api/graphql 200 in 698ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 790ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 831ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 385ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 375ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetIncomingRequests',
query: 'query GetIncomingRequests {\n' +
' incomingRequests {\n' +
' id\n' +
' status\n' +
' message\n' +
' createdAt\n' +
' sender {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' phones\n' +
' email...'
}
POST /api/graphql 200 in 279ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 1908ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjFzb2YwMDAxeTVpNGg2ZnlwNWp3IiwicGhvbmUiOiI3OTk5OTk5OTk5OSIsImlhdCI6MTc1NDQ4NzQ4MSwiZXhwIjoxNzU3MDc5NDgxfQ.9KeIWoNPtDJNEU_SCoCba1ducS2pEpyhplg3YswCED4
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn1sof0001y5i4h6fyp5jw', phone: '79999999999' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjhkYjAwMDBoeTVpNHpveHp6ZmZnIiwicGhvbmUiOiI3ODg4ODg4ODg4OCIsImlhdCI6MTc1NDQ2NTExOCwiZXhwIjoxNzU3MDU3MTE4fQ.VP8LZUaONciSW9qBAVAjHVsY1lCpyiBVkVTcGoDaOGI
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn8db0000hy5i4zoxzzffg', phone: '78888888888' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjVwYWUwMDA5eTVpNHB5YXNpYnJqIiwicGhvbmUiOiI3NjY2NjY2NjY2NiIsImlhdCI6MTc1NDUwOTc4MywiZXhwIjoxNzU3MTAxNzgzfQ.uC19oz6DE323E34mzAW7cZxw0vUjTbzRMktghrt5qgc
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn5pae0009y5i4pyasibrj', phone: '76666666666' }
🔍 GraphQL Operation: {
operationName: 'GetPendingSuppliesCount',
query: 'query GetPendingSuppliesCount {\n' +
' pendingSuppliesCount {\n' +
' supplyOrders\n' +
' ourSupplyOrders\n' +
' sellerSupplyOrders\n' +
' incomingSupplierOrders\n' +
' incomingRequests\n' +
' total\n' +
' __typename\n' +
' }\n' +
'}...'
}
POST /api/graphql 200 in 634ms
POST /api/graphql 200 in 1902ms
POST /api/graphql 200 in 2531ms
POST /api/graphql 200 in 1789ms
GraphQL Context - Auth header: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWR6bjJvOGowMDA0eTVpNDgxMHc4bzVjIiwicGhvbmUiOiI3Nzc3Nzc3Nzc3NyIsImlhdCI6MTc1NDQ2NDg1MiwiZXhwIjoxNzU3MDU2ODUyfQ.sa2a5qIIOzJsgWJkC5qezQ6m4-JvwtxOKyEmHIiJ9zU
GraphQL Context - Token: eyJhbGciOiJIUzI1NiIs...
GraphQL Context - Decoded user: { id: 'cmdzn2o8j0004y5i4810w8o5c', phone: '77777777777' }
🔍 GraphQL Operation: {
operationName: 'GetConversations',
query: 'query GetConversations {\n' +
' conversations {\n' +
' id\n' +
' counterparty {\n' +
' id\n' +
' inn\n' +
' name\n' +
' fullName\n' +
' type\n' +
' address\n' +
' users {\n' +
' id\n' +
' avatar\n' +
' managerName\n' +
' ...'
}
POST /api/graphql 200 in 1464ms
🚀 Проверка инициализации базы данных...
POST /api/graphql 200 in 1162ms
POST /api/graphql 200 in 1638ms
✨ Инициализация базы данных завершена
POST /api/graphql 200 in 1317ms
POST /api/graphql 200 in 1189ms
POST /api/graphql 200 in 961ms
POST /api/graphql 200 in 375ms
POST /api/graphql 200 in 387ms
POST /api/graphql 200 in 321ms
POST /api/graphql 200 in 1497ms
○ Compiling /supplies/create-suppliers ...
✓ Compiled /supplies/create-suppliers in 556ms
GET /supplies/create-suppliers 200 in 793ms
POST /api/graphql 200 in 439ms
POST /api/graphql 200 in 320ms
POST /api/graphql 200 in 300ms
POST /api/graphql 200 in 350ms
POST /api/graphql 200 in 929ms
POST /api/graphql 200 in 1212ms
POST /api/graphql 200 in 1735ms
POST /api/graphql 200 in 638ms
POST /api/graphql 200 in 1867ms
POST /api/graphql 200 in 1048ms
POST /api/graphql 200 in 1179ms
POST /api/graphql 200 in 833ms
POST /api/graphql 200 in 1510ms
POST /api/graphql 200 in 657ms
POST /api/graphql 200 in 1168ms
POST /api/graphql 200 in 1187ms
POST /api/graphql 200 in 353ms
POST /api/graphql 200 in 395ms
POST /api/graphql 200 in 1481ms
POST /api/graphql 200 in 1100ms
POST /api/graphql 200 in 767ms
GET /supplies/create-suppliers 200 in 82ms
GET /favicon.ico 200 in 55ms
POST /api/graphql 200 in 560ms
POST /api/graphql 200 in 1414ms
POST /api/graphql 200 in 368ms
POST /api/graphql 200 in 372ms
POST /api/graphql 200 in 231ms
POST /api/graphql 200 in 1193ms
POST /api/graphql 200 in 397ms
POST /api/graphql 200 in 912ms
POST /api/graphql 200 in 971ms
POST /api/graphql 200 in 826ms
POST /api/graphql 200 in 616ms
○ Compiling /settings ...
✓ Compiled /settings in 689ms
GET /settings 200 in 822ms
POST /api/graphql 200 in 515ms
POST /api/graphql 200 in 301ms
POST /api/graphql 200 in 960ms
POST /api/graphql 200 in 692ms
POST /api/graphql 200 in 617ms
POST /api/graphql 200 in 777ms
POST /api/graphql 200 in 671ms
POST /api/graphql 200 in 291ms

View File

@ -515,6 +515,11 @@ model SupplyOrderItem {
quantity Int quantity Int
price Decimal @db.Decimal(12, 2) price Decimal @db.Decimal(12, 2)
totalPrice Decimal @db.Decimal(12, 2) totalPrice Decimal @db.Decimal(12, 2)
// Поля для рецептуры продукта
services String[] @default([]) // ID услуг
fulfillmentConsumables String[] @default([]) // ID расходников фулфилмента
sellerConsumables String[] @default([]) // ID расходников селлера
marketplaceCardId String? // ID карточки маркетплейса
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
supplyOrder SupplyOrder @relation(fields: [supplyOrderId], references: [id], onDelete: Cascade) supplyOrder SupplyOrder @relation(fields: [supplyOrderId], references: [id], onDelete: Cascade)

View File

@ -1773,6 +1773,129 @@ transition-all duration-150
- **ПРИЧИНА**: Заменяется контекстными кнопками в табах - **ПРИЧИНА**: Заменяется контекстными кнопками в табах
- **СОХРАНИТЬ**: Стили и логику навигации, но адаптировать под новые роуты - **СОХРАНИТЬ**: Стили и логику навигации, но адаптировать под новые роуты
#### **9.2.6 ПРАВИЛА КОРЗИНЫ - ЕДИНЫЙ СТАНДАРТ**
**КРИТИЧЕСКИ ВАЖНО**: Все корзины в системе должны следовать единому стандарту дизайна и функциональности.
##### **9.2.6.1 Размеры и позиционирование**
```tsx
<div className="w-72 flex-shrink-0">
<div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
```
**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**:
- **Ширина**: `w-72` (288px) - фиксированная ширина для всех корзин
- **Флекс**: `flex-shrink-0` - корзина не сжимается
- **Позиция**: `sticky top-0` - прилипает к верху при прокрутке
- **Стиль**: Glass morphism эффект с `backdrop-blur` и `bg-white/10`
##### **9.2.6.2 Автодобавление товаров**
**ПРАВИЛО AUTO-ADD**: При вводе количества товар автоматически добавляется в корзину.
```tsx
// ОБЯЗАТЕЛЬНАЯ РЕАЛИЗАЦИЯ:
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
if (newQuantity > 0) {
// Автоматически добавляем товар в корзину
updateProductQuantity(product.id, newQuantity)
} else {
// Удаляем товар из корзины при количестве 0
removeFromCart(product.id)
}
}
```
**ДЕФОЛТНОЕ ЗНАЧЕНИЕ**: Пустой инпут (`value={''}`) вместо `value={0}`
##### **9.2.6.3 Структура корзины**
**ОБЯЗАТЕЛЬНЫЕ ЭЛЕМЕНТЫ**:
1. **Заголовок**: "Корзина (X шт)" с иконкой корзины
2. **Список товаров**:
- Название товара (БЕЗ суффикса "(с рецептурой)")
- Цена за единицу × количество
- Кнопка удаления (X справа)
3. **Мета-информация**: Дата поставки, фулфилмент-центр, логистика
4. **Итого**: Общая сумма с выделением зелёным цветом
5. **Кнопка действия**: "Создать поставку" с градиентом
**ЗАПРЕЩЕНО**: Отображать текст "(с рецептурой)" в названиях товаров в корзине
##### **9.2.6.4 Единая функция расчета стоимости**
**КРИТИЧЕСКИ ВАЖНО**: Использовать единую функцию расчета для избежания расхождений:
```tsx
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
const product = products.find(p => p.id === productId)
if (!product) return 0
// Базовая цена товара
let total = (product.pricePerUnit || 0) * quantity
// Добавляем услуги
if (product.services && product.services.length > 0) {
const servicesTotal = product.services.reduce((sum, service) => {
return sum + ((service.pricePerUnit || 0) * quantity)
}, 0)
total += servicesTotal
}
// Добавляем FF расходники (используем .price, НЕ .pricePerUnit!)
if (product.ffConsumables && product.ffConsumables.length > 0) {
const ffConsumablesTotal = product.ffConsumables.reduce((sum, consumable) => {
return sum + ((consumable.price || 0) * quantity) // ВАЖНО: .price!
}, 0)
total += ffConsumablesTotal
}
// Добавляем расходники продавца
if (product.sellerConsumables && product.sellerConsumables.length > 0) {
const sellerConsumablesTotal = product.sellerConsumables.reduce((sum, consumable) => {
return sum + ((consumable.pricePerUnit || 0) * quantity)
}, 0)
total += sellerConsumablesTotal
}
return total
}
```
##### **9.2.6.5 Синхронизация данных между блоками**
**ПРАВИЛО СИНХРОНИЗАЦИИ**: Данные в корзине должны отражать выборы из всех блоков формы:
1. **Дата поставки**: Из Блока 3 (дата пикер)
2. **Фулфилмент-центр**: Название выбранного FF (реальные данные!)
3. **Логистическая компания**: Только партнеры типа `'LOGIST'`
**ПОРЯДОК ОТОБРАЖЕНИЯ В КОРЗИНЕ**:
```
Дата поставки: 08.08.2025
Фулфилмент-центр: ФУЛФИЛМЕНТ РУ
Логистическая компания: [Выпадающий список]
```
##### **9.2.6.6 Критические требования**
🚨 **БЕЗОПАСНОСТЬ ТИПОВ**:
- Всегда проверять на `null/undefined`: `selectedSupplier?.id || ''`
- Использовать optional chaining для всех вложенных объектов
🚨 **ПРОИЗВОДИТЕЛЬНОСТЬ**:
- Мемоизация расчетов: `useMemo` для дорогих вычислений
- Debounce для инпутов количества
🚨 **UX КОНСИСТЕНТНОСТЬ**:
- Единые стили для всех корзин в системе
- Одинаковое поведение auto-add во всех формах
- Синхронная валидация данных
### 9.3 Структура страницы "Мои поставки" - Трёхблочная архитектура ### 9.3 Структура страницы "Мои поставки" - Трёхблочная архитектура
#### **9.3.1 Обязательная структура страницы** #### **9.3.1 Обязательная структура страницы**

View File

@ -86,7 +86,7 @@ export function CreateConsumablesSupplyPage() {
const { data: productsData, loading: productsLoading } = useQuery(GET_ORGANIZATION_PRODUCTS, { const { data: productsData, loading: productsLoading } = useQuery(GET_ORGANIZATION_PRODUCTS, {
skip: !selectedSupplier, skip: !selectedSupplier,
variables: { variables: {
organizationId: selectedSupplier.id, organizationId: selectedSupplier?.id || '',
search: productSearchQuery || null, search: productSearchQuery || null,
category: null, category: null,
type: 'CONSUMABLE', // Фильтруем только расходники согласно rules2.md type: 'CONSUMABLE', // Фильтруем только расходники согласно rules2.md

View File

@ -16,6 +16,7 @@ import {
AlertCircle, AlertCircle,
Settings, Settings,
DollarSign, DollarSign,
X,
} from 'lucide-react' } from 'lucide-react'
import Image from 'next/image' import Image from 'next/image'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
@ -25,18 +26,16 @@ import { toast } from 'sonner'
import { OrganizationAvatar } from '@/components/market/organization-avatar' import { OrganizationAvatar } from '@/components/market/organization-avatar'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { DatePicker } from '@/components/ui/date-picker'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
// ВРЕМЕННО ОТКЛЮЧЕНО: импорты для верхней панели - до исправления Apollo ошибки import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
// import { DatePicker } from '@/components/ui/date-picker'
// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { CREATE_SUPPLY_ORDER } from '@/graphql/mutations' import { CREATE_SUPPLY_ORDER } from '@/graphql/mutations'
import { import {
GET_MY_COUNTERPARTIES, GET_MY_COUNTERPARTIES,
GET_ORGANIZATION_PRODUCTS, GET_ORGANIZATION_PRODUCTS,
GET_MY_SERVICES, GET_COUNTERPARTY_SERVICES,
GET_MY_SUPPLIES, GET_COUNTERPARTY_SUPPLIES,
GET_SELLER_SUPPLIES_ON_WAREHOUSE, GET_AVAILABLE_SUPPLIES_FOR_RECIPE,
GET_MY_WILDBERRIES_SUPPLIES,
} from '@/graphql/queries' } from '@/graphql/queries'
import { useAuth } from '@/hooks/useAuth' import { useAuth } from '@/hooks/useAuth'
import { useSidebar } from '@/hooks/useSidebar' import { useSidebar } from '@/hooks/useSidebar'
@ -119,16 +118,16 @@ interface FulfillmentConsumable {
id: string id: string
name: string name: string
price: number price: number
stock: number quantity: number
unit?: string unit?: string
} }
interface SellerConsumable { interface SellerConsumable {
id: string id: string
name: string name: string
stock: number pricePerUnit: number
warehouseStock: number
unit?: string unit?: string
supplierId: string
} }
interface WBCard { interface WBCard {
@ -149,7 +148,7 @@ interface ProductRecipe {
export function CreateSuppliersSupplyPage() { export function CreateSuppliersSupplyPage() {
const router = useRouter() const router = useRouter()
const { user } = useAuth() const { user: _user } = useAuth()
const { getSidebarMargin } = useSidebar() const { getSidebarMargin } = useSidebar()
// Основные состояния // Основные состояния
@ -182,7 +181,6 @@ export function CreateSuppliersSupplyPage() {
(GoodsProduct & { selectedQuantity: number; supplierId: string; supplierName: string })[] (GoodsProduct & { selectedQuantity: number; supplierId: string; supplierName: string })[]
>([]) >([])
// Загружаем партнеров-поставщиков согласно rules2.md 13.3 // Загружаем партнеров-поставщиков согласно rules2.md 13.3
const { const {
data: counterpartiesData, data: counterpartiesData,
@ -248,34 +246,37 @@ export function CreateSuppliersSupplyPage() {
const [createSupplyOrder] = useMutation(CREATE_SUPPLY_ORDER) const [createSupplyOrder] = useMutation(CREATE_SUPPLY_ORDER)
// Запросы для компонентов рецептуры // Запросы для компонентов рецептуры
const { data: fulfillmentServicesData } = useQuery(GET_MY_SERVICES, { const { data: fulfillmentServicesData } = useQuery(GET_COUNTERPARTY_SERVICES, {
variables: { organizationId: selectedFulfillment || '' },
skip: !selectedFulfillment, skip: !selectedFulfillment,
errorPolicy: 'all', errorPolicy: 'all',
}) })
const { data: fulfillmentConsumablesData } = useQuery(GET_MY_SUPPLIES, { const { data: fulfillmentConsumablesData } = useQuery(GET_COUNTERPARTY_SUPPLIES, {
variables: { organizationId: selectedFulfillment || '' },
skip: !selectedFulfillment, skip: !selectedFulfillment,
errorPolicy: 'all', errorPolicy: 'all',
}) })
const { data: sellerConsumablesData } = useQuery(GET_SELLER_SUPPLIES_ON_WAREHOUSE, { const { data: sellerConsumablesData } = useQuery(GET_AVAILABLE_SUPPLIES_FOR_RECIPE, {
skip: !user?.organization?.id, skip: !selectedFulfillment,
errorPolicy: 'all', errorPolicy: 'all',
}) })
const { data: wbCardsData } = useQuery(GET_MY_WILDBERRIES_SUPPLIES, { // TODO: Нужен запрос для получения карточек товаров селлера
skip: !user?.organization?.id, // const { data: wbCardsData } = useQuery(GET_MY_WILDBERRIES_SUPPLIES, {
errorPolicy: 'all', // skip: !user?.organization?.id,
}) // errorPolicy: 'all',
// })
// Фильтруем только партнеров-поставщиков согласно rules2.md 13.3 // Фильтруем только партнеров-поставщиков согласно rules2.md 13.3
const allCounterparties = counterpartiesData?.myCounterparties || [] const allCounterparties = counterpartiesData?.myCounterparties || []
// Извлекаем данные для компонентов рецептуры // Извлекаем данные для компонентов рецептуры
const fulfillmentServices: FulfillmentService[] = fulfillmentServicesData?.myServices || [] const fulfillmentServices: FulfillmentService[] = fulfillmentServicesData?.counterpartyServices || []
const fulfillmentConsumables: FulfillmentConsumable[] = fulfillmentConsumablesData?.mySupplies || [] const fulfillmentConsumables: FulfillmentConsumable[] = fulfillmentConsumablesData?.counterpartySupplies || []
const sellerConsumables: SellerConsumable[] = sellerConsumablesData?.sellerSuppliesOnWarehouse || [] const sellerConsumables: SellerConsumable[] = sellerConsumablesData?.getAvailableSuppliesForRecipe || []
const wbCards: WBCard[] = (wbCardsData?.myWildberriesSupplies || []).flatMap((supply: any) => supply.cards || []) const _wbCards: WBCard[] = [] // Временно отключено
// Показываем только партнеров с типом WHOLESALE согласно rules2.md 13.3 // Показываем только партнеров с типом WHOLESALE согласно rules2.md 13.3
const wholesaleSuppliers = allCounterparties.filter((cp: any) => { const wholesaleSuppliers = allCounterparties.filter((cp: any) => {
@ -343,7 +344,7 @@ export function CreateSuppliersSupplyPage() {
// Функции для работы с рынками согласно rules-complete.md v10.0 // Функции для работы с рынками согласно rules-complete.md v10.0
const getMarketLabel = (market?: string) => { const getMarketLabel = (market?: string) => {
const marketLabels = { const marketLabels = {
'sadovod': 'Садовод', sadovod: 'Садовод',
'tyak-moscow': 'ТЯК Москва', 'tyak-moscow': 'ТЯК Москва',
'opt-market': 'ОПТ Маркет', 'opt-market': 'ОПТ Маркет',
} }
@ -352,18 +353,15 @@ export function CreateSuppliersSupplyPage() {
const getMarketBadgeStyle = (market?: string) => { const getMarketBadgeStyle = (market?: string) => {
const styles = { const styles = {
'sadovod': 'bg-green-500/20 text-green-300 border-green-500/30', sadovod: 'bg-green-500/20 text-green-300 border-green-500/30',
'tyak-moscow': 'bg-blue-500/20 text-blue-300 border-blue-500/30', 'tyak-moscow': 'bg-blue-500/20 text-blue-300 border-blue-500/30',
'opt-market': 'bg-purple-500/20 text-purple-300 border-purple-500/30', 'opt-market': 'bg-purple-500/20 text-purple-300 border-purple-500/30',
} }
return styles[market as keyof typeof styles] || 'bg-gray-500/20 text-gray-300 border-gray-500/30' return styles[market as keyof typeof styles] || 'bg-gray-500/20 text-gray-300 border-gray-500/30'
} }
const logisticsCompanies: LogisticsCompany[] = [ // Получаем логистические компании из партнеров
{ id: 'express', name: 'Экспресс доставка', estimatedCost: 2500, deliveryDays: 1, type: 'EXPRESS' }, const logisticsCompanies = allCounterparties?.filter((partner) => partner.type === 'LOGIST') || []
{ id: 'standard', name: 'Стандартная доставка', estimatedCost: 1200, deliveryDays: 3, type: 'STANDARD' },
{ id: 'economy', name: 'Экономичная доставка', estimatedCost: 800, deliveryDays: 7, type: 'ECONOMY' },
]
// Моковые фулфилмент-центры согласно rules2.md 9.7.2 // Моковые фулфилмент-центры согласно rules2.md 9.7.2
const fulfillmentCenters = [ const fulfillmentCenters = [
@ -432,6 +430,8 @@ export function CreateSuppliersSupplyPage() {
} else { } else {
// Добавляем новый товар // Добавляем новый товар
setSelectedGoods((prev) => [...prev, newGoodsItem]) setSelectedGoods((prev) => [...prev, newGoodsItem])
// Инициализируем рецептуру для нового товара
initializeProductRecipe(product.id)
toast.success(`Товар "${product.name}" добавлен в корзину`) toast.success(`Товар "${product.name}" добавлен в корзину`)
} }
@ -508,7 +508,7 @@ export function CreateSuppliersSupplyPage() {
}) })
} }
const setWBCard = (productId: string, cardId: string) => { const _setWBCard = (productId: string, cardId: string) => {
initializeProductRecipe(productId) initializeProductRecipe(productId)
setProductRecipes((prev) => ({ setProductRecipes((prev) => ({
...prev, ...prev,
@ -606,24 +606,70 @@ export function CreateSuppliersSupplyPage() {
// Удаление из корзины // Удаление из корзины
const removeFromCart = (productId: string) => { const removeFromCart = (productId: string) => {
setSelectedGoods((prev) => prev.filter((item) => item.id !== productId)) setSelectedGoods((prev) => prev.filter((item) => item.id !== productId))
// Удаляем рецептуру товара
setProductRecipes((prev) => {
const updated = { ...prev }
delete updated[productId]
return updated
})
toast.success('Товар удален из корзины') toast.success('Товар удален из корзины')
} }
// Расчеты согласно rules2.md 9.7.6 // Функция расчета полной стоимости товара с рецептурой
const totalGoodsAmount = selectedGoods.reduce((sum, item) => sum + item.price * item.selectedQuantity, 0) const getProductTotalWithRecipe = (productId: string, quantity: number) => {
const product = allSelectedProducts.find(p => p.id === productId)
if (!product) return 0
const baseTotal = product.price * quantity
const recipe = productRecipes[productId]
if (!recipe) return baseTotal
// Услуги ФФ
const servicesCost = (recipe.selectedServices || []).reduce((sum, serviceId) => {
const service = fulfillmentServices.find(s => s.id === serviceId)
return sum + (service ? service.price * quantity : 0)
}, 0)
// Расходники ФФ
const ffConsumablesCost = (recipe.selectedFFConsumables || []).reduce((sum, consumableId) => {
const consumable = fulfillmentConsumables.find(c => c.id === consumableId)
// Используем такую же логику как в карточке - только price
return sum + (consumable ? consumable.price * quantity : 0)
}, 0)
// Расходники селлера
const sellerConsumablesCost = (recipe.selectedSellerConsumables || []).reduce((sum, consumableId) => {
const consumable = sellerConsumables.find(c => c.id === consumableId)
return sum + (consumable ? (consumable.pricePerUnit || 0) * quantity : 0)
}, 0)
return baseTotal + servicesCost + ffConsumablesCost + sellerConsumablesCost
}
// Расчеты для корзины - используем функцию расчета
const totalGoodsAmount = selectedGoods.reduce((sum, item) => {
return sum + getProductTotalWithRecipe(item.id, item.selectedQuantity)
}, 0)
const totalQuantity = selectedGoods.reduce((sum, item) => sum + item.selectedQuantity, 0) const totalQuantity = selectedGoods.reduce((sum, item) => sum + item.selectedQuantity, 0)
const fulfillmentFee = totalGoodsAmount * 0.08 // 8% комиссия фулфилмента const totalAmount = totalGoodsAmount
const selectedLogisticsCompany = logisticsCompanies.find((lc) => lc.id === selectedLogistics)
const logisticsCost = selectedLogistics === 'auto' ? 0 : selectedLogisticsCompany?.estimatedCost || 0
const totalAmount = totalGoodsAmount + fulfillmentFee + logisticsCost
// Валидация формы согласно rules2.md 9.7.6 // Валидация формы согласно rules2.md 9.7.6
const isFormValid = selectedSupplier && selectedGoods.length > 0 && deliveryDate && selectedFulfillment // Проверяем обязательность услуг фулфилмента согласно rules-complete.md
const hasRequiredServices = selectedGoods.every((item) => productRecipes[item.id]?.selectedServices?.length > 0)
const isFormValid =
selectedSupplier && selectedGoods.length > 0 && deliveryDate && selectedFulfillment && hasRequiredServices // Обязательно: каждый товар должен иметь услуги
// Создание поставки // Создание поставки
const handleCreateSupply = async () => { const handleCreateSupply = async () => {
if (!isFormValid) { if (!isFormValid) {
toast.error('Заполните все обязательные поля') if (!hasRequiredServices) {
toast.error('Каждый товар должен иметь минимум 1 услугу фулфилмента')
} else {
toast.error('Заполните все обязательные поля')
}
return return
} }
@ -636,11 +682,14 @@ export function CreateSuppliersSupplyPage() {
items: selectedGoods.map((item) => ({ items: selectedGoods.map((item) => ({
productId: item.id, productId: item.id,
quantity: item.selectedQuantity, quantity: item.selectedQuantity,
price: item.price, recipe: productRecipes[item.id]
completeness: item.completeness, ? {
recipe: item.recipe, services: productRecipes[item.id].selectedServices,
specialRequirements: item.specialRequirements, fulfillmentConsumables: productRecipes[item.id].selectedFFConsumables,
parameters: item.parameters, sellerConsumables: productRecipes[item.id].selectedSellerConsumables,
marketplaceCardId: productRecipes[item.id].selectedWBCard,
}
: undefined,
})), })),
deliveryDate, deliveryDate,
logisticsCompany: selectedLogistics === 'auto' ? null : selectedLogistics, logisticsCompany: selectedLogistics === 'auto' ? null : selectedLogistics,
@ -675,10 +724,10 @@ export function CreateSuppliersSupplyPage() {
<div className="flex-1 flex gap-4 min-h-0"> <div className="flex-1 flex gap-4 min-h-0">
{/* ЛЕВЫЙ БЛОК: ПОСТАВЩИКИ, КАРТОЧКИ ТОВАРОВ И ДЕТАЛЬНЫЙ КАТАЛОГ */} {/* ЛЕВЫЙ БЛОК: ПОСТАВЩИКИ, КАРТОЧКИ ТОВАРОВ И ДЕТАЛЬНЫЙ КАТАЛОГ */}
<div className="flex-1 flex flex-col gap-4 min-h-0"> <div className="flex-1 flex flex-col gap-4 min-h-0">
{/* БЛОК 1: ПОСТАВЩИКИ - обязательный блок согласно rules1.md 19.2.1 */} {/* БЛОК 1: ПОСТАВЩИКИ - обязательный блок согласно rules-complete.md 9.2.1 */}
<div <div
className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0 flex flex-col" className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0 flex flex-col"
style={{ minHeight: '120px', maxHeight: suppliers.length > 4 ? '200px' : 'auto' }} style={{ height: '180px' }}
> >
<div className="p-4 flex-shrink-0"> <div className="p-4 flex-shrink-0">
{/* Навигация и заголовок в одном блоке */} {/* Навигация и заголовок в одном блоке */}
@ -699,19 +748,19 @@ export function CreateSuppliersSupplyPage() {
</div> </div>
<div className="w-64"> <div className="w-64">
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40 h-4 w-4" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/90 h-4 w-4 drop-shadow-sm z-10" />
<Input <Input
placeholder="Поиск поставщиков..." placeholder="Поиск поставщиков..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="bg-white/5 border-white/10 text-white placeholder:text-white/50 pl-10 h-9 text-sm transition-all duration-200 focus:border-white/20" className="bg-white/10 border-white/20 text-white placeholder:text-white/60 pl-10 h-9 text-sm rounded-full transition-all duration-200 focus:border-white/30 backdrop-blur-sm"
/> />
</div> </div>
</div> </div>
</div> </div>
{/* Кнопка поиска в маркете */} {/* Кнопка поиска в маркете */}
{allCounterparties.length === 0 && ( {!isLoading && allCounterparties.length === 0 && (
<div className="mt-4"> <div className="mt-4">
<Button <Button
variant="outline" variant="outline"
@ -725,15 +774,15 @@ export function CreateSuppliersSupplyPage() {
</div> </div>
)} )}
{/* Список поставщиков согласно visual-design-rules.md */} {/* Контейнер скролла поставщиков согласно rules-complete.md 9.2.1 */}
<div className="flex-1 min-h-0"> <div className="flex-1 overflow-hidden">
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center h-44">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white/60"></div> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white/60"></div>
<span className="ml-3 text-white/70">Загрузка поставщиков...</span> <span className="ml-3 text-white/70">Загрузка поставщиков...</span>
</div> </div>
) : suppliers.length === 0 ? ( ) : suppliers.length === 0 ? (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-44">
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<div className="w-12 h-12 mx-auto bg-white/5 rounded-full flex items-center justify-center"> <div className="w-12 h-12 mx-auto bg-white/5 rounded-full flex items-center justify-center">
<Building2 className="h-6 w-6 text-white/40" /> <Building2 className="h-6 w-6 text-white/40" />
@ -753,45 +802,53 @@ export function CreateSuppliersSupplyPage() {
</div> </div>
</div> </div>
) : ( ) : (
<div <div className="h-44 overflow-hidden">
className={`gap-2 overflow-y-auto ${ <div
suppliers.length <= 2 className={`h-full ${
? 'flex flex-wrap' suppliers.length <= 4
: suppliers.length <= 4 ? 'flex items-start gap-3 px-4'
? 'grid grid-cols-2' : 'flex gap-3 overflow-x-auto px-4 pb-2 scrollbar-hide'
: 'grid grid-cols-1 md:grid-cols-2 max-h-32' }`}
}`} style={{
> scrollbarWidth: 'none',
{suppliers.map((supplier: GoodsSupplier) => ( msOverflowStyle: 'none',
<div }}
key={supplier.id} >
onClick={() => setSelectedSupplier(supplier)} {suppliers.map((supplier: GoodsSupplier) => (
className={`p-3 rounded-lg cursor-pointer group transition-all duration-200 ${ <div
selectedSupplier?.id === supplier.id key={supplier.id}
? 'bg-white/15 border border-white/40 shadow-lg' onClick={() => setSelectedSupplier(supplier)}
: 'bg-white/5 border border-white/10 hover:border-white/20 hover:bg-white/10' className={`flex-shrink-0 p-3 rounded-lg cursor-pointer group transition-all duration-200
}`} w-[184px] md:w-[200px] lg:w-[216px] h-[92px]
> ${
<div className="flex items-start gap-2"> selectedSupplier?.id === supplier.id
<div className="flex-shrink-0"> ? 'bg-green-500/20 border border-green-400/60 shadow-lg ring-1 ring-green-400/30'
<OrganizationAvatar organization={supplier} size="sm" /> : 'bg-white/5 border border-white/10 hover:border-white/20 hover:bg-white/10 hover:shadow-md'
</div> }`}
<div className="flex-1 min-w-0"> >
<h4 className="text-white font-medium text-sm truncate group-hover:text-white transition-colors"> <div className="flex items-start gap-2 h-full">
{supplier.name || supplier.fullName} <div className="flex-shrink-0">
</h4> <OrganizationAvatar organization={supplier} size="sm" />
<div className="flex items-center gap-2 mt-1"> </div>
<p className="text-white/60 text-xs font-mono">ИНН: {supplier.inn}</p> <div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-sm truncate group-hover:text-white transition-colors">
{supplier.name || supplier.fullName}
</h4>
<p className="text-white/60 text-xs font-mono mt-1">ИНН: {supplier.inn}</p>
{supplier.market && ( {supplier.market && (
<Badge className={`text-xs font-medium border ${getMarketBadgeStyle(supplier.market)}`}> <div className="mt-1">
{getMarketLabel(supplier.market)} <Badge
</Badge> className={`text-xs font-medium border ${getMarketBadgeStyle(supplier.market)}`}
>
{getMarketLabel(supplier.market)}
</Badge>
</div>
)} )}
</div> </div>
</div> </div>
</div> </div>
</div> ))}
))} </div>
</div> </div>
)} )}
</div> </div>
@ -801,48 +858,52 @@ export function CreateSuppliersSupplyPage() {
{/* БЛОК 2: КАРТОЧКИ ТОВАРОВ */} {/* БЛОК 2: КАРТОЧКИ ТОВАРОВ */}
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0"> <div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0">
<div className="flex gap-3 overflow-x-auto p-4" style={{ scrollbarWidth: 'thin' }}> <div className="flex gap-3 overflow-x-auto p-4" style={{ scrollbarWidth: 'thin' }}>
{selectedSupplier && products.length > 0 && products.map((product: GoodsProduct) => { {selectedSupplier &&
return ( products.length > 0 &&
<div products.map((product: GoodsProduct) => {
key={product.id} return (
className="relative flex-shrink-0 bg-white/5 rounded-lg overflow-hidden border cursor-pointer transition-all duration-300 group w-20 h-28 border-white/10 hover:border-white/30" <div
onClick={() => { key={product.id}
// Добавляем товар в детальный каталог (блок 3) className="relative flex-shrink-0 bg-white/5 rounded-lg overflow-hidden border cursor-pointer transition-all duration-300 group w-20 h-28 border-white/10 hover:border-white/30"
if (!allSelectedProducts.find((p) => p.id === product.id)) { onClick={() => {
setAllSelectedProducts((prev) => [ // Добавляем товар в детальный каталог (блок 3)
...prev, if (!allSelectedProducts.find((p) => p.id === product.id)) {
{ setAllSelectedProducts((prev) => [
...product, ...prev,
selectedQuantity: 1, {
supplierId: selectedSupplier.id, ...product,
supplierName: selectedSupplier.name || selectedSupplier.fullName || 'Поставщик', selectedQuantity: 0,
}, supplierId: selectedSupplier.id,
]) supplierName: selectedSupplier.name || selectedSupplier.fullName || 'Поставщик',
} },
}} ])
> // Инициализируем рецептуру для нового товара
{product.mainImage ? ( initializeProductRecipe(product.id)
<Image }
src={product.mainImage} }}
alt={product.name} >
width={80} {product.mainImage ? (
height={112} <Image
className="w-full h-full object-cover" src={product.mainImage}
/> alt={product.name}
) : ( width={80}
<div className="w-full h-full flex items-center justify-center"> height={112}
<Package className="h-6 w-6 text-white/40" /> className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Package className="h-6 w-6 text-white/40" />
</div>
)}
</div> </div>
)} )
</div> })}
)
})}
</div> </div>
</div> </div>
{/* БЛОК 3: ТОВАРЫ ПОСТАВЩИКА - детальный каталог согласно rules-complete.md 9.2.3 */} {/* БЛОК 3: КАТАЛОГ ТОВАРОВ согласно rules-complete.md 9.2.3 */}
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-1 min-h-0 flex flex-col"> <div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-1 min-h-0 flex flex-col">
{/* ВРЕМЕННО ОТКЛЮЧЕНО: Верхняя панель согласно правилам 9.2.3.1 - до исправления Apollo ошибки {/* Верхняя панель каталога товаров согласно правилам 9.2.3.1 */}
{!counterpartiesLoading && ( {!counterpartiesLoading && (
<div className="flex items-center gap-4 p-4 bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl mb-4"> <div className="flex items-center gap-4 p-4 bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl mb-4">
<DatePicker <DatePicker
@ -891,27 +952,9 @@ export function CreateSuppliersSupplyPage() {
{counterpartiesError && ( {counterpartiesError && (
<div className="flex items-center justify-center p-4 bg-red-500/10 backdrop-blur-xl border border-red-500/20 rounded-2xl mb-4"> <div className="flex items-center justify-center p-4 bg-red-500/10 backdrop-blur-xl border border-red-500/20 rounded-2xl mb-4">
<div className="text-red-300 text-sm"> <div className="text-red-300 text-sm">Ошибка загрузки партнеров: {counterpartiesError.message}</div>
Ошибка загрузки партнеров: {counterpartiesError.message}
</div>
</div> </div>
)} )}
*/}
{/* Заголовок каталога */}
<div className="px-6 py-4 border-b border-white/10 flex-shrink-0">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-400/10 rounded-lg border border-blue-400/20">
<Package className="h-6 w-6 text-blue-400" />
</div>
<div>
<h3 className="text-xl font-semibold text-white">
Детальный каталог ({allSelectedProducts.length} товаров)
</h3>
<p className="text-white/60 text-sm mt-1">Товары для детального управления поставкой</p>
</div>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6"> <div className="flex-1 overflow-y-auto p-6">
{allSelectedProducts.length === 0 ? ( {allSelectedProducts.length === 0 ? (
@ -921,361 +964,361 @@ export function CreateSuppliersSupplyPage() {
<Package className="h-12 w-12 text-blue-400/50" /> <Package className="h-12 w-12 text-blue-400/50" />
</div> </div>
<div> <div>
<h4 className="text-xl font-medium text-white mb-2">Детальный каталог пуст</h4> <h4 className="text-xl font-medium text-white mb-2">Каталог товаров пуст</h4>
<p className="text-white/60 max-w-sm mx-auto"> <p className="text-white/60 max-w-sm mx-auto">Выберите поставщика для просмотра товаров</p>
Добавьте товары
</p>
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{allSelectedProducts.map((product) => ( {allSelectedProducts.map((product) => {
<div // Расчет стоимостей для каждого блока рецептуры
key={product.id} const recipe = productRecipes[product.id]
className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-4 hover:border-white/30 transition-all duration-200" const selectedServicesIds = recipe?.selectedServices || []
> const selectedFFConsumablesIds = recipe?.selectedFFConsumables || []
{/* ОСНОВНОЙ БЛОК: Информация о товаре + количество + сумма */} const selectedSellerConsumablesIds = recipe?.selectedSellerConsumables || []
<div className="flex items-start gap-6 mb-4">
{/* ЛЕВЫЙ БЛОК: Изображение + основная информация */} // Стоимость услуг ФФ
<div className="flex items-start gap-4 flex-1"> const servicesCost = selectedServicesIds.reduce((sum, serviceId) => {
<div className="w-24 h-24 bg-white/5 rounded-lg overflow-hidden flex-shrink-0"> const service = fulfillmentServices.find((s) => s.id === serviceId)
{product.mainImage ? ( return sum + (service ? service.price * product.selectedQuantity : 0)
<Image }, 0)
src={product.mainImage}
alt={product.name} // Стоимость расходников ФФ
width={96} const ffConsumablesCost = selectedFFConsumablesIds.reduce((sum, consumableId) => {
height={96} const consumable = fulfillmentConsumables.find((c) => c.id === consumableId)
className="w-full h-full object-cover" return sum + (consumable ? consumable.price * product.selectedQuantity : 0)
/> }, 0)
) : (
<div className="w-full h-full flex items-center justify-center"> // Стоимость расходников селлера
<Package className="h-8 w-8 text-white/40" /> const sellerConsumablesCost = selectedSellerConsumablesIds.reduce((sum, consumableId) => {
</div> const consumable = sellerConsumables.find((c) => c.id === consumableId)
)} return sum + (consumable ? (consumable.pricePerUnit || 0) * product.selectedQuantity : 0)
</div> }, 0)
<div className="flex-1">
<div className="flex items-start justify-between mb-1"> // Общая стоимость товара с рецептурой
<h4 className="text-white font-semibold text-lg">{product.name}</h4> const totalWithRecipe =
<Button product.price * product.selectedQuantity + servicesCost + ffConsumablesCost + sellerConsumablesCost
variant="ghost"
size="sm" // Debug: сравниваем с функцией расчета корзины
onClick={() => { const cartTotal = getProductTotalWithRecipe(product.id, product.selectedQuantity)
setAllSelectedProducts((prev) => prev.filter((p) => p.id !== product.id)) if (Math.abs(totalWithRecipe - cartTotal) > 0.01) {
}} console.log(`РАЗНИЦА для ${product.name}:`, {
className="text-red-400 hover:text-red-300 hover:bg-red-500/20 p-1 h-auto" карточка: totalWithRecipe,
> корзина: cartTotal,
<Minus className="h-4 w-4" /> базовая_цена: product.price * product.selectedQuantity,
</Button> услуги: servicesCost,
</div> расходники_ФФ: ffConsumablesCost,
<p className="text-white/60 text-sm mb-2 font-mono">Артикул: {product.article}</p> расходники_селлера: sellerConsumablesCost,
<p className="text-white/50 text-xs mb-2">От: {product.supplierName}</p> })
{product.category && ( }
<Badge className="bg-blue-500/20 text-blue-300 border border-blue-500/30 text-xs font-medium mb-2">
{product.category.name} return (
</Badge> <div
)} key={product.id}
<div className="flex items-center gap-4"> className="glass-card border-white/10 hover:border-white/20 transition-all duration-300 group relative"
<span className="text-white font-bold text-xl"> style={{ height: '140px' }}
{product.price.toLocaleString('ru-RU')} >
</span> {/* Элегантный крестик удаления - согласно visual-design-rules.md */}
{product.quantity !== undefined && ( <button
<div className="flex items-center gap-2"> onClick={() => {
<div setAllSelectedProducts((prev) => prev.filter((p) => p.id !== product.id))
className={`w-2 h-2 rounded-full ${ // Очищаем рецептуру
product.quantity > 0 ? 'bg-green-400' : 'bg-red-400' setProductRecipes((prev) => {
}`} const updated = { ...prev }
></div> delete updated[product.id]
<span return updated
className={`text-sm font-medium ${ })
product.quantity > 0 ? 'text-green-400' : 'text-red-400' }}
}`} className="absolute top-3 right-3 z-10 w-7 h-7 flex items-center justify-center rounded-full bg-white/5 text-white/40 hover:bg-red-500/20 hover:text-red-400 transition-all duration-200 opacity-0 group-hover:opacity-100"
> >
{product.quantity > 0 ? `Доступно: ${product.quantity}` : 'Нет в наличии'} <Plus className="h-4 w-4 rotate-45" />
</span> </button>
{/* 7 модулей согласно rules-complete.md 9.2.3.2 + visual-design-rules.md */}
<div className="flex h-full">
{/* 1. ИЗОБРАЖЕНИЕ (80px фиксированная ширина) */}
<div className="w-20 flex-shrink-0 p-3">
<div className="w-full h-full bg-white/5 rounded-lg overflow-hidden">
{product.mainImage ? (
<Image
src={product.mainImage}
alt={product.name}
width={80}
height={112}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Package className="h-5 w-5 text-white/40" />
</div> </div>
)} )}
</div> </div>
</div> </div>
</div>
{/* ПРАВЫЙ БЛОК: Количество + общая сумма */} {/* 2. ОБЩАЯ ИНФОРМАЦИЯ (flex-1) - Правильная типографика согласно 2.2 */}
<div className="flex items-center gap-4 flex-shrink-0"> <div className="flex-1 p-3 flex flex-col justify-center">
<div className="flex items-center gap-2"> <div className="space-y-2">
<Button <h4 className="text-white font-semibold text-sm truncate">{product.name}</h4>
size="sm" <div className="text-white font-bold text-lg">
variant="outline" {product.price.toLocaleString('ru-RU')}
onClick={() => { </div>
if (product.selectedQuantity > 1) { {product.category && (
setAllSelectedProducts((prev) => <Badge className="bg-blue-500/20 text-blue-300 border-0 text-xs font-medium px-2 py-1">
prev.map((p) => {product.category.name}
p.id === product.id ? { ...p, selectedQuantity: p.selectedQuantity - 1 } : p, </Badge>
), )}
<p className="text-white/60 text-xs truncate">От: {product.supplierName}</p>
<p className="font-mono text-xs text-white/60 truncate">Артикул: {product.article}</p>
</div>
</div>
{/* 3. КОЛИЧЕСТВО/СУММА/ОСТАТОК (flex-1) */}
<div className="flex-1 p-3 flex flex-col justify-center">
<div className="space-y-3">
{product.quantity !== undefined && (
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${product.quantity > 0 ? 'bg-green-400' : 'bg-red-400'}`}
></div>
<span className={`text-xs ${product.quantity > 0 ? 'text-green-400' : 'text-red-400'}`}>
{product.quantity > 0 ? `${product.quantity} шт` : 'Нет в наличии'}
</span>
</div>
)}
<div className="flex items-center gap-2">
<Input
type="number"
min="0"
max={product.quantity}
value={product.selectedQuantity || ''}
onChange={(e) => {
const inputValue = e.target.value
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
setAllSelectedProducts((prev) =>
prev.map((p) =>
p.id === product.id ? { ...p, selectedQuantity: newQuantity } : p,
),
)
// Автоматическое добавление/удаление из корзины
if (newQuantity > 0) {
// Добавляем в корзину
const existingItem = selectedGoods.find(item => item.id === product.id)
if (!existingItem) {
// Добавляем новый товар
setSelectedGoods(prev => [...prev, {
id: product.id,
name: product.name,
sku: product.article,
price: product.price,
category: product.category?.name || '',
selectedQuantity: newQuantity,
unit: product.unit || 'шт',
supplierId: selectedSupplier?.id || '',
supplierName: selectedSupplier?.name || selectedSupplier?.fullName || 'Поставщик',
}])
// Инициализируем рецептуру
initializeProductRecipe(product.id)
} else {
// Обновляем количество
setSelectedGoods(prev =>
prev.map(item =>
item.id === product.id
? { ...item, selectedQuantity: newQuantity }
: item,
),
)
}
} else {
// Удаляем из корзины при количестве 0
setSelectedGoods(prev => prev.filter(item => item.id !== product.id))
}
}}
className="glass-input w-16 h-8 text-sm text-center text-white placeholder:text-white/50"
placeholder="0"
/>
<span className="text-white/60 text-sm">шт</span>
</div>
<div className="text-green-400 font-semibold text-sm">
{(product.price * product.selectedQuantity).toLocaleString('ru-RU')}
</div>
</div>
</div>
{/* 4. УСЛУГИ ФФ (flex-1) - Правильные цвета согласно 1.2 */}
<div className="flex-1 p-3 flex flex-col">
<div className="text-center mb-2">
{servicesCost > 0 && (
<div className="text-purple-400 font-semibold text-sm mb-1">
{servicesCost.toLocaleString('ru-RU')}
</div>
)}
<h6 className="text-purple-400 text-xs font-medium uppercase tracking-wider">🛠 Услуги ФФ</h6>
</div>
<div className="flex-1 overflow-y-auto space-y-1" style={{ maxHeight: '75px' }}>
{fulfillmentServices.length > 0 ? (
fulfillmentServices.map((service) => {
const isSelected = selectedServicesIds.includes(service.id)
return (
<label
key={service.id}
className={`block w-full px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 hover:scale-105 ${
isSelected
? 'bg-purple-500/20 border-purple-500/30 text-purple-300 border'
: 'bg-white/5 text-white/70 hover:bg-white/10 hover:text-white'
}`}
>
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleService(product.id, service.id)}
className="sr-only"
/>
<div className="text-center">
<div className="truncate font-medium">{service.name}</div>
<div className="text-xs opacity-80 mt-1">
{service.price.toLocaleString('ru-RU')}
</div>
</div>
</label>
) )
} })
}} ) : (
className="h-8 w-8 p-0 bg-white/5 border-white/20 hover:bg-white/10 hover:border-white/30 text-white" <div className="text-white/60 text-xs p-2 text-center bg-white/5 rounded-md">
disabled={product.selectedQuantity <= 1} {selectedFulfillment ? 'Загрузка...' : 'Выберите ФФ'}
> </div>
<Minus className="h-3 w-3" /> )}
</Button> </div>
</div>
<Input {/* 5. РАСХОДНИКИ ФФ (flex-1) */}
type="number" <div className="flex-1 p-3 flex flex-col">
min="1" <div className="text-center mb-2">
max={product.quantity} {ffConsumablesCost > 0 && (
value={product.selectedQuantity} <div className="text-orange-400 font-semibold text-sm mb-1">
onChange={(e) => { {ffConsumablesCost.toLocaleString('ru-RU')}
const newQuantity = parseInt(e.target.value) || 1 </div>
setAllSelectedProducts((prev) => )}
prev.map((p) => <h6 className="text-orange-400 text-xs font-medium uppercase tracking-wider">📦 Расходники ФФ</h6>
p.id === product.id ? { ...p, selectedQuantity: newQuantity } : p, </div>
), <div className="flex-1 overflow-y-auto space-y-1" style={{ maxHeight: '75px' }}>
) {fulfillmentConsumables.length > 0 ? (
}} fulfillmentConsumables.map((consumable) => {
className="h-8 w-20 text-center bg-white/5 border-white/20 text-white placeholder:text-white/40 focus:border-white/40" const isSelected = selectedFFConsumablesIds.includes(consumable.id)
placeholder="1" return (
/> <label
key={consumable.id}
<Button className={`block w-full px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 hover:scale-105 ${
size="sm" isSelected
variant="outline" ? 'bg-orange-500/20 border-orange-500/30 text-orange-300 border'
onClick={() => { : 'bg-white/5 text-white/70 hover:bg-white/10 hover:text-white'
if (product.selectedQuantity < (product.quantity || 0)) { }`}
setAllSelectedProducts((prev) => >
prev.map((p) => <input
p.id === product.id ? { ...p, selectedQuantity: p.selectedQuantity + 1 } : p, type="checkbox"
), checked={isSelected}
onChange={() => toggleFFConsumable(product.id, consumable.id)}
className="sr-only"
/>
<div className="text-center">
<div className="truncate font-medium">{consumable.name}</div>
<div className="text-xs opacity-80 mt-1">
{consumable.price.toLocaleString('ru-RU')}
</div>
</div>
</label>
) )
} })
}} ) : (
className="h-8 w-8 p-0 bg-white/5 border-white/20 hover:bg-white/10 hover:border-white/30 text-white" <div className="text-white/60 text-xs p-2 text-center bg-white/5 rounded-md">
disabled={ {selectedFulfillment ? 'Загрузка...' : 'Выберите ФФ'}
product.quantity === 0 || product.selectedQuantity >= (product.quantity || 0) </div>
} )}
> </div>
<Plus className="h-3 w-3" />
</Button>
</div> </div>
<div className="bg-gradient-to-r from-green-500/10 to-emerald-500/10 border border-green-500/20 rounded-lg px-4 py-2">
<span className="text-green-400 font-bold text-lg"> {/* 6. РАСХОДНИКИ СЕЛЛЕРА (flex-1) */}
{(product.price * product.selectedQuantity).toLocaleString('ru-RU')} <div className="flex-1 p-3 flex flex-col">
</span> <div className="text-center mb-2">
{sellerConsumablesCost > 0 && (
<div className="text-blue-400 font-semibold text-sm mb-1">
{sellerConsumablesCost.toLocaleString('ru-RU')}
</div>
)}
<h6 className="text-blue-400 text-xs font-medium uppercase tracking-wider">🏪 Расходники сел.</h6>
</div>
<div className="flex-1 overflow-y-auto space-y-1" style={{ maxHeight: '75px' }}>
{sellerConsumables.length > 0 ? (
sellerConsumables.map((consumable) => {
const isSelected = selectedSellerConsumablesIds.includes(consumable.id)
return (
<label
key={consumable.id}
className={`block w-full px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 hover:scale-105 ${
isSelected
? 'bg-blue-500/20 border-blue-500/30 text-blue-300 border'
: 'bg-white/5 text-white/70 hover:bg-white/10 hover:text-white'
}`}
>
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleSellerConsumable(product.id, consumable.id)}
className="sr-only"
/>
<div className="text-center">
<div className="truncate font-medium">{consumable.name}</div>
<div className="text-xs opacity-80 mt-1">
{consumable.pricePerUnit} /{consumable.unit || 'шт'}
</div>
</div>
</label>
)
})
) : (
<div className="text-white/60 text-xs p-2 text-center bg-white/5 rounded-md">Загрузка...</div>
)}
</div>
</div>
{/* 7. МП + ИТОГО (flex-1) */}
<div className="flex-1 p-3 flex flex-col justify-between">
<div className="text-center">
<div className="text-green-400 font-bold text-lg mb-3">
Итого: {totalWithRecipe.toLocaleString('ru-RU')}
</div>
</div>
<div className="flex-1 flex flex-col justify-center">
<Select
value={recipe?.selectedWBCard || 'none'}
onValueChange={(value) => {
if (value !== 'none') {
_setWBCard(product.id, value)
}
}}
>
<SelectTrigger className="glass-input h-9 text-sm text-white">
<SelectValue placeholder="Не выбрано" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
Не выбрано
</SelectItem>
{/* TODO: Загружать из БД */}
<SelectItem value="card1">Карточка 1</SelectItem>
<SelectItem value="card2">Карточка 2</SelectItem>
</SelectContent>
</Select>
</div>
</div> </div>
</div> </div>
</div> </div>
)
{/* БЛОК РЕЦЕПТУРЫ: 4 колонки с чекбоксами */} })}
<div className="grid grid-cols-4 gap-4 pt-4 border-t border-white/10 mb-4">
{/* КОЛОНКА 1: Услуги фулфилмента */}
<div className="space-y-2">
<h5 className="text-white/80 font-medium text-sm flex items-center gap-2">
<Settings className="h-4 w-4 text-purple-400" />
Услуги ФФ
</h5>
<div className="space-y-1 max-h-32 overflow-y-auto">
{fulfillmentServices.length > 0 ? (
fulfillmentServices.map((service) => {
const recipe = productRecipes[product.id]
const isSelected = recipe?.selectedServices.includes(service.id) || false
return (
<label
key={service.id}
className="flex items-center gap-2 text-xs cursor-pointer hover:bg-white/5 p-1 rounded"
>
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleService(product.id, service.id)}
className="w-3 h-3 rounded bg-white/10 border-white/20 text-purple-400 focus:ring-purple-400/50 focus:ring-offset-0"
/>
<span className="text-white/70 flex-1 truncate">{service.name}</span>
<span className="text-purple-400 text-xs font-medium">
{service.price.toLocaleString('ru-RU')}
</span>
</label>
)
})
) : (
<div className="text-white/50 text-xs p-2 bg-white/5 rounded border border-white/10">
{selectedFulfillment ? 'Услуги загружаются...' : 'Выберите фулфилмент-центр'}
</div>
)}
</div>
</div>
{/* КОЛОНКА 2: Расходники фулфилмента */}
<div className="space-y-2">
<h5 className="text-white/80 font-medium text-sm flex items-center gap-2">
<Box className="h-4 w-4 text-orange-400" />
Расходники ФФ
</h5>
<div className="space-y-1 max-h-32 overflow-y-auto">
{fulfillmentConsumables.length > 0 ? (
fulfillmentConsumables.map((consumable) => {
const recipe = productRecipes[product.id]
const isSelected = recipe?.selectedFFConsumables.includes(consumable.id) || false
return (
<label
key={consumable.id}
className="flex items-center gap-2 text-xs cursor-pointer hover:bg-white/5 p-1 rounded"
>
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleFFConsumable(product.id, consumable.id)}
className="w-3 h-3 rounded bg-white/10 border-white/20 text-orange-400 focus:ring-orange-400/50 focus:ring-offset-0"
/>
<span className="text-white/70 flex-1 truncate">{consumable.name}</span>
<span className="text-orange-400 text-xs font-medium">
{consumable.price.toLocaleString('ru-RU')}
</span>
</label>
)
})
) : (
<div className="text-white/50 text-xs p-2 bg-white/5 rounded border border-white/10">
{selectedFulfillment ? 'Расходники загружаются...' : 'Выберите фулфилмент-центр'}
</div>
)}
</div>
</div>
{/* КОЛОНКА 3: Расходники селлера */}
<div className="space-y-2">
<h5 className="text-white/80 font-medium text-sm flex items-center gap-2">
<Package className="h-4 w-4 text-blue-400" />
Расходники селлера
</h5>
<div className="space-y-1 max-h-32 overflow-y-auto">
{sellerConsumables.length > 0 ? (
sellerConsumables.map((consumable) => {
const recipe = productRecipes[product.id]
const isSelected =
recipe?.selectedSellerConsumables.includes(consumable.id) || false
return (
<label
key={consumable.id}
className="flex items-center gap-2 text-xs cursor-pointer hover:bg-white/5 p-1 rounded"
>
<input
type="checkbox"
checked={isSelected}
onChange={() => toggleSellerConsumable(product.id, consumable.id)}
className="w-3 h-3 rounded bg-white/10 border-white/20 text-blue-400 focus:ring-blue-400/50 focus:ring-offset-0"
/>
<span className="text-white/70 flex-1 truncate">{consumable.name}</span>
<span className="text-blue-400 text-xs">Склад: {consumable.stock}</span>
</label>
)
})
) : (
<div className="text-white/50 text-xs p-2 bg-white/5 rounded border border-white/10">
Расходники селлера загружаются...
</div>
)}
</div>
</div>
{/* КОЛОНКА 4: Карточки Wildberries */}
<div className="space-y-2">
<h5 className="text-white/80 font-medium text-sm flex items-center gap-2">
<ShoppingCart className="h-4 w-4 text-pink-400" />
Карточки WB
</h5>
<div className="space-y-1 max-h-32 overflow-y-auto">
{wbCards.length > 0 ? (
wbCards.map((card) => {
const recipe = productRecipes[product.id]
const isSelected = recipe?.selectedWBCard === card.id
return (
<label
key={card.id}
className="flex items-center gap-2 text-xs cursor-pointer hover:bg-white/5 p-1 rounded"
>
<input
type="radio"
name={`wb-card-${product.id}`}
checked={isSelected}
onChange={() => setWBCard(product.id, card.id)}
className="w-3 h-3 rounded-full bg-white/10 border-white/20 text-pink-400 focus:ring-pink-400/50 focus:ring-offset-0"
/>
<span className="text-white/70 flex-1 truncate">{card.title}</span>
<span className="text-pink-400 text-xs">{card.nmID}</span>
</label>
)
})
) : (
<div className="text-white/50 text-xs p-2 bg-white/5 rounded border border-white/10">
Карточки WB загружаются...
</div>
)}
</div>
</div>
</div>
{/* НИЖНИЙ БЛОК: Итоговая стоимость рецептуры + кнопка добавления */}
<div className="flex items-center justify-between pt-4 border-t border-white/10">
<div className="flex items-center gap-6">
{(() => {
const quantity = getProductQuantity(product.id)
const recipeCost = calculateRecipeCost(product.id)
const productTotal = product.price * quantity
return (
<>
<div className="text-sm text-white/70">
Товар:{' '}
<span className="text-white font-semibold">
{productTotal.toLocaleString('ru-RU')}
</span>
</div>
<div className="text-sm text-white/70">
Услуги:{' '}
<span className="text-purple-400 font-semibold">
{recipeCost.services.toLocaleString('ru-RU')}
</span>
</div>
<div className="text-sm text-white/70">
Расходники:{' '}
<span className="text-orange-400 font-semibold">
{recipeCost.consumables.toLocaleString('ru-RU')}
</span>
</div>
</>
)
})()}
</div>
<div className="flex items-center gap-4">
{(() => {
const quantity = getProductQuantity(product.id)
const recipeCost = calculateRecipeCost(product.id)
const productTotal = product.price * quantity
const totalRecipePrice = productTotal + recipeCost.total
return (
<>
<div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-500/30 rounded-lg px-4 py-2">
<span className="text-white/70 text-sm">Итого: </span>
<span className="text-green-400 font-bold text-lg">
{totalRecipePrice.toLocaleString('ru-RU')}
</span>
</div>
<Button
onClick={() => addToCart(product)}
disabled={quantity === 0}
className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white border border-green-500/30 hover:border-green-400/50 transition-all duration-200"
>
<ShoppingCart className="h-4 w-4 mr-2" />
Добавить рецептуру
</Button>
</>
)
})()}
</div>
</div>
</div>
))}
</div> </div>
)} )}
</div> </div>
@ -1283,401 +1326,118 @@ export function CreateSuppliersSupplyPage() {
</div> </div>
{/* БЛОК 4: КОРЗИНА И НАСТРОЙКИ - правый блок согласно rules-complete.md 9.2 */} {/* БЛОК 4: КОРЗИНА И НАСТРОЙКИ - правый блок согласно rules-complete.md 9.2 */}
<div className="w-96 flex-shrink-0 flex flex-col min-h-0"> <div className="w-72 flex-shrink-0">
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-1 flex flex-col min-h-0"> <div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
{/* ЗАГОЛОВОК И СТАТИСТИКА */} <h3 className="text-white font-semibold mb-3 flex items-center text-sm">
<div className="p-4 border-b border-white/10 flex-shrink-0"> <ShoppingCart className="h-4 w-4 mr-2" />
<div className="flex items-center gap-3 mb-4"> Корзина ({selectedGoods.length} шт)
<div className="p-2 bg-purple-400/10 rounded-lg border border-purple-400/20"> </h3>
<ShoppingCart className="h-5 w-5 text-purple-400" />
</div> {selectedGoods.length === 0 ? (
<div> <div className="text-center py-6">
<h3 className="text-lg font-semibold text-white">Корзина и настройки поставки</h3> <div className="bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-full p-4 w-fit mx-auto mb-3">
<p className="text-white/60 text-xs mt-1">Управление заказом и параметрами доставки</p> <ShoppingCart className="h-8 w-8 text-purple-300" />
</div> </div>
<p className="text-white/60 text-sm font-medium mb-2">Корзина пуста</p>
<p className="text-white/40 text-xs mb-3">Добавьте товары из каталога для создания поставки</p>
</div> </div>
) : (
<div className="space-y-2 mb-4">
{selectedGoods.map((item) => {
// Используем единую функцию расчета
const itemTotalPrice = getProductTotalWithRecipe(item.id, item.selectedQuantity)
const basePrice = item.price
const priceWithRecipe = itemTotalPrice / item.selectedQuantity
{/* СТАТИСТИКА ПОСТАВКИ */} return (
<div className="grid grid-cols-2 gap-3"> <div key={item.id} className="flex items-center justify-between bg-white/5 rounded-lg p-2">
<div className="bg-white/5 border border-white/10 rounded-lg p-3"> <div className="flex-1 min-w-0">
<div className="flex items-center justify-between"> <h4 className="text-white text-sm font-medium truncate">{item.name}</h4>
<div> <p className="text-white/60 text-xs">
<p className="text-xs text-white/60">Поставщиков</p> {priceWithRecipe.toLocaleString('ru-RU')} × {item.selectedQuantity}
<p className="text-lg font-semibold text-white">{selectedSupplier ? 1 : 0}</p> </p>
</div> </div>
<Building2 className="h-4 w-4 text-green-400" /> <div className="flex items-center gap-2">
</div> <span className="text-green-400 font-bold text-sm">
</div> {itemTotalPrice.toLocaleString('ru-RU')}
<div className="bg-white/5 border border-white/10 rounded-lg p-3"> </span>
<div className="flex items-center justify-between"> <Button
<div> variant="ghost"
<p className="text-xs text-white/60">Товаров</p> size="sm"
<p className="text-lg font-semibold text-white">{selectedGoods.length}</p> onClick={() => removeFromCart(item.id)}
</div> className="text-red-400 hover:text-red-300 hover:bg-red-500/20 p-1 h-6 w-6"
<Package className="h-4 w-4 text-blue-400" /> >
</div> <X className="h-3 w-3" />
</div> </Button>
<div className="bg-white/5 border border-white/10 rounded-lg p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-white/60">Количество</p>
<p className="text-lg font-semibold text-white">{totalQuantity} шт</p>
</div>
<Box className="h-4 w-4 text-orange-400" />
</div>
</div>
<div className="bg-white/5 border border-white/10 rounded-lg p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-white/60">Сумма</p>
<p className="text-lg font-semibold text-white">{totalAmount.toLocaleString('ru-RU')} </p>
</div>
<DollarSign className="h-4 w-4 text-purple-400" />
</div>
</div>
</div>
</div>
{/* НАСТРОЙКИ ПОСТАВКИ */}
<div className="p-4 border-b border-white/10 flex-shrink-0">
<h4 className="text-sm font-semibold text-white mb-3 flex items-center gap-2">
<Settings className="h-4 w-4 text-blue-400" />
Настройки поставки
</h4>
<div className="space-y-3">
{/* Выбор фулфилмент-центра */}
<div>
<label className="text-white/70 text-xs font-medium mb-2 flex items-center gap-2">
<Building2 className="h-3 w-3 text-green-400" />
Фулфилмент-центр *
</label>
<select
value={selectedFulfillment}
onChange={(e) => setSelectedFulfillment(e.target.value)}
className="w-full bg-white/5 border-white/10 text-white h-8 text-sm rounded-lg hover:border-white/30 focus:border-green-400/50 transition-all duration-200"
>
<option value="" className="bg-gray-800 text-white">
Выберите фулфилмент-центр
</option>
{fulfillmentCenters.map((center) => (
<option key={center.id} value={center.id} className="bg-gray-800 text-white">
{center.name} - {center.address}
</option>
))}
</select>
</div>
{/* Дата поставки */}
<div>
<label className="text-white/70 text-xs font-medium mb-2 flex items-center gap-2">
<Calendar className="h-3 w-3 text-blue-400" />
Желаемая дата поставки *
</label>
<div className="relative">
<Calendar className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40 h-3 w-3 z-10" />
<Input
type="date"
value={deliveryDate}
onChange={(e) => setDeliveryDate(e.target.value)}
min={minDateString}
max={maxDateString}
className="bg-white/5 border-white/10 text-white pl-9 h-8 text-sm hover:border-white/30 focus:border-blue-400/50 transition-all duration-200"
required
/>
</div>
</div>
{/* Выбор логистики */}
<div>
<label className="text-white/70 text-xs font-medium mb-2 flex items-center gap-2">
<Truck className="h-3 w-3 text-orange-400" />
Логистическая компания
</label>
<select
value={selectedLogistics}
onChange={(e) => setSelectedLogistics(e.target.value)}
className="w-full bg-white/5 border-white/10 text-white h-8 text-sm rounded-lg hover:border-white/30 focus:border-orange-400/50 transition-all duration-200"
>
<option value="auto" className="bg-gray-800 text-white">
Автоматический выбор
</option>
{logisticsCompanies.map((company) => (
<option key={company.id} value={company.id} className="bg-gray-800 text-white">
{company.name} (~{company.estimatedCost} , {company.deliveryDays} дн.)
</option>
))}
</select>
</div>
</div>
</div>
{/* ТОВАРЫ В КОРЗИНЕ */}
<div className="flex-1 overflow-y-auto p-4">
{selectedGoods.length === 0 ? (
<div className="flex items-center justify-center h-full">
<div className="text-center space-y-3">
<div className="w-16 h-16 mx-auto bg-purple-400/5 rounded-full flex items-center justify-center">
<ShoppingCart className="h-8 w-8 text-purple-400/50" />
</div>
<div>
<h4 className="text-base font-medium text-white mb-2">Корзина пуста</h4>
<p className="text-white/60 text-sm">Добавьте товары из каталога поставщика</p>
</div>
</div>
</div>
) : (
<div className="space-y-4">
{selectedGoods.map((item) => (
<div
key={item.id}
className="glass-card hover:border-white/20 transition-all duration-200 group"
>
<div className="p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex-1 min-w-0">
<h4 className="text-white font-semibold text-base truncate group-hover:text-white transition-colors">
{item.name}
</h4>
<p className="text-white/60 text-sm font-mono mt-1">Артикул: {item.sku}</p>
{item.category && (
<Badge className="bg-blue-500/20 text-blue-300 border border-blue-500/30 text-xs font-medium mt-2">
{item.category}
</Badge>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeFromCart(item.id)}
className="text-red-400 hover:text-red-300 hover:bg-red-500/20 border border-transparent hover:border-red-500/30 p-2 transition-all duration-200"
>
<Minus className="h-4 w-4" />
</Button>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-white/70 text-xs font-medium">Количество:</span>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => {
const newQuantity = Math.max(1, item.selectedQuantity - 1)
addToCart(
{
id: item.id,
name: item.name,
sku: item.sku,
price: item.price,
category: { name: item.category || '' },
images: [],
organization: { id: item.supplierId, name: item.supplierName },
unit: item.unit,
} as GoodsProduct,
newQuantity,
)
}}
className="h-7 w-7 p-0 border border-white/20 text-white/70 hover:text-white hover:bg-white/10 transition-all duration-200"
>
<Minus className="h-3 w-3" />
</Button>
<Input
type="number"
min="1"
value={item.selectedQuantity}
onChange={(e) => {
const newQuantity = parseInt(e.target.value) || 1
addToCart(
{
id: item.id,
name: item.name,
sku: item.sku,
price: item.price,
category: { name: item.category || '' },
images: [],
organization: { id: item.supplierId, name: item.supplierName },
unit: item.unit,
} as GoodsProduct,
newQuantity,
)
}}
className="glass-input text-white w-16 h-7 text-center text-xs font-medium"
/>
<Button
variant="ghost"
size="sm"
onClick={() => {
const newQuantity = item.selectedQuantity + 1
addToCart(
{
id: item.id,
name: item.name,
sku: item.sku,
price: item.price,
category: { name: item.category || '' },
images: [],
organization: { id: item.supplierId, name: item.supplierName },
unit: item.unit,
} as GoodsProduct,
newQuantity,
)
}}
className="h-7 w-7 p-0 border border-white/20 text-white/70 hover:text-white hover:bg-white/10 transition-all duration-200"
>
<Plus className="h-3 w-3" />
</Button>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-white/70 text-xs font-medium">Цена за {item.unit || 'шт'}:</span>
<span className="text-white text-xs font-semibold">
{item.price.toLocaleString('ru-RU')}
</span>
</div>
<div className="flex items-center justify-between p-2 bg-white/5 rounded-lg border border-white/10">
<span className="text-white/80 text-sm font-medium">Сумма:</span>
<span className="text-green-400 text-base font-bold">
{(item.price * item.selectedQuantity).toLocaleString('ru-RU')}
</span>
</div>
{/* Дополнительная информация */}
{(item.completeness || item.recipe || item.specialRequirements || item.parameters) && (
<div className="mt-2 pt-2 border-t border-white/10 space-y-1">
{item.completeness && (
<div className="flex items-start gap-2">
<FileText className="h-3 w-3 text-blue-400 flex-shrink-0 mt-0.5" />
<div>
<span className="text-blue-300 text-xs font-medium">Комплектность: </span>
<span className="text-white/80 text-xs">{item.completeness}</span>
</div>
</div>
)}
{item.recipe && (
<div className="flex items-start gap-2">
<Settings className="h-3 w-3 text-purple-400 flex-shrink-0 mt-0.5" />
<div>
<span className="text-purple-300 text-xs font-medium">Рецептура: </span>
<span className="text-white/80 text-xs">{item.recipe}</span>
</div>
</div>
)}
{item.specialRequirements && (
<div className="flex items-start gap-2">
<AlertCircle className="h-3 w-3 text-yellow-400 flex-shrink-0 mt-0.5" />
<div>
<span className="text-yellow-300 text-xs font-medium">Требования: </span>
<span className="text-white/80 text-xs">{item.specialRequirements}</span>
</div>
</div>
)}
{item.parameters && item.parameters.length > 0 && (
<div className="space-y-1">
<div className="flex items-center gap-1">
<Settings className="h-3 w-3 text-green-400" />
<span className="text-green-300 text-xs font-medium">Параметры:</span>
</div>
<div className="flex flex-wrap gap-1">
{item.parameters.map((param, idx) => (
<Badge
key={idx}
className="bg-green-500/10 text-green-300 border border-green-500/20 text-xs"
>
{param.name}: {param.value}
</Badge>
))}
</div>
</div>
)}
</div>
)}
</div>
</div> </div>
</div> </div>
))} )
</div> })}
)}
</div>
{/* ИТОГИ И КНОПКА СОЗДАНИЯ */}
<div className="p-4 border-t border-white/10 space-y-4 flex-shrink-0">
{/* Детальные итоги */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-white/70 text-xs font-medium">Товаров:</span>
<span className="text-white text-xs font-semibold">{totalQuantity} шт</span>
</div>
<div className="flex justify-between items-center">
<span className="text-white/70 text-xs font-medium">Стоимость товаров:</span>
<span className="text-white text-xs font-semibold">
{totalGoodsAmount.toLocaleString('ru-RU')}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-purple-300 text-xs font-medium">Фулфилмент (8%):</span>
<span className="text-purple-300 text-xs font-semibold">
{fulfillmentFee.toLocaleString('ru-RU')}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-orange-300 text-xs font-medium">Логистика:</span>
<span className="text-orange-300 text-xs font-semibold">
{selectedLogistics === 'auto' ? '~' : ''}
{logisticsCost.toLocaleString('ru-RU')}
</span>
</div>
<div className="flex justify-between items-center p-2 bg-gradient-to-r from-green-500/10 to-emerald-500/10 border border-green-500/20 rounded-lg">
<span className="text-white text-sm font-semibold">Итого к оплате:</span>
<span className="text-green-400 text-lg font-bold">{totalAmount.toLocaleString('ru-RU')} </span>
</div>
</div> </div>
)}
{/* Кнопка создания поставки */} {selectedGoods.length > 0 && (
<Button <>
onClick={handleCreateSupply} <div className="border-t border-white/10 pt-3 mb-3">
disabled={!isFormValid || isCreatingSupply} {deliveryDate && (
className="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white font-semibold py-3 text-sm border border-green-500/30 hover:border-green-400/50 transition-all duration-300 disabled:opacity-50" <div className="mb-2">
> <p className="text-white/60 text-xs">Дата поставки:</p>
{isCreatingSupply ? ( <p className="text-white text-xs font-medium">
<div className="flex items-center gap-2"> {new Date(deliveryDate).toLocaleDateString('ru-RU')}
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> </p>
<span>Создание поставки...</span> </div>
</div> )}
) : (
<div className="flex items-center gap-2">
<FileText className="h-4 w-4" />
<span>Продолжить оформление</span>
</div>
)}
</Button>
{/* Сообщения об ошибках валидации */} {selectedFulfillment && (
{!isFormValid && ( <div className="mb-2">
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-2 mt-2"> <p className="text-white/60 text-xs">Фулфилмент-центр:</p>
<div className="flex items-center gap-2"> <p className="text-white text-xs font-medium">
<AlertCircle className="h-3 w-3 text-red-400 flex-shrink-0" /> {allCounterparties?.find(c => c.id === selectedFulfillment)?.name ||
<p className="text-red-300 text-xs font-medium"> allCounterparties?.find(c => c.id === selectedFulfillment)?.fullName ||
{!selectedSupplier 'Выбранный центр'}
? 'Выберите поставщика' </p>
: selectedSupplier && selectedGoods.length === 0 </div>
? 'Добавьте товары в корзину' )}
: selectedSupplier && selectedGoods.length > 0 && !deliveryDate
? 'Укажите дату поставки' <div className="mb-3">
: selectedSupplier && <p className="text-white/60 text-xs mb-1">Логистическая компания:</p>
selectedGoods.length > 0 && <select
deliveryDate && value={selectedLogistics}
!selectedFulfillment onChange={(e) => setSelectedLogistics(e.target.value)}
? 'Выберите фулфилмент-центр' className="w-full bg-white/5 border-white/10 text-white h-7 text-xs rounded hover:border-white/30 focus:border-purple-400/50 transition-all duration-200"
: ''} >
</p> <option value="auto" className="bg-gray-800 text-white">
Выбрать
</option>
{logisticsCompanies.length > 0 ? (
logisticsCompanies.map((logisticsPartner) => (
<option key={logisticsPartner.id} value={logisticsPartner.id} className="bg-gray-800 text-white">
{logisticsPartner.name || logisticsPartner.fullName}
</option>
))
) : (
<option value="" disabled className="bg-gray-800 text-white">
Нет доступных логистических партнеров
</option>
)}
</select>
</div> </div>
</div> </div>
)}
</div> <div className="flex items-center justify-between mb-3 pt-2 border-t border-white/10">
<span className="text-white font-semibold text-sm">Итого:</span>
<span className="text-green-400 font-bold text-lg">{totalAmount.toLocaleString('ru-RU')} </span>
</div>
<Button
onClick={handleCreateSupply}
disabled={!isFormValid || isCreatingSupply}
className="w-full bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white disabled:opacity-50 h-8 text-sm"
>
{isCreatingSupply ? 'Создание...' : 'Создать поставку'}
</Button>
</>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -54,7 +54,7 @@ interface WBProductCardsProps {
} }
export function WBProductCards({ export function WBProductCards({
_onBack, // eslint-disable-line @typescript-eslint/no-unused-vars _onBack,
onComplete, onComplete,
showSummary: externalShowSummary, showSummary: externalShowSummary,
setShowSummary: externalSetShowSummary, setShowSummary: externalSetShowSummary,

View File

@ -678,6 +678,34 @@ export const CREATE_SUPPLY_ORDER = gql`
quantity quantity
price price
totalPrice totalPrice
recipe {
services {
id
name
description
price
}
fulfillmentConsumables {
id
name
description
pricePerUnit
unit
imageUrl
organization {
id
name
}
}
sellerConsumables {
id
name
description
price
unit
}
marketplaceCardId
}
product { product {
id id
name name

View File

@ -3797,7 +3797,16 @@ export const resolvers = {
deliveryDate: string deliveryDate: string
fulfillmentCenterId?: string // ID фулфилмент-центра для доставки fulfillmentCenterId?: string // ID фулфилмент-центра для доставки
logisticsPartnerId?: string // ID логистической компании logisticsPartnerId?: string // ID логистической компании
items: Array<{ productId: string; quantity: number }> items: Array<{
productId: string
quantity: number
recipe?: {
services: string[]
fulfillmentConsumables: string[]
sellerConsumables: string[]
marketplaceCardId?: string
}
}>
notes?: string // Дополнительные заметки к заказу notes?: string // Дополнительные заметки к заказу
consumableType?: string // Классификация расходников consumableType?: string // Классификация расходников
} }
@ -3941,6 +3950,11 @@ export const resolvers = {
quantity: item.quantity, quantity: item.quantity,
price: product.price, price: product.price,
totalPrice: new Prisma.Decimal(itemTotal), totalPrice: new Prisma.Decimal(itemTotal),
// Передача данных рецептуры в Prisma модель
services: item.recipe?.services || [],
fulfillmentConsumables: item.recipe?.fulfillmentConsumables || [],
sellerConsumables: item.recipe?.sellerConsumables || [],
marketplaceCardId: item.recipe?.marketplaceCardId,
} }
}) })

View File

@ -609,6 +609,7 @@ export const typeDefs = gql`
quantity: Int! quantity: Int!
price: Float! price: Float!
totalPrice: Float! totalPrice: Float!
recipe: ProductRecipe
} }
enum SupplyOrderStatus { enum SupplyOrderStatus {
@ -635,6 +636,7 @@ export const typeDefs = gql`
input SupplyOrderItemInput { input SupplyOrderItemInput {
productId: ID! productId: ID!
quantity: Int! quantity: Int!
recipe: ProductRecipeInput
} }
type PendingSuppliesCount { type PendingSuppliesCount {
@ -655,6 +657,21 @@ export const typeDefs = gql`
status: String! # Текущий статус заказа status: String! # Текущий статус заказа
} }
# Типы для рецептуры продуктов
type ProductRecipe {
services: [Service!]!
fulfillmentConsumables: [Supply!]!
sellerConsumables: [Supply!]!
marketplaceCardId: String
}
input ProductRecipeInput {
services: [ID!]!
fulfillmentConsumables: [ID!]!
sellerConsumables: [ID!]!
marketplaceCardId: String
}
type SupplyOrderResponse { type SupplyOrderResponse {
success: Boolean! success: Boolean!
message: String! message: String!