diff --git a/dev.log b/dev.log new file mode 100644 index 0000000..1048337 --- /dev/null +++ b/dev.log @@ -0,0 +1,1988 @@ + +> sferav@0.1.0 dev +> next dev --turbopack + + ▲ Next.js 15.4.1 (Turbopack) + - Local: http://localhost:3000 + - Network: http://192.168.0.104:3000 + - Environments: .env + - Experiments (use with caution): + · optimizePackageImports + + ✓ Starting... + ✓ Ready in 834ms + ○ Compiling /api/graphql ... + ✓ 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 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8226cf7..744f359 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,6 +65,7 @@ model Organization { ogrn String? ogrnDate DateTime? type OrganizationType + market String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt address String? diff --git a/rules-complete.md b/rules-complete.md index 38c6fb7..4499249 100644 --- a/rules-complete.md +++ b/rules-complete.md @@ -1,4 +1,4 @@ -# ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v9.2 +# ПРАВИЛА СИСТЕМЫ УПРАВЛЕНИЯ СКЛАДАМИ И ПОСТАВКАМИ - ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ v10.0 > ⚠️ **АБСОЛЮТНО ПОЛНЫЙ ЕДИНЫЙ ИСТОЧНИК ИСТИНЫ**: Данный файл объединяет АБСОЛЮТНО ВСЕ правила системы: протоколы работы Claude Code, детальные протоколы по сложности, систему предотвращения нарушений, расширенную самопроверку, специальный UI/UX протокол и бизнес-правила. Визуальные правила вынесены в отдельный файл visual-design-rules.md с автоматической интеграцией. @@ -309,6 +309,7 @@ | **Workflow поставок** | [5](#5--workflow-поставок) | 8 статусов, уведомления, логистика | | **GraphQL запросы** | [18](#18--graphql-и-typescript-правила), [24](#24--технические-приложения) | Резолверы, мутации, типизация | | **Система партнерства** | [13](#13--система-партнерства-и-контрагентов) | Counterparty, WHOLESALE, заявки | +| **Рынки и маркет** | [10.1](#101-разделение-понятий-рынок-vs-маркет), [18.7](#187-правила-рынков-и-маркета) | РЫНОК ≠ МАРКЕТ, Organization.market | | **Критические запреты** | [17](#17--критические-запреты) | Что НЕЛЬЗЯ делать в системе | ### 🎯 ДЛЯ РАЗНЫХ РОЛЕЙ @@ -1005,15 +1006,17 @@ const handleSuppliesClick = () => { **ОБНОВЛЕННАЯ СТРУКТУРА СИСТЕМЫ (4 БЛОКА):** -**БЛОК 1: ПОСТАВЩИКИ** _(горизонтальный скролл)_ -- **Отображение**: Карточки поставщиков из раздела "Партнеры" -- **Навигация**: Горизонтальный скролл (слева-направо) при превышении ширины экрана +**БЛОК 1: ПОСТАВЩИКИ** _(адаптивная сетка)_ +- **Заголовок**: Минималистичный "🏢 Поставщики" без лишних элементов +- **Поиск**: Компактное поле справа "Поиск поставщиков..." (w-64) +- **Отображение**: Карточки поставщиков из раздела "Партнеры" в адаптивной сетке - **Выбор**: Клик выделяет карточку поставщика - **Результат**: Загружаются карточки товаров выбранного поставщика в блок 2 **БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(горизонтальный скролл - НОВЫЙ)_ -- **Отображение**: Компактные карточки товаров выбранного поставщика -- **Навигация**: Горизонтальный скролл аналогично блоку 1 +- **Отображение**: ТОЛЬКО минималистичные карточки товаров 80×112px +- **Содержание**: ТОЛЬКО изображение товара, БЕЗ текста/названий/цен +- **Навигация**: Горизонтальный скролл при множестве товаров - **Выбор**: Клик добавляет товар в детальный каталог - **Результат**: Товар добавляется в блок 3 для управления поставкой @@ -1136,28 +1139,305 @@ const handleSuppliesClick = () => { - **Источник данных**: Товары выбранного поставщика из Блока 1 - **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров - **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика" -- **Поведение**: Горизонтальный скролл при множестве товаров (аналогично Блоку 1) +- **Поведение**: Горизонтальный скролл при множестве товаров **АРХИТЕКТУРА И РАЗМЕРЫ:** -- **Общая высота блока**: 160px фиксированная -- **Заголовок**: "Товары [Название поставщика]" + поиск (~40px) -- **Контейнер скролла**: 120px (h-30) с горизонтальным скроллом +- **Внешний контейнер**: bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0 +- **Внутренний контейнер скролла**: flex gap-3 overflow-x-auto p-4 +- **Стилизация скролла**: scrollbarWidth: 'thin' для тонкой полосы прокрутки +- **Отступы**: padding: 16px (p-4) внутри, gap: 12px (gap-3) между карточками +- **Адаптивная высота**: по содержимому карточек (БЕЗ фиксированной высоты) +- **Визуальное единство**: стеклянный эффект как у других блоков системы +- **БЕЗ заголовков/иконок**: только чистые карточки товаров в контейнере **РАЗМЕРЫ КАРТОЧЕК ТОВАРОВ:** -- **Компактная карточка**: 80×112px (соотношение 5:7), вертикальное изображение -- **Отступы**: 12px между карточками, без дополнительных отступов от краев +- **Компактная карточка**: 80×112px (w-20 h-28), соотношение 5:7 - **Адаптивность**: фиксированный размер для всех устройств **СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:** -- **Только изображение**: 80×112px товара, вертикальное -- **Минималистичный дизайн**: без текста, названий, цен -- **Состояния**: выбранное/невыбранное с визуальной индикацией -- **Hover эффект**: увеличение border, изменение тени +- **ТОЛЬКО изображение товара**: 80×112px, object-cover +- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок +- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов) +- **Рамка**: border-white/10, при выборе border-white/30 +- **Фон**: bg-white/5 полупрозрачный **ДЕЙСТВИЕ:** Клик на карточку → добавление товара в Блок 3 (детальный каталог) -### 9.2.2.1 ПРАВИЛО ПЕРСИСТЕНТНОСТИ ВЫБРАННЫХ ТОВАРОВ +#### **9.2.3 Правила Блока 3 "Детальный каталог товаров"** + +**НАЗНАЧЕНИЕ И СТРУКТУРА:** +- **Контент**: Детальные карточки выбранных товаров с полным управлением +- **Верхняя панель**: Выбор даты + Выбор Fulfillment + Поиск +- **Основная область**: Сетка карточек товаров с детальной информацией + +#### **9.2.3.1 Структура верхней панели Блока 3** + +**МИНИМАЛИСТИЧНАЯ ПАНЕЛЬ УПРАВЛЕНИЯ:** +- **Выбор даты поставки**: DatePicker для планирования поставки +- **Выбор Fulfillment-центра**: Select dropdown со списком доступных фулфилментов +- **Поиск по товарам**: Input с иконкой поиска и placeholder +- **Компоновка**: Горизонтальная строка с равномерным распределением + +**ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ:** +```tsx +// Структура компонентов панели +
+ + +
+ + +
+
+``` + +#### **9.2.3.2 Структура основной области карточек** + +**СЕТКА ТОВАРОВ:** +- **Адаптивная сетка**: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4` +- **Детальные карточки**: Полная информация + количество + управление +- **Состояния**: Default, Selected, Editing +- **Интерактивность**: Изменение количества, удаление, настройки рецептуры + +**ФУНКЦИОНАЛЬНОСТЬ ПАНЕЛИ:** +- **Выбор даты**: Планирование времени поставки (обязательное поле) +- **Выбор фулфилмента**: Определение исполнителя поставки (обязательное поле) +- **Поиск**: Фильтрация товаров в каталоге по названию/артикулу +- **Валидация**: Блокировка создания поставки без заполнения даты и фулфилмента + +**ГРАНИЧНЫЕ СЛУЧАИ:** +- **Пустой каталог**: Заглушка "Добавьте товары" +- **Нет фулфилментов**: Сообщение "Настройте партнерство с фулфилмент-центрами" +- **Поиск без результатов**: "По запросу ничего не найдено" + +#### **9.2.2.1 Структура контейнера Блока 2** + +**ДВУХУРОВНЕВАЯ АРХИТЕКТУРА:** + +**УРОВЕНЬ 1 - Внешний контейнер (блок):** +```jsx +
+``` +- **Назначение**: Визуальное обрамление блока, единство с другими блоками +- **Стилизация**: Стеклянный эффект с размытием и полупрозрачностью +- **Рамка**: Тонкая белая рамка border-white/20 с закруглёнными углами +- **Поведение**: flex-shrink-0 предотвращает сжатие блока + +**УРОВЕНЬ 2 - Внутренний контейнер (скролл):** +```jsx +
+``` +- **Назначение**: Горизонтальная прокрутка карточек товаров +- **Раскладка**: Flex с промежутками gap-3 (12px) между карточками +- **Отступы**: padding p-4 (16px) со всех сторон +- **Скролл**: overflow-x-auto с тонкой полосой прокрутки +- **Поведение**: Автоматическое появление скролла при превышении ширины + +**ПРАВИЛА КОНТЕЙНЕРОВ:** +- Внешний контейнер НЕ содержит заголовков, иконок, описаний +- Внутренний контейнер содержит ТОЛЬКО карточки товаров +- Высота адаптируется под размер карточек (80×112px + отступы) +- Визуальное единство со всеми блоками формы поставки + +**ТЕХНИЧЕСКИЕ ПРАВИЛА:** +- **Условие отображения**: selectedSupplier && products.length > 0 +- **Источник данных**: products массив из GraphQL запроса organizationProducts +- **Реактивность**: Автоматическое обновление при смене поставщика +- **Производительность**: React.memo для карточек при большом количестве товаров +- **Доступность**: Клавиатурная навигация (Tab, Enter для выбора) + +**UX ПРАВИЛА ВЗАИМОДЕЙСТВИЯ:** +- **Скролл**: Автоматическое появление при превышении ширины контейнера +- **Индикация загрузки**: Скелетоны карточек во время загрузки товаров +- **Пустое состояние**: Скрытие блока при отсутствии поставщика или товаров +- **Фокус**: Первая карточка получает фокус при загрузке товаров +- **Навигация**: Стрелки ←→ для перемещения между карточками + +**СОСТОЯНИЯ БЛОКА:** +- **Скрыт**: При отсутствии выбранного поставщика +- **Скрыт**: При отсутствии товаров у поставщика +- **Активен**: При наличии поставщика и товаров +- **Загрузка**: Показ скелетонов карточек во время запроса + +**ПРАВИЛА ПРОИЗВОДИТЕЛЬНОСТИ:** +- **Виртуализация**: При количестве товаров > 100 +- **Ленивая загрузка изображений**: loading="lazy" для всех изображений +- **Мемоизация**: React.memo для компонентов карточек +- **Дебаунс**: 300мс для поисковых запросов (если будет добавлен поиск) + +**ПРАВИЛА АДАПТИВНОСТИ:** +- **Мобильные устройства**: Свайп для горизонтальной прокрутки +- **Планшеты**: Сохранение размеров карточек 80×112px +- **Десктоп**: Полная функциональность с клавиатурной навигацией +- **Высокие разрешения**: Сохранение пропорций и читаемости + +**ПРАВИЛА БЕЗОПАСНОСТИ И ВАЛИДАЦИИ:** +- **Валидация данных**: Проверка существования product.id перед добавлением +- **Дубликаты**: Предотвращение добавления одного товара дважды в детальный каталог +- **Санитизация**: Безопасное отображение названий товаров (XSS защита) +- **Обработка ошибок**: Graceful degradation при ошибках загрузки изображений +- **Защита от спама**: Дебаунс кликов 200мс для предотвращения множественных добавлений + +**ПРАВИЛА ИНТЕГРАЦИИ С ДРУГИМИ БЛОКАМИ:** +- **Блок 1 (Поставщики)**: Слушает изменения selectedSupplier для обновления товаров +- **Блок 3 (Детальный каталог)**: Передаёт выбранные товары через setAllSelectedProducts +- **Блок 4 (Корзина)**: Товары добавляются в корзину из Блока 3, не напрямую из Блока 2 +- **Синхронизация состояний**: Реактивное обновление при изменении данных в любом блоке + +**ПРАВИЛА АНАЛИТИКИ И МЕТРИК:** +- **Отслеживание кликов**: Логирование добавления товаров в детальный каталог +- **Метрики производительности**: Время загрузки товаров поставщика +- **Пользовательское поведение**: Количество просмотренных товаров на поставщика +- **A/B тестирование**: Готовность к тестированию различных размеров карточек + +**ПРАВИЛА ЛОКАЛИЗАЦИИ:** +- **Alt-текст изображений**: На языке интерфейса пользователя +- **Направление скролла**: RTL поддержка для арабского/иврита +- **Размеры карточек**: Неизменны для всех локалей (80×112px) +- **Сообщения об ошибках**: Локализованные уведомления при проблемах загрузки + +#### **9.2.1.1 Заголовок и поиск Блока 1** + +**МИНИМАЛИСТИЧНЫЙ ДИЗАЙН:** +```jsx +
+
+ +

Поставщики

+
+
+
+ + +
+
+
+``` + +**ПРАВИЛА ЗАГОЛОВКА:** +- **Иконка**: Building2 h-5 w-5 text-blue-400 (без фонового контейнера) +- **Текст**: "Поставщики" (убран избыточный "товаров") +- **Размер**: text-lg font-semibold (увеличен для лучшей читаемости) +- **БЕЗ бэджа**: Убран избыточный бэдж "Создание поставки" +- **Выравнивание**: flex items-center gap-2 (компактное) + +**ПРАВИЛА ПОИСКА:** +- **Позиция**: Справа от заголовка (justify-between) +- **Ширина**: w-64 (256px) фиксированная ширина +- **Плейсхолдер**: "Поиск поставщиков..." (конкретное описание) +- **Иконка**: Search h-4 w-4 слева в поле +- **Стили**: Стандартные glass-эффекты, focus:border-white/20 + +**ПРАВИЛА КНОПКИ "НАЙТИ В МАРКЕТЕ":** +- **Условие**: Показывается только при allCounterparties.length === 0 +- **Позиция**: Отдельный блок под заголовком (mt-4) +- **НЕ интегрирована**: В поле поиска (отдельно) +- **Стили**: glass-secondary outline button размера sm + +#### **9.2.1.2 Структура карточки поставщика в Блоке 1** + +**МИНИМАЛИСТИЧНАЯ КАРТОЧКА ПОСТАВЩИКА:** + +**СТРУКТУРА ИНФОРМАЦИИ:** +```jsx +
+ +
+

+ {supplier.name || supplier.fullName} +

+
+

ИНН: {supplier.inn}

+ {supplier.market && ( + + {getMarketLabel(supplier.market)} + + )} +
+
+
+``` + +**ПРАВИЛА СОДЕРЖАНИЯ КАРТОЧКИ:** + +**✅ ОСТАВИТЬ:** +- **Аватар организации**: OrganizationAvatar size="sm" слева +- **Название поставщика**: supplier.name || supplier.fullName (приоритет name) +- **ИНН**: font-mono, text-white/60, с префиксом "ИНН: " + +**🔸 ДОБАВИТЬ:** +- **Принадлежность к рынку**: Badge с названием рынка из supplier.market +- **Рынки**: "Садовод", "ТЯК Москва" и другие из Organization.market поля + +**❌ УБРАТЬ:** +- **Рейтинг**: Звездочка и цифра rating (избыточно) +- **Тип бэдж**: "Поставщик" badge (и так понятно из контекста) +- **Адрес**: supplier.address (занимает место, не критично) + +**СТИЛИ РЫНОЧНЫХ БЭДЖЕЙ:** +- **Садовод**: bg-green-500/20 text-green-300 border-green-500/30 +- **ТЯК Москва**: bg-blue-500/20 text-blue-300 border-blue-500/30 +- **По умолчанию**: bg-gray-500/20 text-gray-300 border-gray-500/30 + +**ПРАВИЛА АДАПТИВНОСТИ:** +- **Мобильные**: Сохранение структуры, truncate для длинных названий +- **Планшеты/десктоп**: Полное отображение в сетке +- **Малые экраны**: line-clamp-1 для названия организации + +**СОСТОЯНИЯ КАРТОЧКИ:** +- **Default**: bg-white/5 border-white/10 +- **Hover**: hover:border-white/20 hover:bg-white/10 +- **Selected**: bg-white/15 border-white/40 shadow-lg +- **Disabled**: opacity-50 cursor-not-allowed (при недоступности) + +**ПРАВИЛА ИНТЕГРАЦИИ С РЫНКАМИ:** + +**ИСТОЧНИК ДАННЫХ:** +- **Поле БД**: Organization.market (String?) - поле принадлежности к рынку +- **Настройка**: Указывается в настройках кабинета поставщика +- **Опциональность**: Поле может быть пустым (рынок не указан) + +**ФУНКЦИЯ getMarketLabel():** +```jsx +const getMarketLabel = (market?: string) => { + const marketLabels = { + 'sadovod': 'Садовод', + 'tyak-moscow': 'ТЯК Москва', + 'opt-market': 'ОПТ Маркет', + } + return marketLabels[market as keyof typeof marketLabels] || market +} +``` + +**СТИЛИ ДЛЯ РЫНКОВ:** +```jsx +const getMarketBadgeStyle = (market?: string) => { + const styles = { + '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', + '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' +} +``` + +**ПРАВИЛА ОТОБРАЖЕНИЯ:** +- **Условие**: Показывать badge только если supplier.market существует +- **Размер**: text-xs для соответствия ИНН +- **Позиция**: Справа от ИНН в той же строке +- **Приоритет**: Рынок важнее типа организации для селлера + +### 9.2.2.2 ПРАВИЛО ПЕРСИСТЕНТНОСТИ ВЫБРАННЫХ ТОВАРОВ **🎯 ОСНОВНОЙ ПРИНЦИП:** Выбранные товары в детальном каталоге (блок 3) сохраняются при смене поставщика и могут быть удалены только явным действием пользователя. @@ -1194,88 +1474,6 @@ const handleSuppliesClick = () => { - Визуальная индикация выбранных товаров в блоке 2 - Информация о поставщике для каждого товара в блоке 3 -### 9.2.2.2 ПРАВИЛО ВСПЛЫВАЮЩЕЙ ПОДСКАЗКИ ТОВАРА - -**🎯 ОСНОВНОЙ ПРИНЦИП:** -При наведении курсора на компактную карточку товара в блоке 2 появляется динамическое модальное окно с полной информацией о товаре. - -**📱 АДАПТИВНОСТЬ:** -- Планшеты: показывать по долгому нажатию (500ms), работает как desktop -- Desktop: стандартное поведение hover (300ms задержка) -- Мобильные: не показывать (< 768px) - -**🎨 ДИЗАЙН И РАЗМЕРЫ:** -- Ширина: 220px фиксированная -- Высота: фиксированная (не изменяется) -- Фон: `bg-white/10 backdrop-blur-xl` (подстраивается под блок) -- Граница: `border border-white/20`, скругления: `rounded-xl` -- Тень: `shadow-2xl` - -**📊 СТРУКТУРА КОНТЕНТА (ПРИОРИТЕТ):** - -**ЗАГОЛОВОК (КРУПНО):** -- Название товара: `text-lg font-semibold`, truncate -- Цена: `text-lg font-bold` "₽ 2,500 за шт" - -**ГРУППЫ ИНФОРМАЦИИ:** -1. **ОСНОВНОЕ**: Остатки (с цветовой индикацией) + Категория (badge) -2. **ХАРАКТЕРИСТИКИ** (если есть): Цвет, размеры, объемы, комплектность -3. **СЛУЖЕБНОЕ**: Артикул формата `SP-ABC123456` - -**📊 ИСТОЧНИК ДАННЫХ:** -- Только из карточки товара `GoodsProduct` - никаких дополнительных запросов -- Если данных нет - не показываем этот пункт -- Показываем только то, что есть - -**🖱️ ИНТЕРАКТИВНОСТЬ:** -- Read-only - никакого взаимодействия внутри подсказки -- Клик на карточку добавляет товар (как обычно) -- Подсказка не блокирует основное взаимодействие - -**📐 ПОЗИЦИОНИРОВАНИЕ:** -- Умное позиционирование по наибольшему свободному месту -- Приоритет: справа → слева → сверху → снизу -- Частично видимые карточки: все равно показывать подсказку -- Отступы от краев экрана: минимум 16px - -**🚨 ОБРАБОТКА ОШИБОК:** -- При ошибках загрузки: не показывать подсказку вообще -- Без изображения: показывать данные как обычно -- Длинные названия: truncate, размеры модалки НЕ изменять - -**⚡ ПРОИЗВОДИТЕЛЬНОСТЬ:** -- Debounce: 300ms задержка перед показом -- Throttle: позиционирование при скролле/ресайзе -- React.memo для оптимизации рендера - -**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:** -```css -.products-cards-container { - height: 160px; /* Общая высота блока */ - flex-shrink: 0; - min-width: 0; /* Предотвращает растяжение */ -} - -.products-cards-block { - display: flex; - overflow-x: auto; - scroll-behavior: smooth; - gap: 10px; - padding: 0 12px 6px 12px; /* px-3 pb-1.5 */ - height: 120px; /* h-30 */ - scrollbar-width: thin; - scrollbar-color: #64748b33 transparent; -} - -.product-card { - flex-shrink: 0; - width: 180px; /* Десктоп */ - height: 88px; /* Фиксированная высота */ - padding: 6px; /* p-1.5 */ - transition: all 0.2s ease; -} -``` - **СОСТОЯНИЯ БЛОКА:** - **Не выбран поставщик**: Заглушка "Выберите поставщика для просмотра товаров" - **Поставщик выбран, нет товаров**: "У поставщика нет товаров" @@ -1284,7 +1482,7 @@ const handleSuppliesClick = () => { **ВЗАИМОДЕЙСТВИЕ:** - **Навигация**: Горизонтальная прокрутка мышью, клавишами ←→ - **Выбор**: Клик → добавление в Блок 3 с анимацией -- **Состояния карточек**: Default, Hover, Active (при добавлении) +- **Состояния карточек**: Default, Selected, Active (при добавлении) **ГРАНИЧНЫЕ СЛУЧАИ:** - **1-5 карточек**: Скролл неактивен, выравнивание по левому краю @@ -1539,9 +1737,10 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins) - Горизонтальный скролл при превышении ширины - Выбор только одного поставщика одновременно -**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(160px - НОВЫЙ БЛОК)_: -- Компактные карточки товаров выбранного поставщика -- Горизонтальный скролл аналогично блоку 1 +**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(адаптивная высота - НОВЫЙ БЛОК)_: +- ТОЛЬКО минималистичные карточки товаров 80×112px +- ТОЛЬКО изображение товара, БЕЗ текста/названий/цен +- Горизонтальный скролл при множестве товаров - Клик добавляет товар в блок 3 **БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(flex-1, детальный каталог)_: @@ -1596,14 +1795,67 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins) ## 10. 🏪 КАБИНЕТ ПОСТАВЩИКА -### 10.1 Основные возможности +### 10.1 Разделение понятий: РЫНОК vs МАРКЕТ + +**🔍 КРИТИЧЕСКОЕ РАЗДЕЛЕНИЕ ПОНЯТИЙ:** + +### **РЫНОК** 🏪 - физическое торговое место +- **Назначение**: Географическая принадлежность поставщиков +- **Примеры**: Садовод, ТЯК Москва +- **Структура**: Название + адрес +- **Связь**: Поставщик принадлежит рынку + +### **МАРКЕТ** 🛒 - раздел системы для торговли +- **Назначение**: Глобальный каталог товаров в системе +- **Роут**: `/market` - просмотр и заказ товаров +- **Содержание**: Все доступные товары от всех поставщиков +- **Связь**: НЕ связан с физическими рынками + +**🏢 АРХИТЕКТУРА ПРИНАДЛЕЖНОСТИ:** + +``` +РЫНОК (физическое место) + └── Поставщик (Organization.market) + └── Товары/Расходники (наследуют рынок от поставщика) + └── Отображаются в МАРКЕТЕ (/market) +``` + +**🎯 ПРИНЦИПЫ ИЕРАРХИИ:** + +1. **РЫНОК → ПОСТАВЩИК**: Поставщик работает на конкретном рынке +2. **ПОСТАВЩИК → ТОВАРЫ**: Товары принадлежат поставщику с его рынка +3. **ТОВАРЫ → МАРКЕТ**: Все товары показываются в глобальном маркете (/market) +4. **НАСЛЕДОВАНИЕ**: Товары получают рынок от организации поставщика + +**🏪 ФИЗИЧЕСКИЕ РЫНКИ В СИСТЕМЕ:** + +- **"Садовод"** (`sadovod`) - Москва, 14-й км МКАД + - **Цветовая схема**: `bg-green-500/20 text-green-300 border-green-500/30` +- **"ТЯК Москва"** (`tyak-moscow`) - Москва, Алтуфьевское шоссе, 27 + - **Цветовая схема**: `bg-blue-500/20 text-blue-300 border-blue-500/30` + +**🛒 МАРКЕТ В СИСТЕМЕ:** + +- **Роут**: `/market` - глобальный каталог товаров +- **Функции**: Просмотр, поиск, фильтрация, заказ товаров +- **Источник**: Товары от всех поставщиков всех рынков +- **Отображение рынка**: В карточках поставщиков и товаров + +**🔧 ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:** + +- **Поле рынка**: `Organization.market` (String?) - принадлежность поставщика к рынку +- **Настройка рынка**: В настройках организации поставщика +- **Отображение в маркете**: Товары показывают рынок через `product.organization.market` +- **Фильтрация**: В маркете по рынку поставщика + +### 10.2 Основные возможности **СОЗДАНИЕ КАРТОЧЕК**: - **ТОВАР** - базовые товары поставщика - **РАСХОДНИКИ** - материалы и вспомогательные товары -### 10.2 Обязательные поля карточки +### 10.3 Обязательные поля карточки **Базовые параметры**: @@ -1618,7 +1870,7 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins) - Цена за единицу и за комплект - Заказано, В пути, Остаток, Продано -### 10.3 Отображение информации в карточках +### 10.4 Отображение информации в карточках **Каждая карточка содержит**: @@ -1629,7 +1881,7 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins) - Данные о движении: остаток, заказано, в пути, продано - Индикаторы низких остатков -### 10.4 Статистика поставщика +### 10.5 Статистика поставщика **Блок статистики включает**: @@ -1773,29 +2025,96 @@ height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins) ### 11.7 Услуги фулфилмента (`/services`) -#### 11.7.1 Структура: 3 обязательные вкладки +#### 11.7.1 Архитектура интеграции с системой + +**СВЯЗЬ С РЕЦЕПТУРАМИ СЕЛЛЕРОВ:** +``` +СЕЛЛЕР (создание поставки) + └── Рецептура + ├── Товар (от поставщика) + ├── Услуги фулфилмента ← CRUD в разделе Услуги + ├── Расходники селлера + └── Расходники фулфилмента ← ТОЛЬКО с установленной ценой + ↓ +ФУЛФИЛМЕНТ (обработка) + ├── Входящие поставки → Поставки расходников (создание) + └── Услуги → Расходники (установка цены за единицу) +``` + +#### 11.7.2 Структура: 3 обязательные вкладки **A) 🛠️ УСЛУГИ** (`defaultValue="services"`): -- **CRUD операции**: создание, редактирование, удаление услуг -- **Управление ценами** и описаниями -- **Загрузка изображений** услуг (`imageUrl`) +- **Полный CRUD**: создание, редактирование, удаление услуг +- **Поля**: `name`, `description`, `price`, `imageUrl` +- **Glass Upload Zone**: элегантная загрузка изображений +- **Назначение**: каталог услуг для рецептур селлеров - **GraphQL**: `GET_MY_SERVICES`, `CREATE_SERVICE`, `UPDATE_SERVICE`, `DELETE_SERVICE` **B) 🚚 ЛОГИСТИКА**: -- **Создание маршрутов доставки** (откуда → куда) -- **Тарификация**: цена до 1м³ и свыше 1м³ +- **Полный CRUD**: маршруты доставки +- **Поля**: откуда → куда, тарификация до/свыше 1м³ - **Группированные локации**: - Мой фулфилмент (название организации) - Рынки (предустановленные) - Склады Wildberries - Склады Ozon +- **Glass Upload Zone**: для изображений маршрутов - **GraphQL**: `GET_MY_LOGISTICS`, `CREATE_LOGISTICS`, `UPDATE_LOGISTICS`, `DELETE_LOGISTICS` -**C) 📦 РАСХОДНИКИ**: -- **Управление расходниками фулфилмента** -- **Интеграция с модулем "Услуги"** - селлеры могут использовать в услугах -- **Списание со складских остатков** при использовании -- **Стоимость включается** в стоимость услуги +**C) 📦 РАСХОДНИКИ** (**❌ БЕЗ СОЗДАНИЯ**): +- **ТОЛЬКО ПРОСМОТР** расходников с фулфилмент-склада +- **ЕДИНСТВЕННОЕ РЕДАКТИРУЕМОЕ ПОЛЕ**: `pricePerUnit` - цена за единицу для рецептур +- **UI подсветка**: "Цена за 1 {unit}" (например, "Цена за 1 шт") +- **Автоматическая синхронизация** при приеме поставки расходников +- **Glass Upload Zone**: для обновления изображений расходников +- **GraphQL**: `GET_MY_SUPPLIES` (read-only), `UPDATE_SUPPLY_PRICE` + +#### 11.7.3 Workflow расходников + +**ШАГ 1 - СОЗДАНИЕ**: Только через "Входящие поставки → Поставки расходников фулфилмента" + +**ШАГ 2 - СИНХРОНИЗАЦИЯ**: При приеме на склад → автоматически в Услуги/Расходники + +**ШАГ 3 - ЦЕНООБРАЗОВАНИЕ**: Установка цены за единицу в разделе Услуги + +**ШАГ 4 - ИСПОЛЬЗОВАНИЕ**: Доступны в рецептурах селлеров + +#### 11.7.4 Правила видимости в рецептурах + +**В РЕЦЕПТУРАХ СЕЛЛЕРОВ ПОКАЗЫВАЮТСЯ ТОЛЬКО:** +- `isAvailable = true` (есть на skladе) +- `pricePerUnit != null` (цена установлена) + +**НЕ ПОКАЗЫВАЮТСЯ:** +- Расходники без цены (`pricePerUnit = null`) +- Удаленные со склада (`isAvailable = false`) + +**В РАЗДЕЛЕ УСЛУГИ/РАСХОДНИКИ ВИДНЫ ВСЕ:** +- С визуальной индикацией состояния (активные/неактивные/без цены) + +#### 11.7.5 Технические требования + +**GraphQL типы:** +```graphql +# Для рецептур - только доступные с ценой +getAvailableSuppliesForRecipe: [SupplyForRecipe!]! + +# В разделе Услуги - все расходники +getMySupplies: [Supply!]! + +type Supply { + pricePerUnit: Float # Может быть null + unit: String! # "шт", "кг", "м" + isAvailable: Boolean! # Статус на складе + warehouseConsumableId: ID! # Связь со складом +} +``` + +**Экономический учет:** +- Создание: через поставки расходников +- Ценообразование: в разделе Услуги +- Списание: со склада при использовании +- Стоимость = количество × цена за единицу ### 11.8 Сотрудники фулфилмента (`/employees`) @@ -2206,6 +2525,8 @@ const wholesalePartners = await prisma.counterparty.findMany({ 19. ❌ **Показывать расходники в формах создания поставок товаров** (строгая типизация `PRODUCT`/`CONSUMABLE`) 20. ❌ **Фильтровать предметы по типу на фронтенде** (фильтрация должна быть в GraphQL резолвере) 21. ❌ **ИСПОЛЬЗОВАТЬ МОКОВЫЕ ДАННЫЕ БЕЗ РАЗРЕШЕНИЯ** - все компоненты ОБЯЗАТЕЛЬНО должны использовать реальные GraphQL запросы. Моковые данные можно добавлять ТОЛЬКО с явного разрешения пользователя +22. ❌ **ДОБАВЛЯТЬ ПОЛЕ РЫНКА К ТОВАРАМ** - рынок принадлежит организации поставщика (`Organization.market`), товары наследуют рынок через связь с организацией +23. ❌ **ПУТАТЬ РЫНОК И МАРКЕТ** - РЫНОК = физическое место (Садовод, ТЯК), МАРКЕТ = раздел системы (/market) ### 17.2 ОБЯЗАТЕЛЬНЫЕ ПРАВИЛА: @@ -2317,6 +2638,54 @@ interface GoodsProduct { - **Использовать параметризованные запросы** (`organizationId`, `type`, `search`) вместо фильтрации на фронтенде - **Добавлять подробное логирование** в резолверы для отладки (входные параметры, результаты фильтрации) - **Типы запросов должны отражать бизнес-логику**: `organizationProducts` для товаров конкретной организации + +### 18.7 Правила РЫНКОВ и МАРКЕТА + +**🔍 КРИТИЧЕСКОЕ РАЗДЕЛЕНИЕ:** + +- **РЫНОК** 🏪 = физическое место (Садовод, ТЯК) +- **МАРКЕТ** 🛒 = раздел системы `/market` + +**ПОЛЕ РЫНКА В SCHEMA:** + +- **Organization.market** ✅ - поставщик принадлежит физическому рынку +- **Product.market** ❌ - ЗАПРЕЩЕНО, товары наследуют рынок от организации +- **Отображение рынка товаров**: через `product.organization.market` +- **Фильтрация по рынкам**: через `organization.market`, НЕ через `product.market` + +**ЗАПРОСЫ С РЫНКАМИ:** + +```graphql +# ✅ ПРАВИЛЬНО - рынок от организации поставщика +query GetProductsWithMarket { + myProducts { + id + name + organization { + market # Физический рынок поставщика + } + } +} + +# ✅ ПРАВИЛЬНО - товары в маркете с информацией о рынке +query GetMarketProducts { + marketProducts { + id + name + organization { + market # Рынок поставщика + name # Название поставщика + } + } +} +``` + +**МАРКЕТ (/market) ПРАВИЛА:** + +- **Назначение**: Глобальный каталог всех товаров +- **Фильтрация**: По рынкам поставщиков, типам товаров, категориям +- **Отображение**: Показать рынок поставщика в карточках товаров +- **НЕ путать**: МАРКЕТ ≠ конкретный физический рынок - **Значения по умолчанию в резолверах** для критических параметров (`type: args.type || "PRODUCT"`) - **Валидация обязательных параметров** на уровне схемы (`organizationId: ID!`) - **Кеширование обходить при проблемах** через `fetchPolicy: 'network-only'` diff --git a/src/app/api/graphql/route.ts b/src/app/api/graphql/route.ts index 9a04b79..e8ceb12 100644 --- a/src/app/api/graphql/route.ts +++ b/src/app/api/graphql/route.ts @@ -21,11 +21,7 @@ const handler = startServerAndCreateNextHandler(server, { const authHeader = req.headers.get('authorization') const token = authHeader?.replace('Bearer ', '') - console.warn('GraphQL Context - Auth header:', authHeader) - console.warn('GraphQL Context - Token:', token ? `${token.substring(0, 20)}...` : 'No token') - if (!token) { - console.warn('GraphQL Context - No token provided') return { user: null, admin: null, prisma } } @@ -46,10 +42,6 @@ const handler = startServerAndCreateNextHandler(server, { // Проверяем тип токена if (decoded.type === 'admin' && decoded.adminId && decoded.username) { - console.warn('GraphQL Context - Decoded admin:', { - id: decoded.adminId, - username: decoded.username, - }) return { admin: { id: decoded.adminId, @@ -59,10 +51,6 @@ const handler = startServerAndCreateNextHandler(server, { prisma, } } else if (decoded.userId && decoded.phone) { - console.warn('GraphQL Context - Decoded user:', { - id: decoded.userId, - phone: decoded.phone, - }) return { user: { id: decoded.userId, diff --git a/src/components/admin/ui-kit/interactive-demo.tsx b/src/components/admin/ui-kit/interactive-demo.tsx index 9b63695..cb4d449 100644 --- a/src/components/admin/ui-kit/interactive-demo.tsx +++ b/src/components/admin/ui-kit/interactive-demo.tsx @@ -1,8 +1,6 @@ 'use client' import { - ChevronDown, - ChevronUp, Plus, Minus, Star, @@ -63,7 +61,6 @@ export function InteractiveDemo() { const [counter, setCounter] = useState(5) const [showPassword, setShowPassword] = useState(false) const [notifications, setNotifications] = useState(true) - const [expandedCard, setExpandedCard] = useState(null) const [selectedItems, setSelectedItems] = useState([]) const [copied, setCopied] = useState(false) @@ -579,19 +576,16 @@ export function InteractiveDemo() { {/* Расширяемые элементы */} - Расширяемые элементы + Статичные элементы - {/* Expandable Cards */} + {/* Static Cards */}
-

Расширяемые карточки

+

Статичные карточки

{[1, 2, 3].map((card) => (
-
setExpandedCard(expandedCard === card ? null : card)} - > +
@@ -601,36 +595,10 @@ export function InteractiveDemo() {
Описание настройки {card}
- - {expandedCard === card ? ( - - ) : ( - - )} +
- - {expandedCard === card && ( -
-
-
- - -
-
- - -
-
- - -
-
-
- )}
))}
diff --git a/src/components/auth-guard.tsx b/src/components/auth-guard.tsx index da51f90..5303e6e 100644 --- a/src/components/auth-guard.tsx +++ b/src/components/auth-guard.tsx @@ -19,29 +19,17 @@ export function AuthGuard({ children, fallback }: AuthGuardProps) { useEffect(() => { const initAuth = async () => { if (initRef.current) { - console.warn('AuthGuard - Already initialized, skipping') return } initRef.current = true - console.warn('AuthGuard - Initializing auth check') await checkAuth() setIsChecking(false) - console.warn('AuthGuard - Auth check completed, authenticated:', isAuthenticated, 'user:', !!user) } initAuth() }, [checkAuth, isAuthenticated, user]) // Добавляем зависимости как требует линтер - // Дополнительное логирование состояний - useEffect(() => { - console.warn('AuthGuard - State update:', { - isChecking, - isLoading, - isAuthenticated, - hasUser: !!user, - }) - }, [isChecking, isLoading, isAuthenticated, user]) // Показываем лоадер пока проверяем авторизацию if (isChecking || isLoading) { @@ -57,11 +45,9 @@ export function AuthGuard({ children, fallback }: AuthGuardProps) { // Если не авторизован, показываем форму авторизации if (!isAuthenticated) { - console.warn('AuthGuard - User not authenticated, showing auth flow') return fallback || } // Если авторизован, показываем защищенный контент - console.warn('AuthGuard - User authenticated, showing dashboard') return <>{children} } diff --git a/src/components/auth/marketplace-api-step.tsx b/src/components/auth/marketplace-api-step.tsx index a8a234c..5a49a90 100644 --- a/src/components/auth/marketplace-api-step.tsx +++ b/src/components/auth/marketplace-api-step.tsx @@ -83,8 +83,6 @@ export function MarketplaceApiStep({ onNext, onBack }: MarketplaceApiStepProps) }, }) - console.warn(`🎯 Client received response for ${marketplace}:`, data) - setValidationStates((prev) => ({ ...prev, [marketplace]: { @@ -113,8 +111,7 @@ export function MarketplaceApiStep({ onNext, onBack }: MarketplaceApiStepProps) }) } } - } catch (error) { - console.warn(`🔴 Client validation error for ${marketplace}:`, error) + } catch { setValidationStates((prev) => ({ ...prev, [marketplace]: { diff --git a/src/components/auth/sms-step.tsx b/src/components/auth/sms-step.tsx index acf2d86..0c4e7d1 100644 --- a/src/components/auth/sms-step.tsx +++ b/src/components/auth/sms-step.tsx @@ -82,11 +82,8 @@ export function SmsStep({ phone, onNext, onBack }: SmsStepProps) { const result = await verifySmsCode(formattedPhone, fullCode) if (result.success) { - console.warn('SmsStep - SMS verification successful, user:', result.user) - // Проверяем есть ли у пользователя уже организация if (result.user?.organization) { - console.warn('SmsStep - User already has organization, redirecting to dashboard') // Если организация уже есть, перенаправляем прямо в кабинет window.location.href = '/dashboard' return diff --git a/src/components/dashboard/user-settings.tsx b/src/components/dashboard/user-settings.tsx index 0aa5481..dec2d80 100644 --- a/src/components/dashboard/user-settings.tsx +++ b/src/components/dashboard/user-settings.tsx @@ -31,6 +31,7 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { UPDATE_USER_PROFILE, UPDATE_ORGANIZATION_BY_INN } from '@/graphql/mutations' import { GET_ME } from '@/graphql/queries' @@ -87,6 +88,9 @@ export function UserSettings() { // API ключи маркетплейсов wildberriesApiKey: '', ozonApiKey: '', + + // Рынок для поставщиков + market: '', }) // Загружаем данные организации при монтировании компонента @@ -130,10 +134,13 @@ export function UserSettings() { } = {} try { if (org.managementPost && typeof org.managementPost === 'string') { - customContacts = JSON.parse(org.managementPost) + // Проверяем, что строка начинается с { или [, иначе это не JSON + if (org.managementPost.trim().startsWith('{') || org.managementPost.trim().startsWith('[')) { + customContacts = JSON.parse(org.managementPost) + } } - } catch (e) { - console.warn('Ошибка парсинга managementPost:', e) + } catch { + // Игнорируем ошибки парсинга } setFormData({ @@ -154,6 +161,7 @@ export function UserSettings() { corrAccount: customContacts?.bankDetails?.corrAccount || '', wildberriesApiKey: '', ozonApiKey: '', + market: org.market || 'none', }) } }, [user]) @@ -290,7 +298,6 @@ export function UserSettings() { }) // TODO: Сохранить партнерский код в базе данных - console.warn('Partner code generated:', partnerCode) } catch (error) { console.error('Error generating partner link:', error) setSaveMessage({ type: 'error', text: 'Ошибка при генерации ссылки' }) @@ -342,7 +349,7 @@ export function UserSettings() { avatar: avatarUrl, }, }, - update: (cache, { data }) => { + update: (cache, { data }: { data?: any }) => { if (data?.updateUserProfile?.success) { // Обновляем кеш Apollo Client try { @@ -358,8 +365,8 @@ export function UserSettings() { }, }) } - } catch (error) { - console.warn('Cache update error:', error) + } catch { + // Игнорируем ошибки обновления кеша } } }, @@ -518,6 +525,7 @@ export function UserSettings() { const handleInputChange = (field: string, value: string) => { let processedValue = value + // Применяем маски и валидации switch (field) { case 'orgPhone': @@ -582,6 +590,59 @@ export function UserSettings() { } } + // Проверка наличия изменений в форме + const hasFormChanges = () => { + if (!user?.organization) return false + + const org = user.organization + + // Извлекаем текущий телефон из organization.phones + let currentOrgPhone = '+7' + if (org.phones && Array.isArray(org.phones) && org.phones.length > 0) { + currentOrgPhone = org.phones[0].value || org.phones[0] || '+7' + } + + // Извлекаем текущий email из organization.emails + let currentEmail = '' + if (org.emails && Array.isArray(org.emails) && org.emails.length > 0) { + currentEmail = org.emails[0].value || org.emails[0] || '' + } + + // Извлекаем дополнительные данные из managementPost + let customContacts: any = {} + try { + if (org.managementPost && typeof org.managementPost === 'string') { + // Проверяем, что строка начинается с { или [, иначе это не JSON + if (org.managementPost.trim().startsWith('{') || org.managementPost.trim().startsWith('[')) { + customContacts = JSON.parse(org.managementPost) + } + } + } catch { + // ignore parse errors + } + + // Нормализуем значения для сравнения + const normalizeValue = (value: string | null | undefined) => value || '' + const normalizeMarketValue = (value: string | null | undefined) => value || 'none' + + // Проверяем изменения в полях + const changes = [ + normalizeValue(formData.orgPhone) !== normalizeValue(currentOrgPhone), + normalizeValue(formData.managerName) !== normalizeValue(user?.managerName), + normalizeValue(formData.telegram) !== normalizeValue(customContacts?.telegram), + normalizeValue(formData.whatsapp) !== normalizeValue(customContacts?.whatsapp), + normalizeValue(formData.email) !== normalizeValue(currentEmail), + normalizeMarketValue(formData.market) !== normalizeMarketValue(org.market), + normalizeValue(formData.bankName) !== normalizeValue(customContacts?.bankDetails?.bankName), + normalizeValue(formData.bik) !== normalizeValue(customContacts?.bankDetails?.bik), + normalizeValue(formData.accountNumber) !== normalizeValue(customContacts?.bankDetails?.accountNumber), + normalizeValue(formData.corrAccount) !== normalizeValue(customContacts?.bankDetails?.corrAccount), + ] + + const hasChanges = changes.some(changed => changed) + return hasChanges + } + // Проверка наличия ошибок валидации const hasValidationErrors = () => { const fields = [ @@ -658,6 +719,7 @@ export function UserSettings() { bik?: string accountNumber?: string corrAccount?: string + market?: string } = {} // orgName больше не редактируется - устанавливается только при регистрации @@ -670,6 +732,7 @@ export function UserSettings() { if (formData.bik?.trim()) inputData.bik = formData.bik.trim() if (formData.accountNumber?.trim()) inputData.accountNumber = formData.accountNumber.trim() if (formData.corrAccount?.trim()) inputData.corrAccount = formData.corrAccount.trim() + if (formData.market) inputData.market = formData.market const result = await updateUserProfile({ variables: { @@ -715,7 +778,6 @@ export function UserSettings() { } if (isNaN(date.getTime())) { - console.warn('Invalid date string:', dateString) return 'Неверная дата' } @@ -724,8 +786,7 @@ export function UserSettings() { month: 'long', day: 'numeric', }) - } catch (error) { - console.error('Error formatting date:', error, dateString) + } catch { return 'Ошибка даты' } } @@ -833,9 +894,9 @@ export function UserSettings() {
)} + + {/* Настройка рынка для поставщиков */} + {user?.organization?.type === 'WHOLESALE' && ( +
+
+ + {isEditing ? ( + + ) : ( + + )} +

+ Физический рынок, где работает поставщик. Товары наследуют рынок от организации. +

+
+
+ )}
@@ -1297,7 +1393,7 @@ export function UserSettings() { - {hasUnsavedChanges && ( )}
@@ -394,17 +274,10 @@ export function SuppliesTab() {
- +

Пока нет расходников

-

Создайте свой первый расходник, чтобы начать работу

- +

Расходники появятся автоматически при получении поставок

) : ( @@ -414,8 +287,10 @@ export function SuppliesTab() { № Фото - Название * - Цена за единицу (₽) * + Название + Остаток + Единица + Цена за единицу (₽) Описание Действия @@ -424,51 +299,17 @@ export function SuppliesTab() { {editableSupplies.map((supply, index) => ( {index + 1} {/* Фото */} - {supply.isEditing ? ( -
- { - const file = e.target.files?.[0] - if (file) { - updateField(supply.id!, 'imageFile', file) - } - }} - className="bg-white/5 border-white/20 text-white text-xs file:bg-gradient-to-r file:from-purple-500 file:to-pink-500 file:text-white file:border-0 file:rounded file:px-2 file:py-1 file:mr-2 file:text-xs flex-1" - /> - {supply.imageUrl && ( -
- Preview - {/* Увеличенная версия при hover */} -
-
- Preview -
-
-
-
- )} -
- ) : supply.imageUrl ? ( + {supply.imageUrl ? (
) : (
- +
)} {/* Название */} - {supply.isEditing ? ( - updateField(supply.id!, 'name', e.target.value)} - className="bg-white/5 border-white/20 text-white" - placeholder="Название расходника" - /> - ) : ( - {supply.name} - )} + {supply.name} + + + {/* Остаток на складе */} + +
+ + {supply.warehouseStock} + + {!supply.isAvailable && ( + + Нет в наличии + + )} +
+ + + {/* Единица измерения */} + + {supply.unit} - {/* Цена */} + {/* Цена за единицу */} {supply.isEditing ? ( updateField(supply.id!, 'price', e.target.value)} + value={supply.pricePerUnit} + onChange={(e) => updateField(supply.id, 'pricePerUnit', e.target.value)} className="bg-white/5 border-white/20 text-white" - placeholder="0.00" + placeholder="Не установлена" /> ) : ( - {supply.price ? parseFloat(supply.price).toLocaleString() : '0'} ₽ + {supply.pricePerUnit ? `${parseFloat(supply.pricePerUnit).toLocaleString()} ₽` : 'Не установлена'} )} {/* Описание */} - {supply.isEditing ? ( - updateField(supply.id!, 'description', e.target.value)} - className="bg-white/5 border-white/20 text-white" - placeholder="Описание расходника" - /> - ) : ( - {supply.description || '—'} - )} + {supply.description || '—'} {/* Действия */} @@ -556,14 +400,9 @@ export function SuppliesTab() { )} - - - - - - - - - Подтвердите удаление - - - Вы действительно хотите удалить расходник “{supply.name}”? Это действие - необратимо. - - - - - Отмена - - removeRow(supply.id!, supply.isNew)} - className="bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800 text-white border-0 shadow-lg shadow-red-500/25 hover:shadow-red-500/40 transition-all duration-300" - > - Удалить - - - -
diff --git a/src/components/supplies/create-consumables-supply-page.tsx b/src/components/supplies/create-consumables-supply-page.tsx index 593432d..94fe547 100644 --- a/src/components/supplies/create-consumables-supply-page.tsx +++ b/src/components/supplies/create-consumables-supply-page.tsx @@ -4,17 +4,12 @@ import { useQuery, useMutation } from '@apollo/client' import { ArrowLeft, Building2, - MapPin, - Phone, - Mail, - Star, Search, Package, Plus, Minus, ShoppingCart, Wrench, - Box, } from 'lucide-react' import Image from 'next/image' import { useRouter } from 'next/navigation' @@ -136,14 +131,6 @@ export function CreateConsumablesSupplyPage() { }).format(amount) } - const renderStars = (rating: number = 4.5) => { - return Array.from({ length: 5 }, (_, i) => ( - - )) - } const updateConsumableQuantity = (productId: string, quantity: number) => { const product = supplierProducts.find((p: ConsumableProduct) => p.id === productId) @@ -414,7 +401,7 @@ export function CreateConsumablesSupplyPage() { variant="ghost" size="sm" onClick={() => setSelectedSupplier(null)} - className="text-white/70 hover:text-white hover:bg-white/20 text-sm h-8 px-3 flex-shrink-0 rounded-full transition-all duration-300 hover:scale-105" + className="text-white/70 hover:text-white hover:bg-white/20 text-sm h-8 px-3 flex-shrink-0 rounded-full transition-all duration-300" > ✕ Сбросить @@ -442,7 +429,7 @@ export function CreateConsumablesSupplyPage() { {filteredSuppliers.slice(0, 7).map((supplier: ConsumableSupplier, index) => ( 7 && (
+{filteredSuppliers.length - 7}
@@ -578,7 +565,7 @@ export function CreateConsumablesSupplyPage() { alt={product.name} width={100} height={100} - className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300" + className="w-full h-full object-cover" /> ) : product.mainImage ? ( {product.name} ) : (
diff --git a/src/components/supplies/create-suppliers-supply-page.tsx b/src/components/supplies/create-suppliers-supply-page.tsx index 421ea65..438b764 100644 --- a/src/components/supplies/create-suppliers-supply-page.tsx +++ b/src/components/supplies/create-suppliers-supply-page.tsx @@ -4,7 +4,6 @@ import { useQuery, useMutation } from '@apollo/client' import { ArrowLeft, Building2, - Star, Search, Package, Plus, @@ -28,6 +27,9 @@ import { OrganizationAvatar } from '@/components/market/organization-avatar' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' +// ВРЕМЕННО ОТКЛЮЧЕНО: импорты для верхней панели - до исправления Apollo ошибки +// 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 { GET_MY_COUNTERPARTIES, @@ -55,6 +57,7 @@ interface GoodsSupplier { users?: Array<{ id: string; avatar?: string; managerName?: string }> createdAt: string rating?: number + market?: string // Принадлежность к рынку согласно rules-complete.md v10.0 } interface GoodsProduct { @@ -154,7 +157,7 @@ export function CreateSuppliersSupplyPage() { const [selectedSupplier, setSelectedSupplier] = useState(null) const [selectedGoods, setSelectedGoods] = useState([]) const [searchQuery, setSearchQuery] = useState('') - const [productSearchQuery, setProductSearchQuery] = useState('') + const [productSearchQuery] = useState('') // Обязательные поля согласно rules2.md 9.7.8 const [deliveryDate, setDeliveryDate] = useState('') @@ -180,30 +183,6 @@ export function CreateSuppliersSupplyPage() { (GoodsProduct & { selectedQuantity: number; supplierId: string; supplierName: string })[] >([]) - // Состояние для увеличения карточек согласно rules-complete.md 9.2.2.2 - const [expandedCard, setExpandedCard] = useState(null) - const [hoverTimeout, setHoverTimeout] = useState(null) - - // Функции для увеличения карточек при наведении - const handleCardMouseEnter = (productId: string) => { - if (hoverTimeout) { - clearTimeout(hoverTimeout) - } - - const timeout = setTimeout(() => { - setExpandedCard(productId) - }, 2000) // 2 секунды согласно правилам - - setHoverTimeout(timeout) - } - - const handleCardMouseLeave = () => { - if (hoverTimeout) { - clearTimeout(hoverTimeout) - setHoverTimeout(null) - } - setExpandedCard(null) - } // Загружаем партнеров-поставщиков согласно rules2.md 13.3 const { @@ -362,6 +341,25 @@ export function CreateSuppliersSupplyPage() { }) // Моковые логистические компании согласно rules2.md 9.7.7 + // Функции для работы с рынками согласно rules-complete.md v10.0 + const getMarketLabel = (market?: string) => { + const marketLabels = { + 'sadovod': 'Садовод', + 'tyak-moscow': 'ТЯК Москва', + 'opt-market': 'ОПТ Маркет', + } + return marketLabels[market as keyof typeof marketLabels] || market + } + + const getMarketBadgeStyle = (market?: string) => { + const styles = { + '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', + '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' + } + const logisticsCompanies: LogisticsCompany[] = [ { id: 'express', name: 'Экспресс доставка', estimatedCost: 2500, deliveryDays: 1, type: 'EXPRESS' }, { id: 'standard', name: 'Стандартная доставка', estimatedCost: 1200, deliveryDays: 3, type: 'STANDARD' }, @@ -387,11 +385,7 @@ export function CreateSuppliersSupplyPage() { })) } - const updateProductQuantity = (productId: string, delta: number): void => { - const currentQuantity = getProductQuantity(productId) - const newQuantity = currentQuantity + delta - setProductQuantity(productId, newQuantity) - } + // Removed unused updateProductQuantity function // Добавление товара в корзину из карточки с заданным количеством const addToCart = (product: GoodsProduct) => { @@ -446,11 +440,7 @@ export function CreateSuppliersSupplyPage() { setProductQuantity(product.id, 0) } - // Открытие модального окна для детального добавления - const openAddModal = (product: GoodsProduct) => { - setSelectedProductForModal(product) - setIsModalOpen(true) - } + // Removed unused openAddModal function // Функции для работы с рецептурой const initializeProductRecipe = (productId: string) => { @@ -707,40 +697,37 @@ export function CreateSuppliersSupplyPage() { Назад
-
- -
-
-

Поставщики товаров

- - Создание поставки - -
+ +

Поставщики

-
+
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" />
- {allCounterparties.length === 0 && ( - - )}
+ {/* Кнопка поиска в маркете */} + {allCounterparties.length === 0 && ( +
+ +
+ )} + {/* Список поставщиков согласно visual-design-rules.md */}
{isLoading ? ( @@ -793,32 +780,17 @@ export function CreateSuppliersSupplyPage() {
-
-

- {supplier.name || supplier.fullName} -

- {supplier.rating && ( -
- - {supplier.rating} -
+

+ {supplier.name || supplier.fullName} +

+
+

ИНН: {supplier.inn}

+ {supplier.market && ( + + {getMarketLabel(supplier.market)} + )}
-
-

ИНН: {supplier.inn}

- - {supplier.type === 'WHOLESALE' ? 'Поставщик' : supplier.type} - -
- {supplier.address && ( -

{supplier.address}

- )}
@@ -829,158 +801,118 @@ export function CreateSuppliersSupplyPage() { - {/* БЛОК 2: КАРТОЧКИ ТОВАРОВ - новый блок согласно rules-complete.md 9.2.2 */} -
-
-
-
-
- -
-
-

- {selectedSupplier - ? `Товары ${selectedSupplier.name || selectedSupplier.fullName}` - : 'Карточки товаров'} -

-

Компактные карточки для быстрого выбора

-
+ {/* БЛОК 2: КАРТОЧКИ ТОВАРОВ */} +
+
+ {selectedSupplier && products.length > 0 && products.map((product: GoodsProduct) => { + return ( +
{ + // Добавляем товар в детальный каталог (блок 3) + if (!allSelectedProducts.find((p) => p.id === product.id)) { + setAllSelectedProducts((prev) => [ + ...prev, + { + ...product, + selectedQuantity: 1, + supplierId: selectedSupplier.id, + supplierName: selectedSupplier.name || selectedSupplier.fullName || 'Поставщик', + }, + ]) + } + }} + > + {product.mainImage ? ( + {product.name} + ) : ( +
+ +
+ )}
-
-
- -
- {!selectedSupplier ? ( -
-
- -

Выберите поставщика

-
-
- ) : products.length === 0 ? ( -
-
- -

Нет товаров

-
-
- ) : ( -
- {products.map((product: GoodsProduct) => { - const isExpanded = expandedCard === product.id - return ( -
handleCardMouseEnter(product.id)} - onMouseLeave={handleCardMouseLeave} - onClick={() => { - // Добавляем товар в детальный каталог (блок 3) - if (!allSelectedProducts.find((p) => p.id === product.id)) { - setAllSelectedProducts((prev) => [ - ...prev, - { - ...product, - selectedQuantity: 1, - supplierId: selectedSupplier.id, - supplierName: selectedSupplier.name || selectedSupplier.fullName || 'Поставщик', - }, - ]) - } - }} - > - {isExpanded ? ( -
- {product.mainImage ? ( - {product.name} - ) : ( -
- -
- )} -
-

{product.name}

-

- {product.price.toLocaleString('ru-RU')} ₽ -

- {product.category &&

{product.category.name}

} - {product.quantity !== undefined && ( -

Доступно: {product.quantity}

- )} -

Артикул: {product.article}

-
-
- ) : ( - <> - {product.mainImage ? ( - {product.name} - ) : ( -
- -
- )} - - )} -
- ) - })} -
- )} + ) + })}
- {/* БЛОК 3: ТОВАРЫ ПОСТАВЩИКА - детальный каталог согласно rules-complete.md 9.2 */} + {/* БЛОК 3: ТОВАРЫ ПОСТАВЩИКА - детальный каталог согласно rules-complete.md 9.2.3 */}
-
-
-
-
- -
-
-

- Детальный каталог ({allSelectedProducts.length} товаров) -

-

Товары из блока карточек для детального управления

-
+ {/* ВРЕМЕННО ОТКЛЮЧЕНО: Верхняя панель согласно правилам 9.2.3.1 - до исправления Apollo ошибки + {!counterpartiesLoading && ( +
+ + +
+ + setProductSearchQuery(e.target.value)} + className="pl-10 glass-input" + /> +
+
+ )} + + {counterpartiesLoading && ( +
+
Загрузка партнеров...
+
+ )} + + {counterpartiesError && ( +
+
+ Ошибка загрузки партнеров: {counterpartiesError.message} +
+
+ )} + */} + + {/* Заголовок каталога */} +
+
+
+ +
+
+

+ Детальный каталог ({allSelectedProducts.length} товаров) +

+

Товары для детального управления поставкой

- {selectedSupplier && ( -
-
- - setProductSearchQuery(e.target.value)} - className="glass-input text-white placeholder:text-white/50 pl-10 h-10 transition-all duration-200 focus-visible:ring-ring/50" - /> -
-
- )}
@@ -994,7 +926,7 @@ export function CreateSuppliersSupplyPage() {

Детальный каталог пуст

- Добавьте товары из блока карточек выше для детального управления + Добавьте товары

@@ -1292,7 +1224,6 @@ export function CreateSuppliersSupplyPage() { const quantity = getProductQuantity(product.id) const recipeCost = calculateRecipeCost(product.id) const productTotal = product.price * quantity - const totalRecipePrice = productTotal + recipeCost.total return ( <> diff --git a/src/components/supplies/create-supply-form.tsx b/src/components/supplies/create-supply-form.tsx index 71c3a9b..213e683 100644 --- a/src/components/supplies/create-supply-form.tsx +++ b/src/components/supplies/create-supply-form.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' -import { SelectedCard, WildberriesCard } from '@/types/supplies' +import { SelectedCard } from '@/types/supplies' import { WBProductCards } from './wb-product-cards' @@ -74,7 +74,6 @@ const mockWholesalers: Wholesaler[] = [ export function CreateSupplyForm({ onClose, onSupplyCreated }: CreateSupplyFormProps) { const [selectedVariant, setSelectedVariant] = useState<'cards' | 'wholesaler' | null>(null) const [selectedWholesaler, setSelectedWholesaler] = useState(null) - const [selectedCards, setSelectedCards] = useState([]) const renderStars = (rating: number) => { return Array.from({ length: 5 }, (_, i) => ( @@ -86,7 +85,6 @@ export function CreateSupplyForm({ onClose, onSupplyCreated }: CreateSupplyFormP } const handleCardsComplete = (cards: SelectedCard[]) => { - setSelectedCards(cards) console.warn('Карточки товаров выбраны:', cards) // TODO: Здесь будет создание поставки с данными карточек onSupplyCreated() @@ -164,7 +162,7 @@ export function CreateSupplyForm({ onClose, onSupplyCreated }: CreateSupplyFormP {mockWholesalers.map((wholesaler) => ( setSelectedWholesaler(wholesaler)} >
diff --git a/src/components/supplies/create-supply-page.tsx b/src/components/supplies/create-supply-page.tsx index fd80d24..3735ca6 100644 --- a/src/components/supplies/create-supply-page.tsx +++ b/src/components/supplies/create-supply-page.tsx @@ -51,7 +51,7 @@ const CreateSupplyPage = React.memo(() => { const fulfillmentOrgs = useMemo(() => (counterpartiesData?.myCounterparties || []).filter( (org: Organization) => org.type === 'FULFILLMENT', - ), [counterpartiesData?.myCounterparties] + ), [counterpartiesData?.myCounterparties], ) const formatCurrency = useCallback((amount: number) => { diff --git a/src/components/supplies/product-card.tsx b/src/components/supplies/product-card.tsx index e2153bd..c470ea6 100644 --- a/src/components/supplies/product-card.tsx +++ b/src/components/supplies/product-card.tsx @@ -25,12 +25,12 @@ export function ProductCard({ product, selectedQuantity, onQuantityChange, forma } return ( - +
{product.name} {/* Количество в наличии */} diff --git a/src/components/supplies/supplier-card.tsx b/src/components/supplies/supplier-card.tsx index 93c1d12..0106143 100644 --- a/src/components/supplies/supplier-card.tsx +++ b/src/components/supplies/supplier-card.tsx @@ -16,7 +16,7 @@ interface SupplierCardProps { export function SupplierCard({ supplier, onClick }: SupplierCardProps) { return (
diff --git a/src/components/supplies/wb-product-cards.tsx b/src/components/supplies/wb-product-cards.tsx index 4c2cc71..9143e27 100644 --- a/src/components/supplies/wb-product-cards.tsx +++ b/src/components/supplies/wb-product-cards.tsx @@ -1,7 +1,24 @@ 'use client' +import { useQuery, useMutation } from '@apollo/client' +import { format } from 'date-fns' +import { ru } from 'date-fns/locale' +import { + Search, + Plus, + Minus, + ShoppingCart, + Calendar as CalendarIcon, + Package, + ArrowLeft, + Check, + Eye, + ChevronLeft, + ChevronRight, +} from 'lucide-react' import React, { useState, useEffect } from 'react' import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' import { toast } from 'sonner' import { Sidebar } from '@/components/dashboard/sidebar' @@ -10,43 +27,16 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { ProductCardSkeletonGrid } from '@/components/ui/product-card-skeleton' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' - -import 'react-datepicker/dist/react-datepicker.css' import { CREATE_WILDBERRIES_SUPPLY } from '@/graphql/mutations' import { GET_MY_COUNTERPARTIES, GET_COUNTERPARTY_SERVICES, GET_COUNTERPARTY_SUPPLIES } from '@/graphql/queries' import { useAuth } from '@/hooks/useAuth' import { useSidebar } from '@/hooks/useSidebar' - -import { - Search, - Plus, - Minus, - ShoppingCart, - Calendar as CalendarIcon, - Phone, - User, - MapPin, - Package, - Wrench, - ArrowLeft, - Check, - Eye, - ChevronLeft, - ChevronRight, -} from 'lucide-react' - import { apolloClient } from '@/lib/apollo-client' import { WildberriesService } from '@/services/wildberries-service' - -import { useQuery, useMutation } from '@apollo/client' -import { format } from 'date-fns' -import { ru } from 'date-fns/locale' - -import { SelectedCard, FulfillmentService, ConsumableService, WildberriesCard } from '@/types/supplies' +import { SelectedCard, WildberriesCard } from '@/types/supplies' interface Organization { id: string @@ -65,7 +55,7 @@ interface WBProductCardsProps { } export function WBProductCards({ - onBack, + _onBack, // eslint-disable-line @typescript-eslint/no-unused-vars onComplete, showSummary: externalShowSummary, setShowSummary: externalSetShowSummary, @@ -89,7 +79,6 @@ export function WBProductCards({ const actualShowSummary = externalShowSummary !== undefined ? externalShowSummary : showSummary const actualSetShowSummary = externalSetShowSummary || setShowSummary const [globalDeliveryDate, setGlobalDeliveryDate] = useState(undefined) - const [fulfillmentServices, setFulfillmentServices] = useState([]) const [organizationServices, setOrganizationServices] = useState<{ [orgId: string]: Array<{ id: string; name: string; description?: string; price: number }> }>({}) @@ -194,13 +183,6 @@ export function WBProductCards({ }, }) - // Данные рынков можно будет загружать через GraphQL в будущем - const markets = [ - { value: 'sadovod', label: 'Садовод' }, - { value: 'luzhniki', label: 'Лужники' }, - { value: 'tishinka', label: 'Тишинка' }, - { value: 'food-city', label: 'Фуд Сити' }, - ] // Загружаем карточки из GraphQL запроса useEffect(() => { @@ -1009,12 +991,11 @@ export function WBProductCards({ {wbCards.map((card) => { const selectedQuantity = getSelectedQuantity(card) const isSelected = selectedQuantity > 0 - const selectedCard = actualSelectedCards.find((sc) => sc.card.nmID === card.nmID) return (
{/* Изображение и основная информация */} @@ -1024,7 +1005,7 @@ export function WBProductCards({ {card.title} handleCardClick(card)} /> diff --git a/src/components/warehouse/product-card.tsx b/src/components/warehouse/product-card.tsx index 883f917..3d1e7aa 100644 --- a/src/components/warehouse/product-card.tsx +++ b/src/components/warehouse/product-card.tsx @@ -49,6 +49,7 @@ interface Product { isActive: boolean createdAt: string updatedAt: string + organization: { id: string; market?: string } } interface ProductCardProps { @@ -57,6 +58,30 @@ interface ProductCardProps { onDeleted: () => void } +// Функция для отображения бэйджа рынка согласно правилам системы +const getMarketBadge = (market?: string) => { + if (!market) return null + + const marketStyles = { + 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', + } + + const marketLabels = { + sadovod: 'Садовод', + 'tyak-moscow': 'ТЯК Москва', + } + + const style = marketStyles[market as keyof typeof marketStyles] || 'bg-gray-500/20 text-gray-300 border-gray-500/30' + const label = marketLabels[market as keyof typeof marketLabels] || market + + return ( + + {label} + + ) +} + export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) { const [deleteProduct, { loading: deleting }] = useMutation(DELETE_PRODUCT) const [imageDialogOpen, setImageDialogOpen] = useState(false) @@ -103,7 +128,7 @@ export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) { } return ( - + {/* Изображение товара */}
{product.mainImage || product.images[0] ? ( @@ -115,7 +140,7 @@ export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) { alt={product.name} width={300} height={200} - className="w-full h-full object-contain transition-transform duration-300 group-hover:scale-110" + className="w-full h-full object-contain" />
@@ -248,6 +273,9 @@ export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) { {product.type === 'PRODUCT' ? 'Товар' : 'Расходник'} + {/* Рынок */} + {getMarketBadge(product.organization?.market)} + {/* Категория */} {product.category && ( diff --git a/src/components/warehouse/product-form.tsx b/src/components/warehouse/product-form.tsx index fd0b968..d233535 100644 --- a/src/components/warehouse/product-form.tsx +++ b/src/components/warehouse/product-form.tsx @@ -38,6 +38,7 @@ interface Product { images: string[] mainImage: string isActive: boolean + organization?: { id: string; market?: string } } interface ProductFormProps { @@ -46,6 +47,7 @@ interface ProductFormProps { onCancel: () => void } + export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { const [formData, setFormData] = useState({ name: product?.name || '', diff --git a/src/components/warehouse/warehouse-dashboard.tsx b/src/components/warehouse/warehouse-dashboard.tsx index 478dfc1..26e53ce 100644 --- a/src/components/warehouse/warehouse-dashboard.tsx +++ b/src/components/warehouse/warehouse-dashboard.tsx @@ -42,8 +42,34 @@ interface Product { isActive: boolean createdAt: string updatedAt: string + organization: { id: string; market?: string } } +// Функция для отображения бэйджа рынка согласно правилам системы +const getMarketBadge = (market?: string) => { + if (!market) return null + + const marketStyles = { + 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', + } + + const marketLabels = { + sadovod: 'Садовод', + 'tyak-moscow': 'ТЯК Москва', + } + + const style = marketStyles[market as keyof typeof marketStyles] || 'bg-gray-500/20 text-gray-300 border-gray-500/30' + const label = marketLabels[market as keyof typeof marketLabels] || market + + return ( + + {label} + + ) +} + + export function WarehouseDashboard() { const { getSidebarMargin } = useSidebar() const [isDialogOpen, setIsDialogOpen] = useState(false) @@ -58,13 +84,16 @@ export function WarehouseDashboard() { const products: Product[] = data?.myProducts || [] // Фильтрация товаров по поисковому запросу - const filteredProducts = products.filter( - (product) => + const filteredProducts = products.filter((product) => { + const matchesSearch = !searchQuery || ( product.name.toLowerCase().includes(searchQuery.toLowerCase()) || product.article.toLowerCase().includes(searchQuery.toLowerCase()) || product.category?.name?.toLowerCase().includes(searchQuery.toLowerCase()) || - product.brand?.toLowerCase().includes(searchQuery.toLowerCase()), - ) + product.brand?.toLowerCase().includes(searchQuery.toLowerCase()) + ) + + return matchesSearch + }) const handleCreateProduct = () => { setEditingProduct(null) @@ -121,13 +150,14 @@ export function WarehouseDashboard() {
setSearchQuery(e.target.value)} className="glass-input text-white placeholder:text-white/50 h-10" />
+ {/* Переключатель режимов отображения */}
Артикул
Тип
-
Категория
+
Рынок
Цена
Остаток
Заказано
@@ -279,7 +309,9 @@ export function WarehouseDashboard() { {product.type === 'PRODUCT' ? 'Товар' : 'Расходник'}
-
{product.category?.name || 'Нет'}
+
+ {getMarketBadge(product.organization?.market) || Не указан} +
{new Intl.NumberFormat('ru-RU', { style: 'currency', diff --git a/src/graphql/mutations.ts b/src/graphql/mutations.ts index 5b71587..ebc15cd 100644 --- a/src/graphql/mutations.ts +++ b/src/graphql/mutations.ts @@ -212,6 +212,7 @@ export const UPDATE_USER_PROFILE = gql` ogrn ogrnDate type + market status actualityDate registrationDate @@ -622,64 +623,33 @@ export const DELETE_SERVICE = gql` } ` -// Мутации для расходников -export const CREATE_SUPPLY = gql` - mutation CreateSupply($input: SupplyInput!) { - createSupply(input: $input) { +// Мутации для расходников - только обновление цены разрешено +export const UPDATE_SUPPLY_PRICE = gql` + mutation UpdateSupplyPrice($id: ID!, $input: UpdateSupplyPriceInput!) { + updateSupplyPrice(id: $id, input: $input) { success message supply { id name description - price - quantity + pricePerUnit unit - category - status - date - supplier - minStock - currentStock imageUrl + warehouseStock + isAvailable + warehouseConsumableId createdAt updatedAt + organization { + id + name + } } } } ` -export const UPDATE_SUPPLY = gql` - mutation UpdateSupply($id: ID!, $input: SupplyInput!) { - updateSupply(id: $id, input: $input) { - success - message - supply { - id - name - description - price - quantity - unit - category - status - date - supplier - minStock - currentStock - imageUrl - createdAt - updatedAt - } - } - } -` - -export const DELETE_SUPPLY = gql` - mutation DeleteSupply($id: ID!) { - deleteSupply(id: $id) - } -` // Мутация для заказа поставки расходников export const CREATE_SUPPLY_ORDER = gql` @@ -841,6 +811,10 @@ export const CREATE_PRODUCT = gql` isActive createdAt updatedAt + organization { + id + market + } } } } @@ -880,6 +854,10 @@ export const UPDATE_PRODUCT = gql` isActive createdAt updatedAt + organization { + id + market + } } } } diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index 89ab5c3..30f5cff 100644 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -52,6 +52,7 @@ export const GET_ME = gql` ogrn ogrnDate type + market status actualityDate registrationDate @@ -104,19 +105,32 @@ export const GET_MY_SUPPLIES = gql` id name description - price - quantity + pricePerUnit unit - category - status - date - supplier - minStock - currentStock - usedStock imageUrl + warehouseStock + isAvailable + warehouseConsumableId createdAt updatedAt + organization { + id + name + } + } + } +` + +// Новый запрос для получения доступных расходников для рецептур селлеров +export const GET_AVAILABLE_SUPPLIES_FOR_RECIPE = gql` + query GetAvailableSuppliesForRecipe { + getAvailableSuppliesForRecipe { + id + name + pricePerUnit + unit + imageUrl + warehouseStock } } ` @@ -247,6 +261,10 @@ export const GET_MY_PRODUCTS = gql` isActive createdAt updatedAt + organization { + id + market + } } } ` @@ -321,6 +339,7 @@ export const GET_MY_COUNTERPARTIES = gql` managementName type address + market phones emails createdAt diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index eb4294b..c799d21 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -687,28 +687,10 @@ export const resolvers = { throw new GraphQLError('У пользователя нет организации') } - // Получаем заказы поставок, где фулфилмент является получателем, - // но НЕ создателем (т.е. селлеры заказали расходники для фулфилмента) - const sellerSupplyOrders = await prisma.supplyOrder.findMany({ - where: { - fulfillmentCenterId: currentUser.organization.id, // Получатель - мы - organizationId: { not: currentUser.organization.id }, // Создатель - НЕ мы - status: 'DELIVERED', // Только доставленные - }, - include: { - organization: true, - partner: true, - items: { - include: { - product: { - include: { - category: true, - }, - }, - }, - }, - }, - }) + // Проверяем, что это фулфилмент центр + if (currentUser.organization.type !== 'FULFILLMENT') { + return [] // Только фулфилменты имеют расходники + } // Получаем ВСЕ расходники из таблицы supply для фулфилмента const allSupplies = await prisma.supply.findMany({ @@ -717,52 +699,39 @@ export const resolvers = { orderBy: { createdAt: 'desc' }, }) - // Получаем все заказы фулфилмента для себя (чтобы исключить их расходники) - const fulfillmentOwnOrders = await prisma.supplyOrder.findMany({ - where: { - organizationId: currentUser.organization.id, // Созданы фулфилментом - fulfillmentCenterId: currentUser.organization.id, // Для себя - status: 'DELIVERED', - }, - include: { - items: { - include: { - product: true, - }, - }, - }, - }) + // Преобразуем старую структуру в новую согласно GraphQL схеме + const transformedSupplies = allSupplies.map((supply) => ({ + id: supply.id, + name: supply.name, + description: supply.description, + pricePerUnit: supply.price ? parseFloat(supply.price.toString()) : null, // Конвертируем Decimal в Number + unit: supply.unit || 'шт', // Единица измерения + imageUrl: supply.imageUrl, + warehouseStock: supply.currentStock || 0, // Остаток на складе + isAvailable: (supply.currentStock || 0) > 0, // Есть ли в наличии + warehouseConsumableId: supply.id, // Связь со складом (пока используем тот же ID) + createdAt: supply.createdAt, + updatedAt: supply.updatedAt, + organization: supply.organization, + })) - // Создаем набор названий товаров из заказов фулфилмента для себя - const fulfillmentProductNames = new Set( - fulfillmentOwnOrders.flatMap((order) => order.items.map((item) => item.product.name)), - ) - - // Фильтруем расходники: исключаем те, что созданы заказами фулфилмента для себя - const sellerSupplies = allSupplies.filter((supply) => { - // Если расходник соответствует товару из заказа фулфилмента для себя, - // то это расходник фулфилмента, а не селлера - return !fulfillmentProductNames.has(supply.name) - }) - - // Логирование для отладки - console.warn('🔥🔥🔥 SELLER SUPPLIES RESOLVER CALLED 🔥🔥🔥') - console.warn('📊 Расходники селлеров:', { + console.warn('🔥 SUPPLIES RESOLVER - NEW FORMAT:', { organizationId: currentUser.organization.id, - organizationType: currentUser.organization.type, - allSuppliesCount: allSupplies.length, - fulfillmentOwnOrdersCount: fulfillmentOwnOrders.length, - fulfillmentProductNames: Array.from(fulfillmentProductNames), - filteredSellerSuppliesCount: sellerSupplies.length, - sellerOrdersCount: sellerSupplyOrders.length, + suppliesCount: transformedSupplies.length, + supplies: transformedSupplies.map(s => ({ + id: s.id, + name: s.name, + pricePerUnit: s.pricePerUnit, + warehouseStock: s.warehouseStock, + isAvailable: s.isAvailable, + })), }) - // Возвращаем только расходники селлеров (исключая расходники фулфилмента) - return sellerSupplies + return transformedSupplies }, - // Расходники фулфилмента (материалы для работы фулфилмента) - myFulfillmentSupplies: async (_: unknown, __: unknown, context: Context) => { + // Доступные расходники для рецептур селлеров (только с ценой и в наличии) + getAvailableSuppliesForRecipe: async (_: unknown, __: unknown, context: Context) => { if (!context.user) { throw new GraphQLError('Требуется авторизация', { extensions: { code: 'UNAUTHENTICATED' }, @@ -778,83 +747,90 @@ export const resolvers = { throw new GraphQLError('У пользователя нет организации') } - // TypeScript assertion - мы знаем что organization не null после проверки выше - const organization = currentUser.organization + // Селлеры могут получать расходники от своих фулфилмент-партнеров + if (currentUser.organization.type !== 'SELLER') { + return [] // Только селлеры используют рецептуры + } - // Получаем заказы поставок, созданные этим фулфилмент-центром для себя - const fulfillmentSupplyOrders = await prisma.supplyOrder.findMany({ + // TODO: В будущем здесь будет логика получения расходников от партнерских фулфилментов + // Пока возвращаем пустой массив, так как эта функциональность еще разрабатывается + console.warn('🔥 getAvailableSuppliesForRecipe called for seller:', { + sellerId: currentUser.organization.id, + sellerName: currentUser.organization.name, + }) + + return [] + }, + + // Расходники фулфилмента из склада (новая архитектура - синхронизация со склада) + myFulfillmentSupplies: async (_: unknown, __: unknown, context: Context) => { + console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED (NEW ARCHITECTURE) 🔥🔥🔥') + + if (!context.user) { + console.warn('❌ No user in context') + throw new GraphQLError('Требуется авторизация', { + extensions: { code: 'UNAUTHENTICATED' }, + }) + } + + const currentUser = await prisma.user.findUnique({ + where: { id: context.user.id }, + include: { organization: true }, + }) + + console.warn('👤 Current user:', { + id: currentUser?.id, + phone: currentUser?.phone, + organizationId: currentUser?.organizationId, + organizationType: currentUser?.organization?.type, + organizationName: currentUser?.organization?.name, + }) + + if (!currentUser?.organization) { + console.warn('❌ No organization for user') + throw new GraphQLError('У пользователя нет организации') + } + + // Проверяем что это фулфилмент центр + if (currentUser.organization.type !== 'FULFILLMENT') { + console.warn('❌ User organization is not FULFILLMENT:', currentUser.organization.type) + throw new GraphQLError('Доступ только для фулфилмент центров') + } + + // Получаем расходники фулфилмента из таблицы Supply + const supplies = await prisma.supply.findMany({ where: { - organizationId: organization.id, // Создали мы - fulfillmentCenterId: organization.id, // Получатель - мы - status: { - in: ['PENDING', 'CONFIRMED', 'IN_TRANSIT', 'DELIVERED'], // Все статусы - }, + organizationId: currentUser.organization.id, + type: 'FULFILLMENT_CONSUMABLES', // Только расходники фулфилмента }, include: { - partner: true, - items: { - include: { - product: { - include: { - category: true, - }, - }, - }, - }, + organization: true, }, orderBy: { createdAt: 'desc' }, }) - // Преобразуем заказы поставок в формат supply для единообразия - const fulfillmentSupplies = fulfillmentSupplyOrders.flatMap((order) => - order.items.map((item) => ({ - id: `fulfillment-order-${order.id}-${item.id}`, - name: item.product.name, - description: item.product.description || `Расходники от ${order.partner.name}`, - price: item.price, - quantity: item.quantity, - unit: 'шт', - category: item.product.category?.name || 'Расходники фулфилмента', - status: - order.status === 'PENDING' - ? 'planned' - : order.status === 'CONFIRMED' - ? 'confirmed' - : order.status === 'IN_TRANSIT' - ? 'in-transit' - : order.status === 'DELIVERED' - ? 'in-stock' - : 'planned', - date: order.createdAt, - supplier: order.partner.name || order.partner.fullName || 'Не указан', - minStock: Math.round(item.quantity * 0.1), - currentStock: order.status === 'DELIVERED' ? item.quantity : 0, - usedStock: 0, // TODO: Подсчитывать реальное использование - imageUrl: null, - createdAt: order.createdAt, - updatedAt: order.updatedAt, - organizationId: organization.id, - organization: organization, - shippedQuantity: 0, - })), - ) - // Логирование для отладки - console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED 🔥🔥🔥') - console.warn('📊 Расходники фулфилмента:', { - organizationId: organization.id, - organizationType: organization.type, - fulfillmentOrdersCount: fulfillmentSupplyOrders.length, - fulfillmentSuppliesCount: fulfillmentSupplies.length, - fulfillmentOrders: fulfillmentSupplyOrders.map((o) => ({ - id: o.id, - supplierName: o.partner.name, - status: o.status, - itemsCount: o.items.length, + console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED (NEW ARCHITECTURE) 🔥🔥🔥') + console.warn('📊 Расходники фулфилмента из склада:', { + organizationId: currentUser.organization.id, + organizationType: currentUser.organization.type, + suppliesCount: supplies.length, + supplies: supplies.map((s) => ({ + id: s.id, + name: s.name, + type: s.type, + status: s.status, + currentStock: s.currentStock, + quantity: s.quantity, })), }) - return fulfillmentSupplies + // Преобразуем в формат для фронтенда + return supplies.map(supply => ({ + ...supply, + price: supply.price ? parseFloat(supply.price.toString()) : 0, + shippedQuantity: 0, // Добавляем для совместимости + })) }, // Заказы поставок расходников @@ -1411,11 +1387,6 @@ export const resolvers = { // Мои товары и расходники (для поставщиков) myProducts: async (_: unknown, __: unknown, context: Context) => { - console.warn('🔍 MY_PRODUCTS RESOLVER - ВЫЗВАН:', { - hasUser: !!context.user, - userId: context.user?.id, - timestamp: new Date().toISOString(), - }) if (!context.user) { throw new GraphQLError('Требуется авторизация', { @@ -1428,12 +1399,6 @@ export const resolvers = { include: { organization: true }, }) - console.warn('👤 ПОЛЬЗОВАТЕЛЬ НАЙДЕН:', { - userId: currentUser?.id, - hasOrganization: !!currentUser?.organization, - organizationType: currentUser?.organization?.type, - organizationName: currentUser?.organization?.name, - }) if (!currentUser?.organization) { throw new GraphQLError('У пользователя нет организации') @@ -1441,10 +1406,6 @@ export const resolvers = { // Проверяем, что это поставщик if (currentUser.organization.type !== 'WHOLESALE') { - console.warn('❌ ДОСТУП ЗАПРЕЩЕН - НЕ ПОСТАВЩИК:', { - actualType: currentUser.organization.type, - requiredType: 'WHOLESALE', - }) throw new GraphQLError('Товары доступны только для поставщиков') } @@ -2586,6 +2547,7 @@ export const resolvers = { bik?: string accountNumber?: string corrAccount?: string + market?: string } }, context: Context, @@ -2636,6 +2598,7 @@ export const resolvers = { emails?: object managementName?: string managementPost?: string + market?: string } = {} // Название организации больше не обновляется через профиль @@ -2651,6 +2614,11 @@ export const resolvers = { updateData.emails = [{ value: input.email, type: 'main' }] } + // Обновляем рынок для поставщиков + if (input.market !== undefined) { + updateData.market = input.market === 'none' ? null : input.market + } + // Сохраняем дополнительные контакты в custom полях // Пока добавим их как дополнительные JSON поля const customContacts: { @@ -3639,23 +3607,14 @@ export const resolvers = { } }, - // Создать расходник - createSupply: async ( + + // Обновить цену расходника (новая архитектура - только цену можно редактировать) + updateSupplyPrice: async ( _: unknown, args: { + id: string input: { - name: string - description?: string - price: number - quantity: number - unit: string - category: string - status: string - date: string - supplier: string - minStock: number - currentStock: number - imageUrl?: string + pricePerUnit?: number | null } }, context: Context, @@ -3677,167 +3636,68 @@ export const resolvers = { // Проверяем, что это фулфилмент центр if (currentUser.organization.type !== 'FULFILLMENT') { - throw new GraphQLError('Расходники доступны только для фулфилмент центров') + throw new GraphQLError('Обновление цен расходников доступно только для фулфилмент центров') } try { - const supply = await prisma.supply.create({ - data: { - name: args.input.name, - description: args.input.description, - price: args.input.price, - quantity: args.input.quantity, - unit: args.input.unit, - category: args.input.category, - status: args.input.status, - date: new Date(args.input.date), - supplier: args.input.supplier, - minStock: args.input.minStock, - currentStock: args.input.currentStock, - imageUrl: args.input.imageUrl, + // Находим и обновляем расходник + const existingSupply = await prisma.supply.findFirst({ + where: { + id: args.id, organizationId: currentUser.organization.id, }, - include: { organization: true }, }) - return { - success: true, - message: 'Расходник успешно создан', - supply, + if (!existingSupply) { + throw new GraphQLError('Расходник не найден') } - } catch (error) { - console.error('Error creating supply:', error) - return { - success: false, - message: 'Ошибка при создании расходника', - } - } - }, - // Обновить расходник - updateSupply: async ( - _: unknown, - args: { - id: string - input: { - name: string - description?: string - price: number - quantity: number - unit: string - category: string - status: string - date: string - supplier: string - minStock: number - currentStock: number - imageUrl?: string - } - }, - context: Context, - ) => { - if (!context.user) { - throw new GraphQLError('Требуется авторизация', { - extensions: { code: 'UNAUTHENTICATED' }, - }) - } - - const currentUser = await prisma.user.findUnique({ - where: { id: context.user.id }, - include: { organization: true }, - }) - - if (!currentUser?.organization) { - throw new GraphQLError('У пользователя нет организации') - } - - // Проверяем, что расходник принадлежит текущей организации - const existingSupply = await prisma.supply.findFirst({ - where: { - id: args.id, - organizationId: currentUser.organization.id, - }, - }) - - if (!existingSupply) { - throw new GraphQLError('Расходник не найден или нет доступа') - } - - try { - const supply = await prisma.supply.update({ + const updatedSupply = await prisma.supply.update({ where: { id: args.id }, data: { - name: args.input.name, - description: args.input.description, - price: args.input.price, - quantity: args.input.quantity, - unit: args.input.unit, - category: args.input.category, - status: args.input.status, - date: new Date(args.input.date), - supplier: args.input.supplier, - minStock: args.input.minStock, - currentStock: args.input.currentStock, - imageUrl: args.input.imageUrl, + price: args.input.pricePerUnit, // Обновляем только цену + updatedAt: new Date(), }, include: { organization: true }, }) + // Преобразуем в новый формат для GraphQL + const transformedSupply = { + id: updatedSupply.id, + name: updatedSupply.name, + description: updatedSupply.description, + pricePerUnit: updatedSupply.price ? parseFloat(updatedSupply.price.toString()) : null, // Конвертируем Decimal в Number + unit: updatedSupply.unit || 'шт', + imageUrl: updatedSupply.imageUrl, + warehouseStock: updatedSupply.currentStock || 0, + isAvailable: (updatedSupply.currentStock || 0) > 0, + warehouseConsumableId: updatedSupply.id, + createdAt: updatedSupply.createdAt, + updatedAt: updatedSupply.updatedAt, + organization: updatedSupply.organization, + } + + console.warn('🔥 SUPPLY PRICE UPDATED:', { + id: transformedSupply.id, + name: transformedSupply.name, + oldPrice: existingSupply.price, + newPrice: transformedSupply.pricePerUnit, + }) + return { success: true, - message: 'Расходник успешно обновлен', - supply, + message: 'Цена расходника успешно обновлена', + supply: transformedSupply, } } catch (error) { - console.error('Error updating supply:', error) + console.error('Error updating supply price:', error) return { success: false, - message: 'Ошибка при обновлении расходника', + message: 'Ошибка при обновлении цены расходника', } } }, - // Удалить расходник - deleteSupply: async (_: unknown, args: { id: string }, context: Context) => { - if (!context.user) { - throw new GraphQLError('Требуется авторизация', { - extensions: { code: 'UNAUTHENTICATED' }, - }) - } - - const currentUser = await prisma.user.findUnique({ - where: { id: context.user.id }, - include: { organization: true }, - }) - - if (!currentUser?.organization) { - throw new GraphQLError('У пользователя нет организации') - } - - // Проверяем, что расходник принадлежит текущей организации - const existingSupply = await prisma.supply.findFirst({ - where: { - id: args.id, - organizationId: currentUser.organization.id, - }, - }) - - if (!existingSupply) { - throw new GraphQLError('Расходник не найден или нет доступа') - } - - try { - await prisma.supply.delete({ - where: { id: args.id }, - }) - - return true - } catch (error) { - console.error('Error deleting supply:', error) - return false - } - }, - // Использовать расходники фулфилмента useFulfillmentSupplies: async ( _: unknown, diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts index 35a0d97..4c08277 100644 --- a/src/graphql/typedefs.ts +++ b/src/graphql/typedefs.ts @@ -36,6 +36,9 @@ export const typeDefs = gql` # Расходники селлеров (материалы клиентов) mySupplies: [Supply!]! + + # Доступные расходники для рецептур селлеров (только с ценой и в наличии) + getAvailableSuppliesForRecipe: [SupplyForRecipe!]! # Расходники фулфилмента (материалы для работы фулфилмента) myFulfillmentSupplies: [Supply!]! @@ -173,10 +176,8 @@ export const typeDefs = gql` updateService(id: ID!, input: ServiceInput!): ServiceResponse! deleteService(id: ID!): Boolean! - # Работа с расходниками - createSupply(input: SupplyInput!): SupplyResponse! - updateSupply(id: ID!, input: SupplyInput!): SupplyResponse! - deleteSupply(id: ID!): Boolean! + # Работа с расходниками (только обновление цены разрешено) + updateSupplyPrice(id: ID!, input: UpdateSupplyPriceInput!): SupplyResponse! # Использование расходников фулфилмента useFulfillmentSupplies(input: UseFulfillmentSuppliesInput!): SupplyResponse! @@ -278,6 +279,7 @@ export const typeDefs = gql` ogrn: String ogrnDate: DateTime type: OrganizationType! + market: String status: String actualityDate: DateTime registrationDate: DateTime @@ -335,6 +337,9 @@ export const typeDefs = gql` bik: String accountNumber: String corrAccount: String + + # Рынок для поставщиков + market: String } input FulfillmentRegistrationInput { @@ -516,38 +521,45 @@ export const typeDefs = gql` id: ID! name: String! description: String - price: Float! - quantity: Int! - unit: String - category: String - status: String - date: DateTime! - supplier: String - minStock: Int - currentStock: Int - usedStock: Int + # Новые поля для Services архитектуры + pricePerUnit: Float # Цена за единицу для рецептур (может быть null) + unit: String! # Единица измерения: "шт", "кг", "м" + warehouseStock: Int! # Остаток на складе (readonly) + isAvailable: Boolean! # Есть ли на складе (влияет на цвет) + warehouseConsumableId: ID! # Связь со складом + # Поля из базы данных для обратной совместимости + price: Float! # Из Prisma schema + quantity: Int! # Из Prisma schema + category: String! # Из Prisma schema + status: String! # Из Prisma schema + date: DateTime! # Из Prisma schema + supplier: String! # Из Prisma schema + minStock: Int! # Из Prisma schema + currentStock: Int! # Из Prisma schema + usedStock: Int! # Из Prisma schema + type: String! # Из Prisma schema (SupplyType enum) + sellerOwnerId: ID # Из Prisma schema + sellerOwner: Organization # Из Prisma schema + shopLocation: String # Из Prisma schema imageUrl: String - type: SupplyType! - sellerOwner: Organization # Селлер-владелец (для расходников селлеров) - shopLocation: String # Местоположение в магазине фулфилмента createdAt: DateTime! updatedAt: DateTime! organization: Organization! } - input SupplyInput { + # Для рецептур селлеров - только доступные с ценой + type SupplyForRecipe { + id: ID! name: String! - description: String - price: Float! - quantity: Int! + pricePerUnit: Float! # Всегда не null unit: String! - category: String! - status: String! - date: DateTime! - supplier: String! - minStock: Int! - currentStock: Int! imageUrl: String + warehouseStock: Int! # Всегда > 0 + } + + # Для обновления цены расходника в разделе Услуги + input UpdateSupplyPriceInput { + pricePerUnit: Float # Может быть null (цена не установлена) } input UseFulfillmentSuppliesInput { @@ -555,6 +567,14 @@ export const typeDefs = gql` quantityUsed: Int! description: String # Описание использования (например, "Подготовка 300 продуктов") } + + # Устаревшие типы для обратной совместимости + input SupplyInput { + name: String! + description: String + price: Float! + imageUrl: String + } type SupplyResponse { success: Boolean! diff --git a/src/lib/apollo-client.ts b/src/lib/apollo-client.ts index 49a8132..2c5fcc8 100644 --- a/src/lib/apollo-client.ts +++ b/src/lib/apollo-client.ts @@ -1,6 +1,5 @@ import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client' import { setContext } from '@apollo/client/link/context' -import { onError } from '@apollo/client/link/error' // HTTP Link для GraphQL запросов const httpLink = createHttpLink({ @@ -19,87 +18,20 @@ const authLink = setContext((operation, { headers }) => { // Приоритет у админского токена const token = adminToken || userToken - const tokenType = adminToken ? 'admin' : 'user' - - console.warn( - `Apollo Client - Operation: ${operation.operationName}, Token type: ${tokenType}, Token:`, - token ? `${token.substring(0, 20)}...` : 'No token', - ) const authHeaders = { ...headers, authorization: token ? `Bearer ${token}` : '', } - console.warn('Apollo Client - Auth headers:', { - authorization: authHeaders.authorization ? 'Bearer ***' : 'No auth', - }) - return { headers: authHeaders, } }) -// Error Link для обработки ошибок с детальным логированием -const errorLink = onError(({ graphQLErrors, networkError, operation, forward: _forward }) => { - try { - // Расширенная отладочная информация для всех ошибок - const debugInfo = { - hasGraphQLErrors: !!graphQLErrors, - graphQLErrorsLength: graphQLErrors?.length || 0, - hasNetworkError: !!networkError, - operationName: operation?.operationName || 'Unknown', - operationType: (operation?.query?.definitions?.[0] as any)?.operation || 'Unknown', - variables: operation?.variables || {}, - } - - console.warn('🎯 APOLLO ERROR LINK TRIGGERED:', debugInfo) - - // Безопасная обработка GraphQL ошибок - if (graphQLErrors && Array.isArray(graphQLErrors) && graphQLErrors.length > 0) { - console.warn('📊 GRAPHQL ERRORS COUNT:', graphQLErrors.length) - - graphQLErrors.forEach((error, index) => { - try { - // Безопасная деструктуризация - const message = error?.message || 'No message' - const locations = error?.locations || [] - const path = error?.path || [] - const extensions = error?.extensions || {} - - console.warn(`🚨 GraphQL Error #${index + 1}:`, { - message, - locations, - path, - extensions, - operation: operation?.operationName || 'Unknown', - }) - } catch (innerError) { - console.warn(`❌ Error processing GraphQL error #${index + 1}:`, innerError) - } - }) - } - - // Безопасная обработка Network ошибок - if (networkError) { - try { - console.warn('🌐 Network Error:', { - message: networkError.message || 'No message', - statusCode: (networkError as any).statusCode || 'No status', - operation: operation?.operationName || 'Unknown', - }) - } catch (innerError) { - console.warn('❌ Error processing network error:', innerError) - } - } - } catch (outerError) { - console.warn('❌ Critical error in Apollo error link:', outerError) - } -}) - // Создаем Apollo Client export const apolloClient = new ApolloClient({ - link: from([errorLink, authLink, httpLink]), + link: from([authLink, httpLink]), cache: new InMemoryCache({ typePolicies: { User: { diff --git a/test-supplies.js b/test-supplies.js new file mode 100644 index 0000000..157ead1 --- /dev/null +++ b/test-supplies.js @@ -0,0 +1,2 @@ +// Простой тест для проверки GraphQL запроса mySupplies +// testQuery удален из-за неиспользования \ No newline at end of file