{"id":478679,"date":"2026-05-05T14:45:59","date_gmt":"2026-05-05T14:45:59","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=478679"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=478679","title":{"rendered":"Migration toolkit \u0434\u043b\u044f 1\u0421 \u0411\u0438\u0442\u0440\u0438\u043a\u0441: \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u043c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u043c\u0435\u0436\u0434\u0443 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u043c\u0438 \u0447\u0435\u0440\u0435\u0437 crm.*.list + \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u043f\u043e ORIGINATOR_ID"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h4>\u0417\u0430\u0447\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043c\u0435\u0436\u0434\u0443 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u043c\u0438<\/h4>\n<p>\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 (<a href=\"https:\/\/habr.com\/ru\/articles\/1025026\/\" rel=\"noopener noreferrer nofollow\">\u043a\u0430\u043a \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u043b\u0438\u0434\u044b \u0438\u0437 Next.js \u0432 1\u0421 \u0411\u0438\u0442\u0440\u0438\u043a\u0441<\/a>) \u044f \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u043b outbound-\u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e: \u0441\u0430\u0439\u0442 \u043f\u0438\u0448\u0435\u0442 \u043b\u0438\u0434 \u043a \u0441\u0435\u0431\u0435 \u0432 PostgreSQL, \u0447\u0435\u0440\u0435\u0437 <code>after()<\/code> \u043e\u0442\u0434\u0430\u0451\u0442 \u0435\u0433\u043e \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441, \u0432 \u0441\u0442\u0440\u043e\u043a\u0443 \u043b\u0438\u0434\u0430 \u043f\u043e\u0434\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 <code>bitrix_id<\/code>. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043f\u043e\u043a\u0430 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043e\u0434\u0438\u043d.<\/p>\n<p>\u041d\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u0436\u0438\u0437\u043d\u0438 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0440\u0435\u0434\u043a\u043e \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043e\u0434\u0438\u043d. \u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0443\u0436\u043d\u0430 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0430\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u043c\u0438, \u044f \u043b\u043e\u0432\u0438\u043b \u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0447\u0435\u0442\u044b\u0440\u0435 \u0440\u0430\u0437\u0430 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0433\u043e\u0434:<\/p>\n<ul>\n<li>\n<p><strong>\u041f\u0435\u0440\u0435\u0435\u0437\u0434 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432.<\/strong> \u041a\u043b\u0438\u0435\u043d\u0442 \u0434\u0435\u0440\u0436\u0430\u043b self-hosted \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043d\u0430 \u0441\u0442\u0430\u0440\u043e\u043c VPS, \u043f\u0435\u0440\u0435\u0435\u0437\u0436\u0430\u0435\u0442 \u043d\u0430 \u043d\u043e\u0432\u044b\u0439. SaaS-\u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u043d\u0430 \u043d\u043e\u0432\u044b\u0439 \u0434\u043e\u043c\u0435\u043d \u2014 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>\u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 test\/prod.<\/strong> \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d-\u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0435. \u0425\u043e\u0442\u044f\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 staging, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d \u0441\u0440\u0435\u0437 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445, \u0447\u0442\u043e\u0431\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0435\u0437 \u0440\u0438\u0441\u043a\u0430 \u0434\u043b\u044f \u0436\u0438\u0432\u043e\u0439 \u0432\u043e\u0440\u043e\u043d\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u044e\u0440\u043b\u0438\u0446.<\/strong> \u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0434\u0435\u043b\u0438\u0442\u0441\u044f \u043d\u0430 \u0434\u0432\u0430 \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043b\u0438\u0446\u0430, \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d \u0441\u0432\u043e\u0439 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0441 \u0447\u0430\u0441\u0442\u044c\u044e \u043e\u0431\u0449\u0435\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0431\u0430\u0437\u044b.<\/p>\n<\/li>\n<li>\n<p><strong>\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438.<\/strong> \u042f \u043a\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0435 \u043c\u043e\u0433\u0443 \u0433\u043e\u043d\u044f\u0442\u044c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u043f\u043e \u0436\u0438\u0432\u043e\u0439 CRM \u043a\u043b\u0438\u0435\u043d\u0442\u0430. \u041c\u043d\u0435 \u043d\u0443\u0436\u0435\u043d \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u0441 \u0437\u0435\u0440\u043a\u0430\u043b\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0442\u044c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440 \u0431\u0435\u0437 \u0440\u0438\u0441\u043a\u0430 \u043d\u0430\u043b\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0434\u0435.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u043e \u0432\u0441\u0435\u0445 \u0447\u0435\u0442\u044b\u0440\u0451\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0437\u0430\u0434\u0430\u0447\u0430 \u043e\u0434\u043d\u0430: <strong>\u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043b\u0438\u0434\u044b, \u0441\u0434\u0435\u043b\u043a\u0438, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b, \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0438\u0437 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 \u0432 target-\u0438\u043d\u0441\u0442\u0430\u043d\u0441, \u043d\u0435 \u043f\u043b\u043e\u0434\u044f \u0434\u0443\u0431\u043b\u0438 \u043f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u043e\u0433\u043e\u043d\u0430\u0445<\/strong>. \u0422\u043e \u0435\u0441\u0442\u044c \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043a\u0440\u0438\u043f\u0442 \u00ab\u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0437\u0430\u043b\u0438\u0442\u044c \u0438 \u0437\u0430\u0431\u044b\u0442\u044c\u00bb, \u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c 5 \u0440\u0430\u0437 \u2014 \u0438 \u043f\u044f\u0442\u044b\u0439 \u0440\u0430\u0437 \u043e\u043d \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0435\u0449\u0451 \u043f\u044f\u0442\u044c \u043a\u043e\u043f\u0438\u0439 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043b\u0438\u0434\u0430.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u2014 \u043f\u0430\u0442\u0442\u0435\u0440\u043d migration toolkit, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u0435\u0439\u0441\u0430 \u043d\u0435\u0434\u0432\u0438\u0436\u0438\u043c\u043e\u0441\u0442\u0438. \u041e\u0434\u0438\u043d Node-\u0441\u043a\u0440\u0438\u043f\u0442, \u0434\u0432\u0430 webhook URL \u0432 env-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0411\u0414. \u0418\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 <code>ORIGINATOR_ID<\/code> + <code>ORIGIN_ID<\/code> \u2014 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0433\u043b\u0430\u0432\u043d\u043e\u0435, \u0447\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442 migration toolkit \u043e\u0442 \u043d\u0430\u0438\u0432\u043d\u043e\u0433\u043e \u00ab\u0441\u043b\u0438\u043b-\u0437\u0430\u043b\u0438\u043b\u00bb.<\/p>\n<hr\/>\n<h4>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u043e\u0434\u0438\u043d \u0441\u043a\u0440\u0438\u043f\u0442, \u0434\u0432\u0430 webhook<\/h4>\n<p>Migration toolkit \u043d\u0435 \u043d\u0443\u0436\u0435\u043d \u043a\u0430\u043a \u0441\u0435\u0440\u0432\u0438\u0441. \u042d\u0442\u043e <strong>\u0440\u0430\u0437\u043e\u0432\u044b\u0439 CLI-\u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u043c \u0440\u0443\u043a\u0430\u043c\u0438, \u0441\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0441 \u043b\u043e\u0433\u043e\u043c \u0438 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e.<\/p>\n<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502  BITRIX_SOURCE     \u2502  \u2500\u2500 crm.lead.list \u2500\u2500\u2500\u2500\u2500\u25b6     \u2502   migrate.ts       \u2502\u2502  (\u043e\u0442\u043a\u0443\u0434\u0430 \u0442\u044f\u043d\u0435\u043c)    \u2502     crm.deal.list            \u2502   - pagination     \u2502\u2502                    \u2502     crm.contact.list         \u2502   - normalization  \u2502\u2502                    \u2502     crm.company.list         \u2502   - mapping        \u2502\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                              \u2502                    \u2502                                                    \u2502                    \u2502\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                              \u2502                    \u2502\u2502  BITRIX_TARGET     \u2502  \u25c0\u2500\u2500 crm.lead.add \u2500\u2500\u2500\u2500       \u2502                    \u2502\u2502  (\u043a\u0443\u0434\u0430 \u043f\u0438\u0448\u0435\u043c)      \u2502      crm.deal.add            \u2502                    \u2502\u2502                    \u2502      crm.contact.add         \u2502                    \u2502\u2502                    \u2502      crm.company.add         \u2502                    \u2502\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                                                            \u2502                                                            \u25bc                                                   \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                                                   \u2502  migration.log  \u2502                                                   \u2502  (\u043f\u043b\u043e\u0441\u043a\u0438\u0439 JSON) \u2502                                                   \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0432 \u044d\u0442\u043e\u0439 \u0441\u0445\u0435\u043c\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442:<\/p>\n<ul>\n<li>\n<p><strong>\u041d\u0435\u0442 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0439 \u0411\u0414.<\/strong> \u0414\u0430\u043d\u043d\u044b\u0435 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u0431\u0430\u0442\u0447\u0430\u043c\u0438 \u0432 \u043f\u0430\u043c\u044f\u0442\u044c (\u043f\u043e 50 \u0437\u0430\u043f\u0438\u0441\u0435\u0439), \u043c\u0430\u043f\u044f\u0442\u0441\u044f, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 target \u0438 \u0437\u0430\u0431\u044b\u0432\u0430\u044e\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0443\u043f\u0430\u0434\u0451\u0442 \u2014 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c, \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0438\u0442 \u043e\u0442 \u0434\u0443\u0431\u043b\u0435\u0439.<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0442 \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432 \u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439.<\/strong> \u042d\u0442\u043e \u0440\u0430\u0437\u043e\u0432\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f, \u043d\u0435 daemon. Redis\/BullMQ \u0442\u0443\u0442 \u2014 over-engineering. \u0415\u0441\u043b\u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 6 \u0447\u0430\u0441\u043e\u0432 \u2014 \u043f\u0443\u0441\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 6 \u0447\u0430\u0441\u043e\u0432 \u0432 <code>screen<\/code>\/<code>tmux<\/code>.<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0442 \u0434\u0432\u0443\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0439 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438.<\/strong> \u042d\u0442\u043e mi\u0433\u0440\u0430\u0446\u0438\u044f, \u0430 \u043d\u0435 sync. Source \u2192 target, \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u043e\u0440\u043e\u043d\u0443. \u041f\u043e\u0441\u043b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 source \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043a \u0430\u0440\u0445\u0438\u0432.<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433 \u2014 \u0434\u0432\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f:<\/p>\n<p>bash<\/p>\n<pre><code># .envBITRIX_SOURCE_WEBHOOK_URL=https:\/\/old-account.bitrix24.ru\/rest\/1\/abc123def456\/BITRIX_TARGET_WEBHOOK_URL=https:\/\/new-account.bitrix24.ru\/rest\/1\/xyz789ghi012\/<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>Webhook \u0432\u043c\u0435\u0441\u0442\u043e OAuth-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u0442\u0435\u043c \u0436\u0435 \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c, \u0447\u0442\u043e \u0438 \u0432 outbound-\u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438: \u043d\u0435 \u043d\u0443\u0436\u0435\u043d Marketplace-\u0430\u043f\u0440\u0443\u0432, \u0441\u043a\u043e\u0443\u043f\u044b \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0432\u0435\u0431\u0445\u0443\u043a\u0430, \u0442\u043e\u043a\u0435\u043d \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 env. \u041c\u0438\u043d\u0443\u0441 \u2014 \u0442\u043e\u043a\u0435\u043d \u0432 URL, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u043e: \u043d\u0438 \u0432 \u043b\u043e\u0433\u0438, \u043d\u0438 \u0432 Sentry \u043f\u043e\u043b\u043d\u044b\u0439 URL \u043d\u0435 \u043f\u0438\u0448\u0435\u0442\u0441\u044f. \u0422\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u0438 payload.<\/p>\n<hr\/>\n<h4>\u0427\u0442\u0435\u043d\u0438\u0435 source: crm.*.list + \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f<\/h4>\n<p>\u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043e\u0442\u0434\u0430\u0451\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u043c\u0435\u0439\u0441\u0442\u0432\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 <code>crm.{entity}.list<\/code>. \u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u0432\u044b\u0437\u043e\u0432:<\/p>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/source.tsimport { bitrixRequest } from \".\/client\";const PAGE_SIZE = 50; \/\/ \u0416\u0451\u0441\u0442\u043a\u0438\u0439 \u043b\u0438\u043c\u0438\u0442 Bitrix24, \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u043b\u044c\u0437\u044fexport async function* iterateLeads(): AsyncGenerator&lt;BitrixLead&gt; {  let start = 0;  while (true) {    const response = await bitrixRequest(\"source\", \"crm.lead.list\", {      start,      order: { ID: \"ASC\" },      filter: {}, \/\/ \u0431\u0435\u0437 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u2014 \u0442\u044f\u043d\u0435\u043c \u0432\u0441\u0451      select: [\"*\", \"UF_*\"], \/\/ \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u043e\u043b\u044f    });    const leads: BitrixLead[] = response.result ?? [];    for (const lead of leads) {      yield lead;    }    \/\/ \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 next \u0432 \u0432\u0438\u0434\u0435 \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b    if (response.next === undefined || leads.length &lt; PAGE_SIZE) {      return;    }    start = response.next;  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0440\u0438 \u043c\u043e\u043c\u0435\u043d\u0442\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0442\u044b\u043a\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435:<\/p>\n<p><strong>1. <\/strong><code><strong>start<\/strong><\/code><strong> \u2014 \u044d\u0442\u043e \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435, \u043d\u0435 \u043d\u043e\u043c\u0435\u0440 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/strong> \u0415\u0441\u043b\u0438 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 50 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0438 \u0432\u044b \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b\u0438 10 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u2014 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 <code>start = 500<\/code>. \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043e\u0442\u0434\u0430\u0451\u0442 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 <a href=\"http:\/\/response.next\" rel=\"noopener noreferrer nofollow\"><code>response.next<\/code><\/a>, \u0438 \u043f\u0440\u043e\u0449\u0435 \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0435\u043c\u0443, \u0447\u0435\u043c \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u043c\u0443.<\/p>\n<p><strong>2. \u041b\u0438\u043c\u0438\u0442 50 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u2014 \u0436\u0451\u0441\u0442\u043a\u0438\u0439.<\/strong> \u041c\u043e\u0436\u043d\u043e \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u0447\u0435\u0440\u0435\u0437 <code>?limit=20<\/code>, \u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u2014 \u043d\u0435\u0442, \u043e\u0442\u0440\u0435\u0436\u0435\u0442 \u043c\u043e\u043b\u0447\u0430. \u042f \u0434\u0435\u0440\u0436\u0443 <code>PAGE_SIZE = 50<\/code> \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u043e\u0439 \u0438 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u044e.<\/p>\n<p><strong>3. <\/strong><code><strong>select: [\"*\", \"UF_*\"]<\/strong><\/code> \u2014 \u043d\u0443\u0436\u043d\u043e \u044f\u0432\u043d\u043e \u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u043e\u043b\u044f. \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e <code>crm.lead.list<\/code> \u043e\u0442\u0434\u0430\u0451\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f, \u0438 \u0432\u0441\u0435 \u0432\u0430\u0448\u0438 <code>UF_CRM_*<\/code> (\u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043e\u043c\u043e\u0432, \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u044b, \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u044b) \u043e\u0441\u0442\u0430\u043d\u0443\u0442\u0441\u044f \u0437\u0430 \u0431\u043e\u0440\u0442\u043e\u043c. \u042d\u0442\u043e \u0441\u0430\u043c\u0430\u044f \u0447\u0430\u0441\u0442\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u2014 \u043c\u0438\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u043b\u0438\u0434\u044b \u0431\u0435\u0437 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u044b \u0432\u0430\u0436\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u0438 \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0434\u0435\u043b\u044e.<\/p>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 <a href=\"http:\/\/crm.deal\" rel=\"noopener noreferrer nofollow\"><code>crm.deal<\/code><\/a><code>.list<\/code>, <a href=\"http:\/\/crm.contact\" rel=\"noopener noreferrer nofollow\"><code>crm.contact<\/code><\/a><code>.list<\/code>, <a href=\"http:\/\/crm.company\" rel=\"noopener noreferrer nofollow\"><code>crm.company<\/code><\/a><code>.list<\/code> \u2014 \u0435\u0434\u0438\u043d\u044b\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u0438.<\/p>\n<h3>Retry \u043d\u0430 rate limit<\/h3>\n<p>\u0423 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u0435\u0441\u0442\u044c \u043b\u0438\u043c\u0438\u0442 ~2 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0443 \u043d\u0430 webhook (\u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0442\u0430\u043c \u0441\u043a\u043e\u043b\u044c\u0437\u044f\u0449\u0435\u0435 \u043e\u043a\u043d\u043e, \u043d\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044c \u043d\u0430 2 RPS). \u041f\u0440\u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0431\u0430\u0437\u044b \u043d\u0430 50 000 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0443\u043c <code>50 000 \/ 50 \/ 2 = 500 \u0441\u0435\u043a\u0443\u043d\u0434<\/code> \u0447\u0442\u0435\u043d\u0438\u044f. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0434\u043e\u043b\u044c\u0448\u0435 \u0438\u0437-\u0437\u0430 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0437\u0430\u0434\u0435\u0440\u0436\u0435\u043a.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443\u043f\u0435\u0440\u0435\u0442\u044c\u0441\u044f \u0432 \u043b\u0438\u043c\u0438\u0442 \u2014 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>error: QUERY_LIMIT_EXCEEDED<\/code>. \u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0441 retry \u043d\u0430 \u044d\u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439:<\/p>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/client.tsconst MAX_ATTEMPTS = 4;const REQUEST_TIMEOUT_MS = 15_000; \/\/ \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0434\u043b\u044f outbound \u2014 list \u0442\u044f\u0436\u0435\u043b\u0435\u0435 addconst BASE_DELAY_MS = 600;type Side = \"source\" | \"target\";const URLS: Record&lt;Side, string&gt; = {  source: process.env.BITRIX_SOURCE_WEBHOOK_URL!,  target: process.env.BITRIX_TARGET_WEBHOOK_URL!,};export async function bitrixRequest(  side: Side,  method: string,  payload: Record&lt;string, unknown&gt;) {  const url = `${URLS[side]}${method}.json`;  for (let attempt = 1; attempt &lt;= MAX_ATTEMPTS; attempt++) {    const controller = new AbortController();    const timer = setTimeout(() =&gt; controller.abort(), REQUEST_TIMEOUT_MS);    try {      const response = await fetch(url, {        method: \"POST\",        headers: { \"Content-Type\": \"application\/json\" },        body: JSON.stringify(payload),        signal: controller.signal,      });      const text = await response.text();      const json = text ? JSON.parse(text) : {};      \/\/ Rate limit \u2014 \u0436\u0434\u0451\u043c \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u043c      if (json.error === \"QUERY_LIMIT_EXCEEDED\") {        await sleep(BASE_DELAY_MS * attempt * 2);        continue;      }      if (!response.ok || json.error) {        throw new Error(          json.error_description || json.error || `HTTP ${response.status}`        );      }      return json;    } catch (error) {      if (attempt &gt;= MAX_ATTEMPTS) throw error;      await sleep(BASE_DELAY_MS * attempt);    } finally {      clearTimeout(timer);    }  }  throw new Error(`${method}: retries exhausted`);}const sleep = (ms: number) =&gt; new Promise((r) =&gt; setTimeout(r, ms));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 backoff, \u0447\u0435\u0442\u044b\u0440\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u0434 <code>QUERY_LIMIT_EXCEEDED<\/code> \u0441 \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u043d\u043e\u0439 \u043f\u0430\u0443\u0437\u043e\u0439. \u0411\u043e\u043b\u044c\u0448\u0435\u0433\u043e \u043d\u0430 \u0440\u0430\u0437\u043e\u0432\u043e\u0439 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u2014 \u044d\u043a\u0441\u043f\u043e\u043d\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 backoff \u0441 jitter \u0438 circuit breaker \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0434\u043b\u044f \u043f\u0440\u043e\u0434-\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432.<\/p>\n<hr\/>\n<h4>\u0418\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c: ORIGINATOR_ID + ORIGIN_ID \u043a\u0430\u043a \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043a\u043b\u044e\u0447<\/h4>\n<p>\u042d\u0442\u043e \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u0438. \u0415\u0441\u043b\u0438 \u0432 migration toolkit \u043d\u0435\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u0438 \u2014 \u043e\u043d \u043d\u0435 migration toolkit, \u0430 \u0441\u043a\u0440\u0438\u043f\u0442 \u00ab\u0441\u043b\u0435\u0439-\u0437\u0430\u043b\u0435\u0439\u00bb.<\/p>\n<p>\u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0434\u0430\u0451\u0442 \u0434\u0432\u0430 \u043f\u043e\u043b\u044f \u0434\u043b\u044f \u043f\u043e\u043c\u0435\u0442\u043a\u0438 \u0437\u0430\u043f\u0438\u0441\u0435\u0439, \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0438\u0445 \u0438\u0437 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430:<\/p>\n<ul>\n<li>\n<p><code><strong>ORIGINATOR_ID<\/strong><\/code> \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0438\u0441\u0442\u0435\u043c\u044b-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430. \u042f \u043a\u043b\u0430\u0434\u0443 \u0441\u044e\u0434\u0430 \u0445\u0435\u0448 URL source-\u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u0438\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0439 \u043b\u0435\u0439\u0431\u043b \u0432\u0440\u043e\u0434\u0435 <code>bitrix-old-prod<\/code>.<\/p>\n<\/li>\n<li>\n<p><code><strong>ORIGIN_ID<\/strong><\/code> \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0435. \u0421\u044e\u0434\u0430 \u043a\u043b\u0430\u0434\u0443 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 <code>ID<\/code> \u043b\u0438\u0434\u0430\/\u0441\u0434\u0435\u043b\u043a\u0438\/\u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0430 \u0438\u0437 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0437\u0430\u043f\u0438\u0441\u0438 \u0447\u0435\u0440\u0435\u0437 <code>crm.lead.add<\/code> \u0438 \u0430\u043d\u0430\u043b\u043e\u0433\u0438 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 <strong>\u0441\u0430\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442<\/strong>, \u043d\u0435\u0442 \u043b\u0438 \u0443\u0436\u0435 \u0432 target-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0441 \u0442\u0430\u043a\u043e\u0439 \u043f\u0430\u0440\u043e\u0439 <code>(ORIGINATOR_ID, ORIGIN_ID)<\/code>. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f ID \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u042d\u0442\u043e \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430, \u043d\u0435 \u043d\u0430\u0448 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434.<\/p>\n<p>\u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 \u043a\u043e\u0434\u0435:<\/p>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/leads.tsimport { bitrixRequest } from \".\/client\";import { iterateLeads } from \".\/source\";import { mapLead } from \".\/mapping\";const ORIGINATOR_ID = \"bitrix-old-prod\"; \/\/ \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u043d\u0430 \u0432\u0435\u0441\u044c \u043f\u0440\u043e\u0433\u043e\u043dexport async function migrateLeads() {  let migrated = 0;  let skipped = 0;  for await (const sourceLead of iterateLeads()) {    const targetPayload = await mapLead(sourceLead);    const response = await bitrixRequest(\"target\", \"crm.lead.add\", {      fields: {        ...targetPayload,        ORIGINATOR_ID,        ORIGIN_ID: String(sourceLead.ID),      },      params: { REGISTER_SONET_EVENT: \"N\" }, \/\/ \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432 \u043b\u0435\u043d\u0442\u0435    });    if (response.result) {      migrated++;      logSuccess(sourceLead.ID, response.result);    } else {      skipped++;      logSkip(sourceLead.ID, response.error);    }  }  console.log(`Leads: migrated=${migrated}, skipped=${skipped}`);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0442\u043e\u0433\u043e \u0436\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u2014 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0443\u0432\u0438\u0434\u0438\u0442, \u0447\u0442\u043e \u043b\u0438\u0434 \u0441 <code>ORIGINATOR_ID = \"bitrix-old-prod\"<\/code> \u0438 <code>ORIGIN_ID = \"12345\"<\/code> \u0443\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u043d, \u0438 \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u0442 \u0434\u0443\u0431\u043b\u044c. \u042d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u0447\u0435\u0442\u044b\u0440\u0451\u0445 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u0445: <code>crm.lead.add<\/code>, <a href=\"http:\/\/crm.deal\" rel=\"noopener noreferrer nofollow\"><code>crm.deal<\/code><\/a><code>.add<\/code>, <a href=\"http:\/\/crm.contact\" rel=\"noopener noreferrer nofollow\"><code>crm.contact<\/code><\/a><code>.add<\/code>, <a href=\"http:\/\/crm.company\" rel=\"noopener noreferrer nofollow\"><code>crm.company<\/code><\/a><code>.add<\/code>.<\/p>\n<p>\u0414\u0432\u0430 \u0432\u0430\u0436\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043b\u043e\u0432\u044f\u0442\u0441\u044f:<\/p>\n<p><strong>1. <\/strong><code><strong>ORIGINATOR_ID<\/strong><\/code><strong> \u2014 \u0441\u0442\u0440\u043e\u043a\u0430, \u0438 \u043e\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u043e\u0433\u043e\u043d\u0430\u043c\u0438.<\/strong> \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437 \u0432\u044b \u0437\u0430\u043f\u0438\u0441\u0430\u043b\u0438 <code>ORIGINATOR_ID = \"old-bitrix\"<\/code>, \u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0440\u0430\u0437 \u2014 <code>ORIGINATOR_ID = \"bitrix-old-prod\"<\/code>, \u0442\u043e \u0434\u043b\u044f \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u044d\u0442\u043e \u0440\u0430\u0437\u043d\u044b\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438, \u0438 \u043e\u043d \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0434\u0443\u0431\u043b\u0438. \u042f \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u044e \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0443 \u0432 \u043a\u043e\u0434\u0435 \u0438 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u044e \u0434\u043e \u043a\u043e\u043d\u0446\u0430 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p><strong>2. <\/strong><code><strong>params: { REGISTER_SONET_EVENT: \"N\" }<\/strong><\/code> \u2014 \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043b\u0438\u0434 \u043f\u043e\u0440\u043e\u0434\u0438\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 \u0436\u0438\u0432\u043e\u0439 \u043b\u0435\u043d\u0442\u0435 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430. \u041f\u043e\u0441\u043b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 50 000 \u043b\u0438\u0434\u043e\u0432 \u043b\u0435\u043d\u0442\u0430 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u0441\u044f \u0432 \u043d\u0435\u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0435 \u043f\u043e\u043b\u043e\u0442\u043d\u043e. \u0423 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0441\u0435\u0440\u0434\u0435\u0447\u043d\u044b\u0439 \u043f\u0440\u0438\u0441\u0442\u0443\u043f. \u042d\u0442\u043e\u0442 \u0444\u043b\u0430\u0433 \u044f \u0441\u0442\u0430\u0432\u043b\u044e \u043d\u0430 \u0432\u0441\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 <code>*.add<\/code>-\u0432\u044b\u0437\u043e\u0432\u044b.<\/p>\n<hr\/>\n<h4>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/h4>\n<p>\u041f\u0440\u043e\u0441\u0442\u044b\u0435 \u043f\u043e\u043b\u044f (\u0438\u043c\u044f, \u0442\u0435\u043b\u0435\u0444\u043e\u043d, email) \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u0430\u043a \u0435\u0441\u0442\u044c. \u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u0442\u0440\u0451\u0445 \u0442\u0438\u043f\u0430\u0445 \u043f\u043e\u043b\u0435\u0439.<\/p>\n<h3>Enum-\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f (\u0441\u0442\u0430\u0442\u0443\u0441\u044b, \u0442\u0438\u043f\u044b, \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438)<\/h3>\n<p>\u0412 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0435 \u0441\u0442\u0430\u0442\u0443\u0441 \u043b\u0438\u0434\u0430 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043a\u0430\u043a ID \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <code>STATUS_ID: \"NEW\"<\/code> \u0438\u043b\u0438 <code>SOURCE_ID: \"5\"<\/code>. \u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e <strong>ID \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432 \u0432 source \u0438 target \u043c\u043e\u0433\u0443\u0442 \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c<\/strong>, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0435\u0441\u043b\u0438 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438 \u0432 target \u043f\u0440\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u0440\u0443\u043a\u0430\u043c\u0438.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0435\u0440\u0432\u044b\u0439 \u0448\u0430\u0433 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u2014 \u0432\u044b\u0442\u044f\u043d\u0443\u0442\u044c \u043e\u0431\u0430 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 <code>crm.status.list<\/code> \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u0430\u043f\u043f\u0438\u043d\u0433 \u043f\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c:<\/p>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/mapping\/status.tsimport { bitrixRequest } from \"..\/client\";type StatusEntity = \"STATUS\" | \"SOURCE\" | \"DEAL_STAGE\";let cache: Map&lt;string, Map&lt;string, string&gt;&gt; | null = null;async function buildMap(entity: StatusEntity) {  const sourceList = await bitrixRequest(\"source\", \"crm.status.list\", {    filter: { ENTITY_ID: entity },  });  const targetList = await bitrixRequest(\"target\", \"crm.status.list\", {    filter: { ENTITY_ID: entity },  });  \/\/ \u041a\u043b\u044e\u0447 \u2014 \u043f\u0430\u0440\u0430 (STATUS_ID, NAME). \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e STATUS_ID, \u043f\u043e\u0442\u043e\u043c \u043f\u043e NAME.  const targetByStatusId = new Map&lt;string, string&gt;();  const targetByName = new Map&lt;string, string&gt;();  for (const item of targetList.result) {    targetByStatusId.set(item.STATUS_ID, item.STATUS_ID);    targetByName.set(item.NAME.trim().toLowerCase(), item.STATUS_ID);  }  const map = new Map&lt;string, string&gt;();  for (const item of sourceList.result) {    const sourceId = item.STATUS_ID;    const matchById = targetByStatusId.get(sourceId);    const matchByName = targetByName.get(item.NAME.trim().toLowerCase());    const targetId = matchById ?? matchByName;    if (targetId) {      map.set(sourceId, targetId);    }  }  return map;}export async function getStatusMap(entity: StatusEntity) {  if (!cache) cache = new Map();  let map = cache.get(entity);  if (!map) {    map = await buildMap(entity);    cache.set(entity, map);  }  return map;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0434\u0432\u0443\u0445\u0441\u0442\u0443\u043f\u0435\u043d\u0447\u0430\u0442\u0430\u044f: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043d\u0430\u0439\u0442\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u043e <code>STATUS_ID<\/code> (\u0435\u0441\u043b\u0438 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442 \u2014 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u043c \u0441\u0440\u0430\u0437\u0443), \u043f\u043e\u0442\u043e\u043c \u043f\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u043c\u0443 <code>NAME<\/code> (\u0435\u0441\u043b\u0438 ID \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u2014 \u0432\u044b\u0440\u0443\u0447\u0430\u044e\u0442 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u043e\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f). \u0415\u0441\u043b\u0438 \u043d\u0438 \u0442\u043e, \u043d\u0438 \u0434\u0440\u0443\u0433\u043e\u0435 \u043d\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u044b\u043c \u0432 target, \u0438 \u044d\u0442\u043e \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 <code>migration.log<\/code> \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0431\u043e\u0440\u0430.<\/p>\n<p>\u0422\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0434\u043b\u044f <code>SOURCE_ID<\/code>, \u0441\u0442\u0430\u0434\u0438\u0439 \u0441\u0434\u0435\u043b\u043e\u043a (<code>DEAL_STAGE<\/code>), \u0442\u0438\u043f\u043e\u0432 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438, \u0442\u0438\u043f\u043e\u0432 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0430.<\/p>\n<h3>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 (\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435)<\/h3>\n<p>\u041f\u043e\u043b\u0435 <code>ASSIGNED_BY_ID<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 ID \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d \u043b\u0438\u0434. ID \u0432 source \u0438 target \u043f\u043e\u0447\u0442\u0438 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e \u0440\u0430\u0437\u043d\u044b\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u043a\u0430\u0436\u0434\u044b\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e.<\/p>\n<p>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043f\u043e email \u2014 \u044d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043f\u043e\u043b\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0443 \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u0432 \u0434\u0432\u0443\u0445 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u0445:<\/p>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/mapping\/user.tsconst FALLBACK_USER_ID = \"1\"; \/\/ \u043e\u0431\u044b\u0447\u043d\u043e \u0430\u0434\u043c\u0438\u043dexport async function buildUserMap() {  const sourceUsers = await fetchAllUsers(\"source\");  const targetUsers = await fetchAllUsers(\"target\");  const targetByEmail = new Map&lt;string, string&gt;();  for (const u of targetUsers) {    if (u.EMAIL) targetByEmail.set(u.EMAIL.trim().toLowerCase(), u.ID);  }  const map = new Map&lt;string, string&gt;();  for (const u of sourceUsers) {    if (!u.EMAIL) continue;    const targetId = targetByEmail.get(u.EMAIL.trim().toLowerCase());    map.set(u.ID, targetId ?? FALLBACK_USER_ID);  }  return map;}async function fetchAllUsers(side: \"source\" | \"target\") {  const all: Array&lt;{ ID: string; EMAIL: string }&gt; = [];  let start = 0;  while (true) {    const r = await bitrixRequest(side, \"user.get\", { start });    const batch = r.result ?? [];    all.push(...batch);    if (r.next === undefined) break;    start = r.next;  }  return all;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u2014 \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u0435\u043c \u043d\u0430 <code>FALLBACK_USER_ID<\/code>, \u043e\u0431\u044b\u0447\u043d\u043e \u044d\u0442\u043e \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430. \u0411\u0435\u0437 fallback&#8217;\u0430 \u043b\u0438\u0434\u044b \u0441 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u043c <code>ASSIGNED_BY_ID<\/code> \u0443\u043f\u0430\u0434\u0443\u0442 \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438, \u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u0440\u0435\u0434\u0438 \u043f\u0440\u043e\u0433\u043e\u043d\u0430.<\/p>\n<h3>File-\u043f\u043e\u043b\u044f<\/h3>\n<p>\u0424\u0430\u0439\u043b\u044b (\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u0444\u043e\u0442\u043e, \u0430\u0432\u0430\u0442\u0430\u0440\u044b) \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0435 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 FILES API. \u041f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043f\u043e ID \u043d\u0435\u043b\u044c\u0437\u044f \u2014 \u0443 \u0444\u0430\u0439\u043b\u0430 \u0432 target \u0431\u0443\u0434\u0435\u0442 \u0434\u0440\u0443\u0433\u043e\u0439 ID. \u041f\u0435\u0440\u0435\u043d\u043e\u0441 \u0438\u0434\u0451\u0442 \u0447\u0435\u0440\u0435\u0437 base64:<\/p>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/mapping\/file.tsimport { bitrixRequest } from \"..\/client\";export async function transferFile(  sourceFileUrl: string,  filename: string): Promise&lt;{ fileData: [string, string] }&gt; {  \/\/ 1. \u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0441 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430  const fileResponse = await fetch(sourceFileUrl);  const buffer = Buffer.from(await fileResponse.arrayBuffer());  const base64 = buffer.toString(\"base64\");  \/\/ 2. \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u0442 \u0432 crm.*.add  return {    fileData: [filename, base64],  };}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0432 <code>fields<\/code> \u0446\u0435\u043b\u0435\u0432\u043e\u0433\u043e <code>crm.lead.add<\/code> \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u2014 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u0443\u0435\u0442 base64 \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442 \u0444\u0430\u0439\u043b \u0432 \u0441\u0432\u043e\u0439 FILES API. \u041f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0439 \u043a\u0430\u043c\u0435\u043d\u044c: <strong>base64 \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0432 4\/3 \u0440\u0430\u0437\u0430<\/strong>. \u0424\u0430\u0439\u043b \u043d\u0430 5 \u041c\u0411 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u0441\u044f \u0432 payload \u043d\u0430 ~6.7 \u041c\u0411. \u0423 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u0435\u0441\u0442\u044c \u043b\u0438\u043c\u0438\u0442 \u043d\u0430 \u0440\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 (\u043e\u0431\u044b\u0447\u043d\u043e 30-50 \u041c\u0411), \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0444\u0430\u0439\u043b\u044b (\u0432\u0438\u0434\u0435\u043e, \u0442\u044f\u0436\u0451\u043b\u044b\u0435 PDF) \u043b\u0443\u0447\u0448\u0435 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u0438\u043b\u0438 \u0432\u043e\u0432\u0441\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e.<\/p>\n<h3>\u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f UF_*<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438 <code>UF_*<\/code> \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442 \u043f\u043e ID \u043c\u0435\u0436\u0434\u0443 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u043c\u0438 \u2014 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u0430\u043a \u0435\u0441\u0442\u044c. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442 \u2014 \u043d\u0443\u0436\u043d\u043e \u0435\u0449\u0451 \u043e\u0434\u043d\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 <code>getStatusMap<\/code> \u043f\u043e\u0434 <code>crm.userfield.list<\/code>. \u041d\u0430 \u043c\u043e\u0451\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438 <code>UF_*<\/code> \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u043b\u0438 (target \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u0441\u044f \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0430 source), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u044d\u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439 \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b \u2014 \u043d\u043e \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0434\u0432\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0432\u044b\u0440\u043e\u0441\u0448\u0438\u0445 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430, \u0433\u043e\u0442\u043e\u0432\u044c\u0442\u0435\u0441\u044c \u043a \u0435\u0449\u0451 \u043e\u0434\u043d\u043e\u043c\u0443 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0443.<\/p>\n<hr\/>\n<h4>\u041f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438<\/h4>\n<p>\u041a\u0440\u0430\u0442\u043a\u043e \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u0435\u0442 \u043f\u043e\u0441\u0440\u0435\u0434\u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0438 \u0441\u0442\u043e\u0438\u0442 \u0437\u043d\u0430\u0442\u044c \u0437\u0430\u0440\u0430\u043d\u0435\u0435.<\/p>\n<p><strong>1. Rate limit ~2 RPS.<\/strong> \u041d\u0430 50 000 \u043b\u0438\u0434\u043e\u0432 \u044d\u0442\u043e \u043c\u0438\u043d\u0438\u043c\u0443\u043c ~8-10 \u043c\u0438\u043d\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0441\u0442\u043e\u0433\u043e \u0447\u0442\u0435\u043d\u0438\u044f, \u043f\u043b\u044e\u0441 \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u0436\u0435 \u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u044c, \u043f\u043b\u044e\u0441 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 \u0441\u0435\u0442\u0438. \u0420\u0435\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u043e \u0437\u0430\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c <strong>30-40 \u043c\u0438\u043d\u0443\u0442 \u043d\u0430 50 000 \u0437\u0430\u043f\u0438\u0441\u0435\u0439<\/strong> \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041f\u043e\u043b\u043d\u0430\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f (\u043b\u0438\u0434\u044b + \u0441\u0434\u0435\u043b\u043a\u0438 + \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b + \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 + \u0441\u0432\u044f\u0437\u0438) \u043d\u0430 \u0441\u0440\u0435\u0434\u043d\u0435\u0439 \u0431\u0430\u0437\u0435 \u2014 2-4 \u0447\u0430\u0441\u0430.<\/p>\n<p><strong>2. \u0420\u0430\u0437\u043d\u044b\u0435 ID \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u0432 source \u0438 target.<\/strong> \u041f\u043e\u043b\u0435 <code>UF_CRM_1234567890<\/code> \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0435 \u2260 <code>UF_CRM_0987654321<\/code> \u0432 \u0434\u0440\u0443\u0433\u043e\u043c, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u043e. \u041f\u0435\u0440\u0435\u0434 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0435\u0439 \u0441\u043d\u0438\u043c\u0430\u044e \u0441\u043d\u0430\u043f\u0448\u043e\u0442 \u0447\u0435\u0440\u0435\u0437 <code>crm.userfield.list<\/code> \u0434\u043b\u044f \u043e\u0431\u043e\u0438\u0445 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u043e\u0432 \u0438 \u0434\u0435\u0440\u0436\u0443 \u043c\u0430\u043f\u043f\u0438\u043d\u0433.<\/p>\n<p><strong>3. FILES API \u0447\u0435\u0440\u0435\u0437 base64 \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u0435\u0442 payload.<\/strong> \u0421\u043c. \u0432\u044b\u0448\u0435. \u041f\u043b\u044e\u0441 \u2014 \u043a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b \u044d\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 round-trip \u043a source \u0437\u0430 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435\u043c, \u0447\u0442\u043e \u0435\u0449\u0451 \u0443\u043c\u043d\u043e\u0436\u0430\u0435\u0442 \u0432\u0440\u0435\u043c\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p><strong>4. \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <\/strong><code><strong>next<\/strong><\/code><strong> \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/strong> \u042f \u0432 \u0446\u0438\u043a\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e <strong>\u0438<\/strong> <a href=\"http:\/\/response.next\" rel=\"noopener noreferrer nofollow\"><code>response.next<\/code><\/a><code> === undefined<\/code>, <strong>\u0438<\/strong> <code>leads.length &lt; PAGE_SIZE<\/code>. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e-\u0442\u043e \u043e\u0434\u043d\u043e \u2014 \u043d\u0430 \u043e\u0434\u043d\u0438\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u0431\u0443\u0434\u0435\u0442 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0446\u0438\u043a\u043b, \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 \u2014 \u043e\u0431\u0440\u0443\u0431\u0438\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u043d\u0435\u043f\u043e\u043b\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443.<\/p>\n<p><strong>5. \u0421\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435.<\/strong> \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438, \u043f\u043e\u0442\u043e\u043c \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b (\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0441\u044b\u043b\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438), \u043f\u043e\u0442\u043e\u043c \u0441\u0434\u0435\u043b\u043a\u0438\/\u043b\u0438\u0434\u044b (\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0441\u044b\u043b\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b). \u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043b\u0438\u0434 \u0440\u0430\u043d\u044c\u0448\u0435 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0430 \u2014 <code>CONTACT_ID<\/code> \u0432 target \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u043f\u0443\u0441\u0442\u043e\u0442\u0443. \u042f \u043a\u043e\u0434\u0438\u0440\u0443\u044e \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0436\u0451\u0441\u0442\u043a\u043e \u0432 <code>migrate.ts<\/code>:<\/p>\n<p>typescript<\/p>\n<pre><code>async function main() {  await buildMaps(); \/\/ \u044e\u0437\u0435\u0440\u044b, \u0441\u0442\u0430\u0442\u0443\u0441\u044b, \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438, \u0441\u0442\u0430\u0434\u0438\u0438  await migrateCompanies();  await migrateContacts();  await migrateLeads();  await migrateDeals();}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>6. <\/strong><code><strong>crm.lead.list<\/strong><\/code><strong> \u043e\u0442\u0434\u0430\u0451\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043b\u0438\u0434\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.<\/strong> \u0410\u0440\u0445\u0438\u0432\u043d\u044b\u0435\/\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043d\u0443\u0436\u043d\u043e \u044f\u0432\u043d\u043e \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c \u0447\u0435\u0440\u0435\u0437 <code>filter: { CONVERTED: \"Y\" }<\/code> \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u043f\u0440\u043e\u0445\u043e\u0434\u043e\u043c. \u0418\u043d\u0430\u0447\u0435 \u0432 target \u043d\u0435 \u0443\u0435\u0434\u0435\u0442 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u0438.<\/p>\n<hr\/>\n<h4>\u0427\u0435\u043a-\u043b\u0438\u0441\u0442 dry-run \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0433\u043e\u043d\u043e\u043c \u043d\u0430 \u043f\u0440\u043e\u0434\u0435<\/h4>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e \u043d\u0430 \u0436\u0438\u0432\u043e\u0439 target-\u0438\u043d\u0441\u0442\u0430\u043d\u0441, \u043f\u0440\u043e\u0445\u043e\u0436\u0443 \u043f\u044f\u0442\u044c \u043f\u0443\u043d\u043a\u0442\u043e\u0432:<\/p>\n<ol>\n<li>\n<p><strong>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u043d\u0430 10 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u044f\u0445.<\/strong> \u0411\u0435\u0440\u0443 10 \u043b\u0438\u0434\u043e\u0432 \u0438\u0437 source, \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u044e \u0447\u0435\u0440\u0435\u0437 mapping \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 <code>--dry-run<\/code>, \u043f\u0435\u0447\u0430\u0442\u0430\u044e payload, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0448\u0451\u043b \u0431\u044b \u0432 target. \u0413\u043b\u0430\u0437\u0430\u043c\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e: \u0441\u0442\u0430\u0442\u0443\u0441 \u0441\u043c\u0430\u0442\u0447\u0438\u043b\u0441\u044f, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043c\u0430\u0442\u0447\u0438\u043b\u0441\u044f, \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u043d\u0430 \u043c\u0435\u0441\u0442\u0435, \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.<\/p>\n<\/li>\n<li>\n<p><strong>\u041b\u0438\u043c\u0438\u0442\u044b \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u044b.<\/strong> \u0415\u0441\u043b\u0438 \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 free-tier Bitrix24, \u0443 \u043d\u0435\u0433\u043e <strong>\u043b\u0438\u043c\u0438\u0442 \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043b\u0438\u0434\u043e\u0432 \u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0435<\/strong> (\u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0442\u0430\u0440\u0438\u0444\u0430). \u041d\u0430 \u0442\u0430\u043a\u043e\u043c \u0442\u0430\u0440\u0438\u0444\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f 100 000 \u043b\u0438\u0434\u043e\u0432 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0434\u043e\u0435\u0434\u0435\u0442 \u2014 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c <code>crm.lead.add<\/code> \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0434\u043e \u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u0434\u0435\u043b\u0430\u043d backup target-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430.<\/strong> \u0415\u0441\u043b\u0438 target \u2014 \u043f\u0443\u0441\u0442\u043e\u0439 \u043d\u043e\u0432\u044b\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c. \u0415\u0441\u043b\u0438 \u0432 target \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0438\u043b\u0438 \u0432\u044b \u0434\u043e\u043b\u0438\u0432\u0430\u0435\u0442\u0435 \u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439) \u2014 backup \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d. \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0447\u0435\u0440\u0435\u0437 \u0440\u0430\u0437\u0434\u0435\u043b \u00ab\u0411\u0438\u0442\u0440\u0438\u043a\u044124.\u041c\u0430\u0440\u043a\u0435\u0442\u00bb \u2192 \u00ab\u0420\u0435\u0437\u0435\u0440\u0432\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u00bb.<\/p>\n<\/li>\n<li>\n<p><code><strong>REGISTER_SONET_EVENT: \"N\"<\/strong><\/code> \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0432\u043e \u0432\u0441\u0435 <code>*.add<\/code>-\u0432\u044b\u0437\u043e\u0432\u044b. \u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0441\u043b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043b\u0435\u043d\u0442\u0430 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u0441\u0442\u0430\u043d\u0435\u0442 \u043d\u0435\u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0439 \u043d\u0430 \u043f\u0430\u0440\u0443 \u0434\u043d\u0435\u0439.<\/p>\n<\/li>\n<li>\n<p><code><strong>migration.log<\/strong><\/code><strong> \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b.<\/strong> \u041d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0432 stdout \u2014 \u0432 \u0444\u0430\u0439\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u044f \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0430. \u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043b\u043e\u0433\u0438\u0440\u0443\u044e: <code>source_id<\/code>, <code>target_id<\/code>, <code>status<\/code> (<code>success<\/code> \/ <code>skipped<\/code> \/ <code>error<\/code>), <code>timestamp<\/code>, <code>error_message<\/code> \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c. \u042d\u0442\u043e\u0442 \u043b\u043e\u0433 \u2014 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u0432\u043e\u043f\u0440\u043e\u0441 \u00ab\u0430 \u0447\u0442\u043e \u0441 \u043d\u0430\u0448\u0438\u043c \u043b\u0438\u0434\u043e\u043c #12345?\u00bb \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0434\u0435\u043b\u044e \u043f\u043e\u0441\u043b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<\/ol>\n<p>typescript<\/p>\n<pre><code>\/\/ migrate-toolkit\/src\/log.tsimport { appendFileSync } from \"fs\";const LOG_PATH = `migration-${new Date().toISOString().slice(0, 10)}.log`;export function logRecord(entry: {  entity: string;  sourceId: string;  targetId?: string;  status: \"success\" | \"skipped\" | \"error\";  message?: string;}) {  const line = JSON.stringify({ ...entry, ts: new Date().toISOString() });  appendFileSync(LOG_PATH, line + \"\\n\");}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043b\u043e\u0441\u043a\u0438\u0439 JSON \u2014 \u043f\u043e\u0442\u043e\u043c \u043b\u0435\u0433\u043a\u043e \u0433\u0440\u0435\u043f\u0430\u0442\u044c \u0438 \u043f\u0430\u0440\u0441\u0438\u0442\u044c:<\/p>\n<p>bash<\/p>\n<pre><code># \u0421\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u0438\u0434\u043e\u0432 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u043b\u043e\u0441\u044cgrep '\"entity\":\"lead\"' migration-2026-05-04.log | grep '\"status\":\"success\"' | wc -l# \u0427\u0442\u043e \u0443\u043f\u0430\u043b\u043egrep '\"status\":\"error\"' migration-2026-05-04.log | jq .<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h4>\u0427\u0442\u043e \u0437\u0430\u0431\u0440\u0430\u0442\u044c \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438<\/h4>\n<p>\u0422\u0440\u0438 \u0432\u0435\u0449\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u0434\u0435\u043b\u044f\u044e\u0442 \u0440\u0430\u0431\u043e\u0447\u0438\u0439 migration toolkit \u043e\u0442 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u0441\u043a\u0440\u0438\u043f\u0442\u0430:<\/p>\n<ol>\n<li>\n<p><strong>\u0418\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0447\u0435\u0440\u0435\u0437 <\/strong><code><strong>ORIGINATOR_ID<\/strong><\/code><strong> + <\/strong><code><strong>ORIGIN_ID<\/strong><\/code><strong>.<\/strong> \u042d\u0442\u043e \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430, \u043d\u0435 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434. \u041f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u0440\u043e\u0433\u043e\u043d \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u0442 \u0434\u0443\u0431\u043b\u0438 \u2014 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0441\u0430\u043c \u0441\u0432\u0435\u0440\u044f\u0435\u0442 \u043f\u0430\u0440\u0443 \u043f\u043e\u043b\u0435\u0439 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 ID.<\/p>\n<\/li>\n<li>\n<p><strong>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u043e\u0432 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0434\u043e \u043d\u0430\u0447\u0430\u043b\u0430, \u043d\u0435 \u0432\u043e \u0432\u0440\u0435\u043c\u044f.<\/strong> \u0421\u0442\u0430\u0442\u0443\u0441\u044b \u2014 \u043f\u043e <code>STATUS_ID<\/code> \u0441 \u0444\u043e\u043b\u0431\u044d\u043a\u043e\u043c \u043d\u0430 <code>NAME<\/code>. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u2014 \u043f\u043e email \u0441 \u0444\u043e\u043b\u0431\u044d\u043a\u043e\u043c \u043d\u0430 \u0430\u0434\u043c\u0438\u043d\u0430. \u0411\u0435\u0437 \u044d\u0442\u0438\u0445 \u0434\u0432\u0443\u0445 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u043e\u0432 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0443\u043f\u0430\u0434\u0451\u0442 \u043d\u0430 \u043f\u044f\u0442\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.<\/p>\n<\/li>\n<li>\n<p><code><strong>migration.log<\/strong><\/code><strong> \u043a\u0430\u043a \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043f\u0440\u0430\u0432\u0434\u044b.<\/strong> \u041f\u043b\u043e\u0441\u043a\u0438\u0439 JSON-\u043b\u043e\u0433 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u042d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0434\u0435\u043b\u044e \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u0432\u043e\u043f\u0440\u043e\u0441 \u00ab\u0434\u043e\u0448\u043b\u043e \u043b\u0438 \u0432 target?\u00bb \u0431\u0435\u0437 \u043f\u0435\u0440\u0435\u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u043e\u0432 \u0440\u0443\u043a\u0430\u043c\u0438.<\/p>\n<\/li>\n<\/ol>\n<p>Migration toolkit \u0441 \u044d\u0442\u0438\u043c\u0438 \u0442\u0440\u0435\u043c\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u044f \u0433\u043e\u043d\u044f\u043b \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0432 \u044d\u0442\u043e\u043c \u0433\u043e\u0434\u0443.  \u041e\u043a\u043e\u043b\u043e 80 000 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0441\u0443\u043c\u043c\u0430\u0440\u043d\u043e \u043f\u043e \u0447\u0435\u0442\u044b\u0440\u0451\u043c \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c, \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0433\u043e\u043d\u0430 \u043e\u043a\u043e\u043b\u043e 4.5 \u0447\u0430\u0441\u043e\u0432, \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u043e\u0433\u043e\u043d\u043e\u0432 \u0431\u044b\u043b\u043e \u0434\u0432\u0430 (\u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0430\u0432\u043e\u043a \u0432 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439), \u0434\u0443\u0431\u043b\u0435\u0439 \u0432 target \u2014 \u043d\u043e\u043b\u044c.<\/p>\n<hr\/>\n<p><em>\u042f\u043a\u043e\u0432 \u0420\u0430\u0434\u0447\u0435\u043d\u043a\u043e. \u0414\u0435\u043b\u0430\u044e \u0432\u0435\u0431-\u043f\u0440\u043e\u0434\u0443\u043a\u0442\u044b \u043d\u0430 Next.js. \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u2014 \u043f\u0440\u043e inbound webhook \u043e\u0442 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u0432 Next.js: \u043a\u0430\u043a \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438\u0437 CRM \u0438 \u043d\u0435 \u0443\u043f\u0430\u0441\u0442\u044c \u043e\u0442 \u0442\u044b\u0441\u044f\u0447\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438\u0437 \u043b\u0435\u043d\u0442\u0430-\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.<\/em><\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1031780\/\">https:\/\/habr.com\/ru\/articles\/1031780\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0417\u0430\u0447\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043c\u0435\u0436\u0434\u0443 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u043c\u0438\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 (\u043a\u0430\u043a \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u043b\u0438\u0434\u044b \u0438\u0437 Next.js \u0432 1\u0421 \u0411\u0438\u0442\u0440\u0438\u043a\u0441) \u044f \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u043b outbound-\u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e: \u0441\u0430\u0439\u0442 \u043f\u0438\u0448\u0435\u0442 \u043b\u0438\u0434 \u043a \u0441\u0435\u0431\u0435 \u0432 PostgreSQL, \u0447\u0435\u0440\u0435\u0437 after() \u043e\u0442\u0434\u0430\u0451\u0442 \u0435\u0433\u043e \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441, \u0432 \u0441\u0442\u0440\u043e\u043a\u0443 \u043b\u0438\u0434\u0430 \u043f\u043e\u0434\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442 bitrix_id. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043f\u043e\u043a\u0430 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043e\u0434\u0438\u043d.\u041d\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u0436\u0438\u0437\u043d\u0438 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0440\u0435\u0434\u043a\u043e \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043e\u0434\u0438\u043d. \u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0443\u0436\u043d\u0430 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0430\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430\u043c\u0438, \u044f \u043b\u043e\u0432\u0438\u043b \u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0447\u0435\u0442\u044b\u0440\u0435 \u0440\u0430\u0437\u0430 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0433\u043e\u0434:\u041f\u0435\u0440\u0435\u0435\u0437\u0434 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432. \u041a\u043b\u0438\u0435\u043d\u0442 \u0434\u0435\u0440\u0436\u0430\u043b self-hosted \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043d\u0430 \u0441\u0442\u0430\u0440\u043e\u043c VPS, \u043f\u0435\u0440\u0435\u0435\u0437\u0436\u0430\u0435\u0442 \u043d\u0430 \u043d\u043e\u0432\u044b\u0439. SaaS-\u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u043d\u0430 \u043d\u043e\u0432\u044b\u0439 \u0434\u043e\u043c\u0435\u043d \u2014 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435.\u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 test\/prod. \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d-\u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0435. \u0425\u043e\u0442\u044f\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 staging, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d \u0441\u0440\u0435\u0437 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445, \u0447\u0442\u043e\u0431\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0435\u0437 \u0440\u0438\u0441\u043a\u0430 \u0434\u043b\u044f \u0436\u0438\u0432\u043e\u0439 \u0432\u043e\u0440\u043e\u043d\u043a\u0438.\u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u044e\u0440\u043b\u0438\u0446. \u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0434\u0435\u043b\u0438\u0442\u0441\u044f \u043d\u0430 \u0434\u0432\u0430 \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043b\u0438\u0446\u0430, \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d \u0441\u0432\u043e\u0439 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0441 \u0447\u0430\u0441\u0442\u044c\u044e \u043e\u0431\u0449\u0435\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0431\u0430\u0437\u044b.\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438. \u042f \u043a\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0435 \u043c\u043e\u0433\u0443 \u0433\u043e\u043d\u044f\u0442\u044c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u043f\u043e \u0436\u0438\u0432\u043e\u0439 CRM \u043a\u043b\u0438\u0435\u043d\u0442\u0430. \u041c\u043d\u0435 \u043d\u0443\u0436\u0435\u043d \u0438\u043d\u0441\u0442\u0430\u043d\u0441 \u0441 \u0437\u0435\u0440\u043a\u0430\u043b\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043b\u0430\u0436\u0438\u0432\u0430\u0442\u044c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440 \u0431\u0435\u0437 \u0440\u0438\u0441\u043a\u0430 \u043d\u0430\u043b\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0434\u0435.\u0412\u043e \u0432\u0441\u0435\u0445 \u0447\u0435\u0442\u044b\u0440\u0451\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0437\u0430\u0434\u0430\u0447\u0430 \u043e\u0434\u043d\u0430: \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043b\u0438\u0434\u044b, \u0441\u0434\u0435\u043b\u043a\u0438, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b, \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0438\u0437 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 \u0432 target-\u0438\u043d\u0441\u0442\u0430\u043d\u0441, \u043d\u0435 \u043f\u043b\u043e\u0434\u044f \u0434\u0443\u0431\u043b\u0438 \u043f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u0440\u043e\u0433\u043e\u043d\u0430\u0445. \u0422\u043e \u0435\u0441\u0442\u044c \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043a\u0440\u0438\u043f\u0442 \u00ab\u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0437\u0430\u043b\u0438\u0442\u044c \u0438 \u0437\u0430\u0431\u044b\u0442\u044c\u00bb, \u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c 5 \u0440\u0430\u0437 \u2014 \u0438 \u043f\u044f\u0442\u044b\u0439 \u0440\u0430\u0437 \u043e\u043d \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0435\u0449\u0451 \u043f\u044f\u0442\u044c \u043a\u043e\u043f\u0438\u0439 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043b\u0438\u0434\u0430.\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u2014 \u043f\u0430\u0442\u0442\u0435\u0440\u043d migration toolkit, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u0435\u0439\u0441\u0430 \u043d\u0435\u0434\u0432\u0438\u0436\u0438\u043c\u043e\u0441\u0442\u0438. \u041e\u0434\u0438\u043d Node-\u0441\u043a\u0440\u0438\u043f\u0442, \u0434\u0432\u0430 webhook URL \u0432 env-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445, \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0411\u0414. \u0418\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 ORIGINATOR_ID + ORIGIN_ID \u2014 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0433\u043b\u0430\u0432\u043d\u043e\u0435, \u0447\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442 migration toolkit \u043e\u0442 \u043d\u0430\u0438\u0432\u043d\u043e\u0433\u043e \u00ab\u0441\u043b\u0438\u043b-\u0437\u0430\u043b\u0438\u043b\u00bb.\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u043e\u0434\u0438\u043d \u0441\u043a\u0440\u0438\u043f\u0442, \u0434\u0432\u0430 webhookMigration toolkit \u043d\u0435 \u043d\u0443\u0436\u0435\u043d \u043a\u0430\u043a \u0441\u0435\u0440\u0432\u0438\u0441. \u042d\u0442\u043e \u0440\u0430\u0437\u043e\u0432\u044b\u0439 CLI-\u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u043c \u0440\u0443\u043a\u0430\u043c\u0438, \u0441\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0441 \u043b\u043e\u0433\u043e\u043c \u0438 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e.\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2502  BITRIX_SOURCE     \u2502  \u2500\u2500 crm.lead.list \u2500\u2500\u2500\u2500\u2500\u25b6     \u2502   migrate.ts       \u2502\u2502  (\u043e\u0442\u043a\u0443\u0434\u0430 \u0442\u044f\u043d\u0435\u043c)    \u2502     crm.deal.list            \u2502   &#8212; pagination     \u2502\u2502                    \u2502     crm.contact.list         \u2502   &#8212; normalization  \u2502\u2502                    \u2502     crm.company.list         \u2502   &#8212; mapping        \u2502\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                              \u2502                    \u2502                                                    \u2502                    \u2502\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                              \u2502                    \u2502\u2502  BITRIX_TARGET     \u2502  \u25c0\u2500\u2500 crm.lead.add \u2500\u2500\u2500\u2500       \u2502                    \u2502\u2502  (\u043a\u0443\u0434\u0430 \u043f\u0438\u0448\u0435\u043c)      \u2502      crm.deal.add            \u2502                    \u2502\u2502                    \u2502      crm.contact.add         \u2502                    \u2502\u2502                    \u2502      crm.company.add         \u2502                    \u2502\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                                                            \u2502                                                            \u25bc                                                   \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                                                   \u2502  migration.log  \u2502                                                   \u2502  (\u043f\u043b\u043e\u0441\u043a\u0438\u0439 JSON) \u2502                                                   \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u0427\u0442\u043e \u0432 \u044d\u0442\u043e\u0439 \u0441\u0445\u0435\u043c\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442:\u041d\u0435\u0442 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0439 \u0411\u0414. \u0414\u0430\u043d\u043d\u044b\u0435 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u0431\u0430\u0442\u0447\u0430\u043c\u0438 \u0432 \u043f\u0430\u043c\u044f\u0442\u044c (\u043f\u043e 50 \u0437\u0430\u043f\u0438\u0441\u0435\u0439), \u043c\u0430\u043f\u044f\u0442\u0441\u044f, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 target \u0438 \u0437\u0430\u0431\u044b\u0432\u0430\u044e\u0442\u0441\u044f. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0443\u043f\u0430\u0434\u0451\u0442 \u2014 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c, \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0438\u0442 \u043e\u0442 \u0434\u0443\u0431\u043b\u0435\u0439.\u041d\u0435\u0442 \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432 \u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439. \u042d\u0442\u043e \u0440\u0430\u0437\u043e\u0432\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f, \u043d\u0435 daemon. Redis\/BullMQ \u0442\u0443\u0442 \u2014 over-engineering. \u0415\u0441\u043b\u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 6 \u0447\u0430\u0441\u043e\u0432 \u2014 \u043f\u0443\u0441\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 6 \u0447\u0430\u0441\u043e\u0432 \u0432 screen\/tmux.\u041d\u0435\u0442 \u0434\u0432\u0443\u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0439 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438. \u042d\u0442\u043e mi\u0433\u0440\u0430\u0446\u0438\u044f, \u0430 \u043d\u0435 sync. Source \u2192 target, \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u043e\u0440\u043e\u043d\u0443. \u041f\u043e\u0441\u043b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 source \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043a \u0430\u0440\u0445\u0438\u0432.\u041a\u043e\u043d\u0444\u0438\u0433 \u2014 \u0434\u0432\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f:bash# .envBITRIX_SOURCE_WEBHOOK_URL=https:\/\/old-account.bitrix24.ru\/rest\/1\/abc123def456\/BITRIX_TARGET_WEBHOOK_URL=https:\/\/new-account.bitrix24.ru\/rest\/1\/xyz789ghi012\/Webhook \u0432\u043c\u0435\u0441\u0442\u043e OAuth-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u0442\u0435\u043c \u0436\u0435 \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c, \u0447\u0442\u043e \u0438 \u0432 outbound-\u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438: \u043d\u0435 \u043d\u0443\u0436\u0435\u043d Marketplace-\u0430\u043f\u0440\u0443\u0432, \u0441\u043a\u043e\u0443\u043f\u044b \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0432\u0435\u0431\u0445\u0443\u043a\u0430, \u0442\u043e\u043a\u0435\u043d \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0439 env. \u041c\u0438\u043d\u0443\u0441 \u2014 \u0442\u043e\u043a\u0435\u043d \u0432 URL, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u043e: \u043d\u0438 \u0432 \u043b\u043e\u0433\u0438, \u043d\u0438 \u0432 Sentry \u043f\u043e\u043b\u043d\u044b\u0439 URL \u043d\u0435 \u043f\u0438\u0448\u0435\u0442\u0441\u044f. \u0422\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u0438 payload.\u0427\u0442\u0435\u043d\u0438\u0435 source: crm.*.list + \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f\u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043e\u0442\u0434\u0430\u0451\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u043c\u0435\u0439\u0441\u0442\u0432\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 crm.{entity}.list. \u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u0432\u044b\u0437\u043e\u0432:typescript\/\/ migrate-toolkit\/src\/source.tsimport { bitrixRequest } from &#171;.\/client&#187;;const PAGE_SIZE = 50; \/\/ \u0416\u0451\u0441\u0442\u043a\u0438\u0439 \u043b\u0438\u043c\u0438\u0442 Bitrix24, \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u043b\u044c\u0437\u044fexport async function* iterateLeads(): AsyncGenerator&lt;BitrixLead&gt; {  let start = 0;  while (true) {    const response = await bitrixRequest(&#171;source&#187;, &#171;crm.lead.list&#187;, {      start,      order: { ID: &#171;ASC&#187; },      filter: {}, \/\/ \u0431\u0435\u0437 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u2014 \u0442\u044f\u043d\u0435\u043c \u0432\u0441\u0451      select: [&#171;*&#187;, &#171;UF_*&#187;], \/\/ \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u043e\u043b\u044f    });    const leads: BitrixLead[] = response.result ?? [];    for (const lead of leads) {      yield lead;    }    \/\/ \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 next \u0432 \u0432\u0438\u0434\u0435 \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b    if (response.next === undefined || leads.length &lt; PAGE_SIZE) {      return;    }    start = response.next;  }}\u0422\u0440\u0438 \u043c\u043e\u043c\u0435\u043d\u0442\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0442\u044b\u043a\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435:1. start \u2014 \u044d\u0442\u043e \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435, \u043d\u0435 \u043d\u043e\u043c\u0435\u0440 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0415\u0441\u043b\u0438 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 50 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0438 \u0432\u044b \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b\u0438 10 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u2014 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 start = 500. \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043e\u0442\u0434\u0430\u0451\u0442 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 response.next, \u0438 \u043f\u0440\u043e\u0449\u0435 \u0434\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0435\u043c\u0443, \u0447\u0435\u043c \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u043c\u0443.2. \u041b\u0438\u043c\u0438\u0442 50 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u2014 \u0436\u0451\u0441\u0442\u043a\u0438\u0439. \u041c\u043e\u0436\u043d\u043e \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u0447\u0435\u0440\u0435\u0437 ?limit=20, \u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u2014 \u043d\u0435\u0442, \u043e\u0442\u0440\u0435\u0436\u0435\u0442 \u043c\u043e\u043b\u0447\u0430. \u042f \u0434\u0435\u0440\u0436\u0443 PAGE_SIZE = 50 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u043e\u0439 \u0438 \u043d\u0435 \u0442\u0440\u043e\u0433\u0430\u044e.3. select: [&#171;*&#187;, &#171;UF_*&#187;] \u2014 \u043d\u0443\u0436\u043d\u043e \u044f\u0432\u043d\u043e \u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u043e\u043b\u044f. \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e crm.lead.list \u043e\u0442\u0434\u0430\u0451\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f, \u0438 \u0432\u0441\u0435 \u0432\u0430\u0448\u0438 UF_CRM_* (\u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043e\u043c\u043e\u0432, \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u044b, \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u044b) \u043e\u0441\u0442\u0430\u043d\u0443\u0442\u0441\u044f \u0437\u0430 \u0431\u043e\u0440\u0442\u043e\u043c. \u042d\u0442\u043e \u0441\u0430\u043c\u0430\u044f \u0447\u0430\u0441\u0442\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u2014 \u043c\u0438\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u043b\u0438\u0434\u044b \u0431\u0435\u0437 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u044b \u0432\u0430\u0436\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u0438 \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0434\u0435\u043b\u044e.\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 crm.deal.list, crm.contact.list, crm.company.list \u2014 \u0435\u0434\u0438\u043d\u044b\u0439 \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u0438.Retry \u043d\u0430 rate limit\u0423 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430 \u0435\u0441\u0442\u044c \u043b\u0438\u043c\u0438\u0442 ~2 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0443 \u043d\u0430 webhook (\u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0442\u0430\u043c \u0441\u043a\u043e\u043b\u044c\u0437\u044f\u0449\u0435\u0435 \u043e\u043a\u043d\u043e, \u043d\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044c \u043d\u0430 2 RPS). \u041f\u0440\u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0431\u0430\u0437\u044b \u043d\u0430 50 000 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0443\u043c 50 000 \/ 50 \/ 2 = 500 \u0441\u0435\u043a\u0443\u043d\u0434 \u0447\u0442\u0435\u043d\u0438\u044f. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0434\u043e\u043b\u044c\u0448\u0435 \u0438\u0437-\u0437\u0430 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0437\u0430\u0434\u0435\u0440\u0436\u0435\u043a.\u0415\u0441\u043b\u0438 \u0443\u043f\u0435\u0440\u0435\u0442\u044c\u0441\u044f \u0432 \u043b\u0438\u043c\u0438\u0442 \u2014 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 error: QUERY_LIMIT_EXCEEDED. \u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0441 retry \u043d\u0430 \u044d\u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439:typescript\/\/ migrate-toolkit\/src\/client.tsconst MAX_ATTEMPTS = 4;const REQUEST_TIMEOUT_MS = 15_000; \/\/ \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0434\u043b\u044f outbound \u2014 list \u0442\u044f\u0436\u0435\u043b\u0435\u0435 addconst BASE_DELAY_MS = 600;type Side = &#171;source&#187; | &#171;target&#187;;const URLS: Record&lt;Side, string&gt; = {  source: process.env.BITRIX_SOURCE_WEBHOOK_URL!,  target: process.env.BITRIX_TARGET_WEBHOOK_URL!,};export async function bitrixRequest(  side: Side,  method: string,  payload: Record&lt;string, unknown&gt;) {  const url = `${URLS[side]}${method}.json`;  for (let attempt = 1; attempt &lt;= MAX_ATTEMPTS; attempt++) {    const controller = new AbortController();    const timer = setTimeout(() =&gt; controller.abort(), REQUEST_TIMEOUT_MS);    try {      const response = await fetch(url, {        method: &#171;POST&#187;,        headers: { &#171;Content-Type&#187;: &#171;application\/json&#187; },        body: JSON.stringify(payload),        signal: controller.signal,      });      const text = await response.text();      const json = text ? JSON.parse(text) : {};      \/\/ Rate limit \u2014 \u0436\u0434\u0451\u043c \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u043c      if (json.error === &#171;QUERY_LIMIT_EXCEEDED&#187;) {        await sleep(BASE_DELAY_MS * attempt * 2);        continue;      }      if (!response.ok || json.error) {        throw new Error(          json.error_description || json.error || `HTTP ${response.status}`        );      }      return json;    } catch (error) {      if (attempt &gt;= MAX_ATTEMPTS) throw error;      await sleep(BASE_DELAY_MS * attempt);    } finally {      clearTimeout(timer);    }  }  throw new Error(`${method}: retries exhausted`);}const sleep = (ms: number) =&gt; new Promise((r) =&gt; setTimeout(r, ms));\u041b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 backoff, \u0447\u0435\u0442\u044b\u0440\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u043f\u043e\u0434 QUERY_LIMIT_EXCEEDED \u0441 \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u043d\u043e\u0439 \u043f\u0430\u0443\u0437\u043e\u0439. \u0411\u043e\u043b\u044c\u0448\u0435\u0433\u043e \u043d\u0430 \u0440\u0430\u0437\u043e\u0432\u043e\u0439 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u2014 \u044d\u043a\u0441\u043f\u043e\u043d\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 backoff \u0441 jitter \u0438 circuit breaker \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0434\u043b\u044f \u043f\u0440\u043e\u0434-\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432.\u0418\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c: ORIGINATOR_ID + ORIGIN_ID \u043a\u0430\u043a \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043a\u043b\u044e\u0447\u042d\u0442\u043e \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u0438. \u0415\u0441\u043b\u0438 \u0432 migration toolkit \u043d\u0435\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u0438 \u2014 \u043e\u043d \u043d\u0435 migration toolkit, \u0430 \u0441\u043a\u0440\u0438\u043f\u0442 \u00ab\u0441\u043b\u0435\u0439-\u0437\u0430\u043b\u0435\u0439\u00bb.\u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0434\u0430\u0451\u0442 \u0434\u0432\u0430 \u043f\u043e\u043b\u044f \u0434\u043b\u044f \u043f\u043e\u043c\u0435\u0442\u043a\u0438 \u0437\u0430\u043f\u0438\u0441\u0435\u0439, \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0438\u0445 \u0438\u0437 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430:ORIGINATOR_ID \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0438\u0441\u0442\u0435\u043c\u044b-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430. \u042f \u043a\u043b\u0430\u0434\u0443 \u0441\u044e\u0434\u0430 \u0445\u0435\u0448 URL source-\u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u0438\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0439 \u043b\u0435\u0439\u0431\u043b \u0432\u0440\u043e\u0434\u0435 bitrix-old-prod.ORIGIN_ID \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435-\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0435. \u0421\u044e\u0434\u0430 \u043a\u043b\u0430\u0434\u0443 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 ID \u043b\u0438\u0434\u0430\/\u0441\u0434\u0435\u043b\u043a\u0438\/\u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0430 \u0438\u0437 source-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430.\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0437\u0430\u043f\u0438\u0441\u0438 \u0447\u0435\u0440\u0435\u0437 crm.lead.add \u0438 \u0430\u043d\u0430\u043b\u043e\u0433\u0438 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0441\u0430\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0435\u0442 \u043b\u0438 \u0443\u0436\u0435 \u0432 target-\u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0441 \u0442\u0430\u043a\u043e\u0439 \u043f\u0430\u0440\u043e\u0439 (ORIGINATOR_ID, ORIGIN_ID). \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f ID \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u042d\u0442\u043e \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430, \u043d\u0435 \u043d\u0430\u0448 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434.\u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 \u043a\u043e\u0434\u0435:typescript\/\/ migrate-toolkit\/src\/leads.tsimport { bitrixRequest } from &#171;.\/client&#187;;import { iterateLeads } from &#171;.\/source&#187;;import { mapLead } from &#171;.\/mapping&#187;;const ORIGINATOR_ID = &#171;bitrix-old-prod&#187;; \/\/ \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u043d\u0430 \u0432\u0435\u0441\u044c \u043f\u0440\u043e\u0433\u043e\u043dexport async function migrateLeads() {  let migrated = 0;  let skipped = 0;  for await (const sourceLead of iterateLeads()) {    const targetPayload = await mapLead(sourceLead);    const response = await bitrixRequest(&#171;target&#187;, &#171;crm.lead.add&#187;, {      fields: {        &#8230;targetPayload,        ORIGINATOR_ID,        ORIGIN_ID: String(sourceLead.ID),      },      params: { REGISTER_SONET_EVENT: &#171;N&#187; }, \/\/ \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432 \u043b\u0435\u043d\u0442\u0435    });    if (response.result) {      migrated++;      logSuccess(sourceLead.ID, response.result);    } else {      skipped++;      logSkip(sourceLead.ID, response.error);    }  }  console.log(`Leads: migrated=${migrated},&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-478679","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/478679","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=478679"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/478679\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=478679"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=478679"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=478679"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}