work-license-server 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/assets/landing.css +41 -0
- package/assets/landing.js +59 -10
- package/package.json +1 -1
- package/src/agent.js +93 -15
- package/src/bots.js +1 -1
- package/src/catalog.js +19 -12
- package/src/landing.js +143 -94
- package/src/store.js +8 -6
- package/src/yookassa.js +1 -1
package/README.md
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
1. **Витрина `/`** — вкладки «Платформа», «ИИ-агенты», «Тарифы», «Комплекс», «Докупка лицензий» (только после входа), «Франшиза», «Поддержка»; светлая и тёмная темы, мягкий анимированный фон (графики роста и сеть связей).
|
|
8
8
|
- Комплекс: **AMD Ryzen™ AI Max+ PRO 495 · Radeon™ 8065S · 192 ГБ LPDDR5x-8533 · SSD 4 ТБ**.
|
|
9
9
|
- Внутри: встроенная система учёта, документооборот, управление процессами, ИИ-консультант в каждой форме, мультиагентная система (13 ролевых агентов, 26+ навыков) и 4 типа локальных моделей (языковая, голосовая, генерация изображений, векторный поиск).
|
|
10
|
-
- Базовый комплект — `5 000 000 ₽`: **вечная лицензия на
|
|
10
|
+
- Базовый комплект — `5 000 000 ₽`: **вечная лицензия на 10 одновременных сессий** и **1-й год поддержки уровня Enterprise** (далее продление поддержки и докупка мест — по желанию).
|
|
11
|
+
- Интегрированные на комплексе 4 типа моделей предназначены для базовых ежедневных операций внутри контура; при необходимости более мощных ИИ они подключаются через API из любого источника.
|
|
11
12
|
2. **Годовые лицензии на дополнительных пользователей** со скидкой за количество (5+ мест −5% … 100+ мест −25%):
|
|
12
13
|
- **Start** — `10 000 ₽ / год` (до 12 обращений в год на место, реакция до 8 рабочих часов);
|
|
13
14
|
- **Business** — `15 000 ₽ / год` (до 48 обращений, реакция до 2 часов);
|
|
14
15
|
- **Enterprise** — `20 000 ₽ / год` (безлимит, реакция до 15 минут 24/7).
|
|
15
|
-
3. **Региональная франшиза** — интерактивная карта всех субъектов РФ (`assets/russia-regions.json`, границы © участники OpenStreetMap), квота франшиз пропорциональна населению,
|
|
16
|
+
3. **Региональная франшиза** — интерактивная карта всех субъектов РФ (`assets/russia-regions.json`, границы © участники OpenStreetMap), квота франшиз пропорциональна населению, паушальный взнос — `500 000 ₽ ×` индекс субъекта, резерв — невозвратный залог `100 000 ₽` (`POST /api/partners/reserve`), полная оплата паушального взноса — в течение 1 года.
|
|
16
17
|
4. **ЮKassa** (`/api/orders/checkout`, `/api/payments/yookassa/webhook`) — чеки 54-ФЗ, выпуск и продление сертификатов `Ed25519` (`WORK-LIC-v1`) с привязкой к `HWID` и ключу узла `#system/node-key.json`.
|
|
17
18
|
5. **Боты поддержки** — `/api/bots/max/webhook`, `/api/bots/telegram/webhook`, `/api/bots/vk/webhook`.
|
|
18
19
|
6. **ИИ-агент** — `POST /api/agent/chat`: консультации, расчёт лицензий, вакантные франшизы по субъекту, контакты.
|
package/assets/landing.css
CHANGED
|
@@ -572,6 +572,47 @@ table.t { width: 100%; border-collapse: collapse; font-size: 13.5px; }
|
|
|
572
572
|
.ai-input { display: flex; gap: 8px; padding: 12px; border-top: 1px solid var(--line); }
|
|
573
573
|
.ai-input input { border-radius: 999px; }
|
|
574
574
|
.ai-input .btn { border-radius: 999px; width: 44px; padding: 0; }
|
|
575
|
+
body[data-stage="admin"] .ai-fab { display: none; }
|
|
576
|
+
|
|
577
|
+
/* ---------- Админка с левой вкладкой ИИ-агента ---------- */
|
|
578
|
+
.admin-workspace { display: grid; grid-template-columns: 380px minmax(0, 1fr); gap: 20px; align-items: start; width: 100%; }
|
|
579
|
+
.admin-workspace.only-agent { grid-template-columns: minmax(0, 1fr); }
|
|
580
|
+
.admin-workspace.only-agent .admin-main { display: none; }
|
|
581
|
+
.admin-left { position: sticky; top: 88px; display: grid; gap: 14px; min-width: 0; }
|
|
582
|
+
.admin-workspace.only-agent .admin-left { position: static; }
|
|
583
|
+
.admin-nav {
|
|
584
|
+
display: grid; grid-template-columns: 1fr 1fr; gap: 6px; padding: 8px;
|
|
585
|
+
border-radius: 18px; border: 1px solid var(--line); background: var(--panel);
|
|
586
|
+
backdrop-filter: blur(18px);
|
|
587
|
+
}
|
|
588
|
+
.adm-tab {
|
|
589
|
+
display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid transparent;
|
|
590
|
+
border-radius: 12px; background: none; cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 650;
|
|
591
|
+
color: var(--text-2); text-align: left; transition: all .2s; min-width: 0;
|
|
592
|
+
}
|
|
593
|
+
.adm-tab span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
594
|
+
.adm-tab svg { width: 16px; height: 16px; flex: none; color: var(--brand-2); }
|
|
595
|
+
.adm-tab:hover { background: color-mix(in srgb, var(--brand) 10%, transparent); color: var(--text); }
|
|
596
|
+
.adm-tab.on {
|
|
597
|
+
background: linear-gradient(135deg, var(--brand), #2563eb); color: #fff;
|
|
598
|
+
box-shadow: 0 10px 24px -10px var(--glow);
|
|
599
|
+
}
|
|
600
|
+
.adm-tab.on svg { color: #fff; }
|
|
601
|
+
.adm-tab .live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 8px var(--ok); margin-left: auto; flex: none; }
|
|
602
|
+
.adm-agent-card { padding: 16px; display: flex; flex-direction: column; min-width: 0; overflow: hidden; border-color: color-mix(in srgb, var(--brand) 38%, var(--line)); }
|
|
603
|
+
.adm-ai-msgs {
|
|
604
|
+
padding: 12px; border-radius: 14px; border: 1px solid var(--line);
|
|
605
|
+
background: color-mix(in srgb, var(--bg-2) 60%, transparent);
|
|
606
|
+
min-height: 290px; max-height: 410px; overflow-x: hidden;
|
|
607
|
+
}
|
|
608
|
+
.admin-workspace.only-agent .adm-ai-msgs { min-height: 420px; max-height: 560px; }
|
|
609
|
+
.admin-main { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; min-width: 0; width: 100%; }
|
|
610
|
+
.admin-main > .card { min-width: 0; overflow: hidden; }
|
|
611
|
+
.admin-main .g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
612
|
+
@media (max-width: 1120px) {
|
|
613
|
+
.admin-workspace { grid-template-columns: minmax(0, 1fr); }
|
|
614
|
+
.admin-left { position: static; }
|
|
615
|
+
}
|
|
575
616
|
|
|
576
617
|
/* ---------- Модальное окно ---------- */
|
|
577
618
|
.modal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; padding: 16px; background: rgba(2, 4, 10, .6); backdrop-filter: blur(10px); opacity: 0; pointer-events: none; transition: opacity .25s; }
|
package/assets/landing.js
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
if (id === 'addons' && !currentUser) { openAuth(); return; }
|
|
57
57
|
if (!$('#stage-' + id)) id = 'product';
|
|
58
58
|
currentStage = id;
|
|
59
|
+
document.body.setAttribute('data-stage', id);
|
|
59
60
|
$$('.tab').forEach(t => t.classList.toggle('active', t.dataset.stage === id));
|
|
60
61
|
$$('.stage').forEach(s => s.classList.toggle('active', s.id === 'stage-' + id));
|
|
61
62
|
const admBtn = $('#btnAdminTop');
|
|
@@ -203,7 +204,7 @@
|
|
|
203
204
|
['Мест × лет', `${seats} × ${years}`],
|
|
204
205
|
['Без скидки', rub(gross)],
|
|
205
206
|
['Скидка', `−${disc.discountPercent}%`],
|
|
206
|
-
['Всего мест после докупки', `${(D.product?.baseSeats ||
|
|
207
|
+
['Всего мест после докупки', `${(D.product?.baseSeats || 10) + seats}`],
|
|
207
208
|
]);
|
|
208
209
|
markLadder('ladderAddons', seats);
|
|
209
210
|
}
|
|
@@ -224,7 +225,7 @@
|
|
|
224
225
|
const rows = [
|
|
225
226
|
['Комплекс ODANT-WORK', rub(D.product.basePriceRub)],
|
|
226
227
|
['Вечные рабочие места', String(D.product.baseSeats)],
|
|
227
|
-
['Поддержка в комплекте', '1 год ·
|
|
228
|
+
['Поддержка в комплекте', '1 год · Enterprise (далее — по желанию)'],
|
|
228
229
|
];
|
|
229
230
|
if (extra > 0) rows.push([`Доп. места · ${esc(tier.name)}${disc.discountPercent ? ' · −' + disc.discountPercent + '%' : ''}`, `${extra} · ${rub(net)}`]);
|
|
230
231
|
$('#lblPakBreakdown').innerHTML = lines(rows);
|
|
@@ -520,7 +521,7 @@
|
|
|
520
521
|
<div class="r"><span>Округ</span><em>${esc(r.district)}</em></div>
|
|
521
522
|
<div class="r"><span>Население</span><em>${String(r.populationMillions).replace('.', ',')} млн</em></div>
|
|
522
523
|
<div class="r"><span>Вакантно</span><em style="color:${vac > 0 ? 'var(--ok)' : 'var(--danger)'}">${vac} из ${q}</em></div>
|
|
523
|
-
<div class="r"><span
|
|
524
|
+
<div class="r"><span>Паушальный взнос</span><em>${rub(feeOf(r))}</em></div>`;
|
|
524
525
|
const br = box.getBoundingClientRect();
|
|
525
526
|
let x = e.clientX - br.left + 16, y = e.clientY - br.top + 16;
|
|
526
527
|
const tw = tip.offsetWidth || 200, th = tip.offsetHeight || 110;
|
|
@@ -696,7 +697,7 @@
|
|
|
696
697
|
const out = [];
|
|
697
698
|
out.push(`Лицензии (${data.licenses?.length || 0})`);
|
|
698
699
|
for (const l of data.licenses || [])
|
|
699
|
-
out.push(` ${l.licenseId} · ${l.customer?.name || '—'} · мест ${l.totalSeats} (${l.baseSeats ??
|
|
700
|
+
out.push(` ${l.licenseId} · ${l.customer?.name || '—'} · мест ${l.totalSeats} (${l.baseSeats ?? 10} вечных + ${l.addonSeats}) · ${l.support?.tierName || ''}`);
|
|
700
701
|
out.push('', `Резервы франшиз (${data.reservations?.length || 0})`);
|
|
701
702
|
for (const r of data.reservations || [])
|
|
702
703
|
out.push(` ${r.reservationId} · ${r.regionName} · ${r.companyName} · залог ${r.depositPaid ? 'оплачен' : 'ожидает оплаты'}`);
|
|
@@ -743,8 +744,8 @@
|
|
|
743
744
|
<td><b>${esc(l.licenseId)}</b><div class="small muted">Код бота: <code>${esc(l.bindingCode || l.licenseId)}</code></div></td>
|
|
744
745
|
<td><b>${esc(l.customer?.name || '—')}</b><div class="small muted">ИНН ${esc(l.customer?.inn || '—')} · ${esc(l.customer?.email || '—')}</div></td>
|
|
745
746
|
<td><code>${esc(l.boundHwid || 'не привязан')}</code><div class="small muted">${esc((l.boundNodeFingerprint || '').slice(0, 18) || 'ожидает синхронизации')}</div></td>
|
|
746
|
-
<td class="num"><b>${l.totalSeats}</b> <span class="muted">(${l.baseSeats ??
|
|
747
|
-
<td><span class="tag ok">${esc(l.support?.tierName || '
|
|
747
|
+
<td class="num"><b>${l.totalSeats}</b> <span class="muted">(${l.baseSeats ?? 10} вечн. + ${l.addonSeats || 0} доп.)</span></td>
|
|
748
|
+
<td><span class="tag ok">${esc(l.support?.tierName || 'Enterprise')}</span><div class="small muted">до ${fmtDate(l.support?.expiresAt)}</div></td>
|
|
748
749
|
<td><button class="btn btn-ghost btn-sm" data-cert="${esc(l.licenseId)}">Сертификат JSON</button></td>
|
|
749
750
|
</tr>`).join('') || '<tr><td colspan="6" class="muted">Лицензий пока нет — нажмите «+ Демо-лицензия ПАК»</td></tr>';
|
|
750
751
|
}
|
|
@@ -772,7 +773,7 @@
|
|
|
772
773
|
<td><b>${esc(r.reservationId)}</b><div class="small muted">Резерв франшизы</div></td>
|
|
773
774
|
<td><b>${esc(r.regionName)}</b> <span class="muted">(${esc(r.regionCode)})</span></td>
|
|
774
775
|
<td><b>${esc(r.companyName)}</b><div class="small muted">ИНН ${esc(r.inn || '—')} · ${esc(r.email || '—')}</div></td>
|
|
775
|
-
<td
|
|
776
|
+
<td>Паушальный взнос <b>${rub(r.franchiseFeeRub)}</b><div class="small muted">Залог ${rub(r.nonRefundableDepositRub || r.depositAmountRub || D.franchise.reservationDepositRub)} · полная оплата за 1 год</div></td>
|
|
776
777
|
<td><span class="tag ${r.depositPaid ? 'ok' : ''}">${r.depositPaid ? 'залог оплачен' : 'ждёт оплаты залога'}</span></td>
|
|
777
778
|
<td><div style="display:flex;gap:6px;flex-wrap:wrap">
|
|
778
779
|
${!r.depositPaid && r.orderId ? `<button class="btn btn-ok btn-sm" data-pay-order="${esc(r.orderId)}">Оплатить залог</button>` : ''}
|
|
@@ -895,6 +896,44 @@
|
|
|
895
896
|
$('#certModal').classList.add('open');
|
|
896
897
|
}
|
|
897
898
|
|
|
899
|
+
function switchAdminTab(tab = 'all') {
|
|
900
|
+
$$('[data-adm-tab]').forEach(b => b.classList.toggle('on', b.dataset.admTab === tab));
|
|
901
|
+
const ws = $('#adminWorkspace');
|
|
902
|
+
if (ws) ws.classList.toggle('only-agent', tab === 'agent');
|
|
903
|
+
$$('[data-adm-sec]').forEach(sec => {
|
|
904
|
+
sec.style.display = (tab === 'all' || tab === sec.dataset.admSec) ? '' : 'none';
|
|
905
|
+
});
|
|
906
|
+
if (tab === 'agent') setTimeout(() => $('#admAiInput')?.focus(), 50);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async function sendAdminAi(text) {
|
|
910
|
+
const inp = $('#admAiInput');
|
|
911
|
+
text = (text ?? inp?.value ?? '').trim();
|
|
912
|
+
if (!text) return;
|
|
913
|
+
if (inp) inp.value = '';
|
|
914
|
+
const box = $('#admAiMessages');
|
|
915
|
+
if (!box) return;
|
|
916
|
+
box.insertAdjacentHTML('beforeend', `<div class="msg me">${esc(text)}</div><div class="msg bot" id="admAiTyping"><span class="typing"><i></i><i></i><i></i></span></div>`);
|
|
917
|
+
box.scrollTop = box.scrollHeight;
|
|
918
|
+
let data;
|
|
919
|
+
try { data = await api('/api/agent/chat', { message: text, sessionKey: currentUser?.sessionKey || '' }); }
|
|
920
|
+
catch (err) { data = { reply: err.message, actions: [] }; }
|
|
921
|
+
$('#admAiTyping')?.remove();
|
|
922
|
+
box.insertAdjacentHTML('beforeend', `<div class="msg bot">${md(data.reply || '')}</div>`);
|
|
923
|
+
box.scrollTop = box.scrollHeight;
|
|
924
|
+
for (const act of data.actions || []) {
|
|
925
|
+
if (act.type === 'refresh_admin') {
|
|
926
|
+
refreshRegistry();
|
|
927
|
+
}
|
|
928
|
+
else if (act.type === 'admin_tab' && act.tab) {
|
|
929
|
+
switchAdminTab(act.tab);
|
|
930
|
+
}
|
|
931
|
+
else if (act.type === 'select_region' && act.regionCode) {
|
|
932
|
+
selectRegion(act.regionCode, { zoom: true, scrollTable: false });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
898
937
|
/* ================= ИИ-агент лицензионного центра ================= */
|
|
899
938
|
function toggleAi(force) {
|
|
900
939
|
const d = $('#aiDrawer');
|
|
@@ -903,7 +942,10 @@
|
|
|
903
942
|
if (open) setTimeout(() => $('#aiInput').focus(), 60);
|
|
904
943
|
}
|
|
905
944
|
function md(text) {
|
|
906
|
-
return esc(text)
|
|
945
|
+
return esc(text)
|
|
946
|
+
.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>')
|
|
947
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
948
|
+
.replace(/(^|\s)\*(.+?)\*(?=\s|$)/g, '$1<i>$2</i>');
|
|
907
949
|
}
|
|
908
950
|
async function sendAi(text) {
|
|
909
951
|
const inp = $('#aiInput');
|
|
@@ -920,7 +962,10 @@
|
|
|
920
962
|
box.insertAdjacentHTML('beforeend', `<div class="msg bot">${md(data.reply || '')}</div>`);
|
|
921
963
|
box.scrollTop = box.scrollHeight;
|
|
922
964
|
for (const act of data.actions || []) {
|
|
923
|
-
if (act.type === '
|
|
965
|
+
if (act.type === 'refresh_admin') {
|
|
966
|
+
refreshRegistry();
|
|
967
|
+
}
|
|
968
|
+
else if (act.type === 'select_region') {
|
|
924
969
|
switchStage('franchise');
|
|
925
970
|
setTimeout(() => selectRegion(act.regionCode, { zoom: true, scrollTable: true }), 80);
|
|
926
971
|
}
|
|
@@ -1101,9 +1146,11 @@
|
|
|
1101
1146
|
/* ================= Привязка событий ================= */
|
|
1102
1147
|
function bind() {
|
|
1103
1148
|
document.addEventListener('click', (e) => {
|
|
1104
|
-
const el = e.target.closest('[data-stage],[data-action],[data-reserve],[data-bot],[data-ask],[data-district],[data-cert],[data-pay-order],[data-activate-res],[data-certify-partner],[data-reply-ticket],th[data-sort],#regionsTableBody tr');
|
|
1149
|
+
const el = e.target.closest('[data-stage],[data-action],[data-reserve],[data-bot],[data-ask],[data-adm-ask],[data-adm-tab],[data-district],[data-cert],[data-pay-order],[data-activate-res],[data-certify-partner],[data-reply-ticket],th[data-sort],#regionsTableBody tr');
|
|
1105
1150
|
if (!el) return;
|
|
1106
1151
|
if (el.matches('[data-reserve]')) { e.stopPropagation(); reserveRegionFranchise(el.dataset.reserve); return; }
|
|
1152
|
+
if (el.matches('[data-adm-tab]')) { switchAdminTab(el.dataset.admTab); return; }
|
|
1153
|
+
if (el.matches('[data-adm-ask]')) { sendAdminAi(el.dataset.admAsk); return; }
|
|
1107
1154
|
if (el.matches('[data-cert]')) { openCertModal(el.dataset.cert); return; }
|
|
1108
1155
|
if (el.matches('[data-pay-order]')) { adminConfirmOrder(el.dataset.payOrder); return; }
|
|
1109
1156
|
if (el.matches('[data-activate-res]')) { adminActivateReservation(el.dataset.activateRes); return; }
|
|
@@ -1137,6 +1184,7 @@
|
|
|
1137
1184
|
case 'registry': refreshRegistry(); break;
|
|
1138
1185
|
case 'demo-pak': createDemoPak(); break;
|
|
1139
1186
|
case 'adm-issue-lic': adminIssueLicense(); break;
|
|
1187
|
+
case 'adm-ai-send': sendAdminAi(); break;
|
|
1140
1188
|
case 'cert-close': $('#certModal')?.classList.remove('open'); break;
|
|
1141
1189
|
case 'cert-copy':
|
|
1142
1190
|
navigator.clipboard?.writeText($('#certJsonBox')?.textContent || '');
|
|
@@ -1152,6 +1200,7 @@
|
|
|
1152
1200
|
});
|
|
1153
1201
|
$('#authModal').addEventListener('keydown', (e) => { if (e.key === 'Enter') performLogin(); });
|
|
1154
1202
|
$('#aiInput').addEventListener('keydown', (e) => { if (e.key === 'Enter') sendAi(); });
|
|
1203
|
+
$('#admAiInput')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendAdminAi(); });
|
|
1155
1204
|
$('#botText').addEventListener('keydown', (e) => { if (e.key === 'Enter') sendBot(); });
|
|
1156
1205
|
$('#rngAddonSeats').addEventListener('input', (e) => syncSeats(e.target.value));
|
|
1157
1206
|
$('#inpSeats').addEventListener('input', (e) => syncSeats(e.target.value));
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - комплекс ODANT-WORK на AMD Ryzen™ AI Max+ PRO 495 + Radeon™ 8065S;
|
|
6
6
|
* - ИИ-консультант и мультиагентная система платформы (ролевые агенты, навыки, оркестрация);
|
|
7
7
|
* - докупка годовых лицензий (Start 10 000 ₽, Business 15 000 ₽, Enterprise 20 000 ₽) со скидкой от количества;
|
|
8
|
-
* - вакантные франшизы по любому субъекту РФ (залог 100 000 ₽, базовая стоимость
|
|
8
|
+
* - вакантные франшизы по любому субъекту РФ (залог 100 000 ₽, базовая стоимость 500 000 ₽);
|
|
9
9
|
* - контакты разработчика платформы.
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
@@ -62,23 +62,100 @@ export class LicenseServerAiAgent {
|
|
|
62
62
|
const text = String(message || '').trim();
|
|
63
63
|
if (!text) {
|
|
64
64
|
return {
|
|
65
|
-
reply: 'Здравствуйте! Я ИИ-агент лицензионного центра ODANT-WORK. Спросите о комплексе, ИИ-агентах платформы, расчёте лицензий со
|
|
65
|
+
reply: 'Здравствуйте! Я ИИ-агент лицензионного центра ODANT-WORK. Спросите о комплексе, ИИ-агентах платформы, расчёте лицензий со скидкой, вакантных франшизах или управлении реестром сервера.',
|
|
66
66
|
actions: [],
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
const lower = text.toLowerCase();
|
|
70
70
|
|
|
71
|
+
// 0.1 Команда администратора: выпуск новой лицензии ПАК
|
|
72
|
+
if (/(выпуст|создай|оформ)\S*\s+(?:базов\S*\s+|демо[-\s]*)?лиценз/i.test(lower)) {
|
|
73
|
+
const orgMatch = text.match(/(?:для\s+)?((?:ООО|АО|ПАО|ЗАО|ИП)\s*«[^»]+»)/i);
|
|
74
|
+
const innMatch = text.match(/ИНН\s*(\d{10,12})/i) || text.match(/\b(\d{10,12})\b/);
|
|
75
|
+
const emailMatch = text.match(/([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})/i);
|
|
76
|
+
const lic = this.store.issueOrUpdateLicense({
|
|
77
|
+
customer: {
|
|
78
|
+
name: orgMatch ? orgMatch[1] : 'ООО «ТехноПром-Интеграция»',
|
|
79
|
+
inn: innMatch ? innMatch[1] : '6234109876',
|
|
80
|
+
email: emailMatch ? emailMatch[1] : 'it@technoprom.ru',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
reply: [
|
|
85
|
+
`Выпущена и подписана сертификатом **Ed25519** базовая лицензия **${lic.licenseId}**:`,
|
|
86
|
+
`• Организация: **${lic.customer?.name || '—'}** (ИНН ${lic.customer?.inn || '—'})`,
|
|
87
|
+
`• Рабочие места: **${lic.totalSeats}** (${lic.baseSeats} вечных лицензий в комплекте)`,
|
|
88
|
+
`• Поддержка: **1 год ${lic.support?.tierName || 'Enterprise'}** (${lic.support?.reactionLabel || 'до 15 минут'}, далее — по желанию)`,
|
|
89
|
+
`• Код привязки бота MAX/TG/VK: \`${lic.support?.bindCode || lic.licenseId}\``,
|
|
90
|
+
].join('\n'),
|
|
91
|
+
actions: [{ type: 'refresh_admin' }, { type: 'admin_tab', tab: 'licenses' }],
|
|
92
|
+
data: { license: lic },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 0.2 Команда администратора: проведение оплаты ожидающих заказов ЮKassa
|
|
97
|
+
if (/(провед|подтверд)\S*\s+оплат/i.test(lower)) {
|
|
98
|
+
const pending = Object.values(this.store.state.orders || {}).filter(o => o.status === 'pending');
|
|
99
|
+
if (!pending.length) {
|
|
100
|
+
return {
|
|
101
|
+
reply: 'Все выставленные заказы ЮKassa уже оплачены — ожидающих платежей сейчас нет.',
|
|
102
|
+
actions: [{ type: 'refresh_admin' }],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const completed = [];
|
|
106
|
+
for (const o of pending) {
|
|
107
|
+
const done = this.store.completePaidOrder(o.orderId, { paymentId: o.paymentId || ('yk_' + o.orderId) });
|
|
108
|
+
if (done) completed.push(done);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
reply: [
|
|
112
|
+
`Проведена оплата по **${completed.length}** заказам ЮKassa:`,
|
|
113
|
+
...completed.map(o => `• **${o.orderId}** — ${o.customer?.name || 'Клиент'} · **${fmt(o.amountRub)} ₽** (статус: оплачен)`),
|
|
114
|
+
'Сертификаты лицензий и резервы франшиз автоматически обновлены.',
|
|
115
|
+
].join('\n'),
|
|
116
|
+
actions: [{ type: 'refresh_admin' }, { type: 'admin_tab', tab: 'orders' }],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 0.3 Команда администратора: сводка по реестру сервера лицензий / заказам / тикетам
|
|
121
|
+
if (/(сводк|реестр|статус сервер|обзор сервер|неоплачен\S*\s+заказ|покажи заказ|открыт\S*\s+тикет|покажи тикет|резерв\S*\s+франш)/i.test(lower)) {
|
|
122
|
+
const licenses = this.store.listLicenses();
|
|
123
|
+
const orders = Object.values(this.store.state.orders || {});
|
|
124
|
+
const partners = this.store.listPartners();
|
|
125
|
+
const reservations = Object.values(this.store.state.franchiseReservations || {});
|
|
126
|
+
const tickets = this.store.listTickets();
|
|
127
|
+
const totalSeats = licenses.reduce((s, l) => s + (l.totalSeats || 0), 0);
|
|
128
|
+
const paidOrders = orders.filter(o => o.status === 'paid');
|
|
129
|
+
const pendingOrders = orders.filter(o => o.status === 'pending');
|
|
130
|
+
const paidSum = paidOrders.reduce((s, o) => s + (Number(o.amountRub) || 0), 0);
|
|
131
|
+
const pendingSum = pendingOrders.reduce((s, o) => s + (Number(o.amountRub) || 0), 0);
|
|
132
|
+
const openTickets = tickets.filter(t => t.status !== 'answered' && t.status !== 'closed');
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
reply: [
|
|
136
|
+
'**Сводка центра лицензирования и биллинга ODANT-WORK:**',
|
|
137
|
+
`• Выпущено лицензий ПАК: **${licenses.length}** (суммарно **${totalSeats}** активных мест, по ${PAK_PRODUCT.baseSeats} вечных в базе)`,
|
|
138
|
+
`• Оплачено заказов ЮKassa: **${paidOrders.length}** на сумму **${fmt(paidSum)} ₽**`,
|
|
139
|
+
`• Ожидают оплаты: **${pendingOrders.length}** на сумму **${fmt(pendingSum)} ₽**`,
|
|
140
|
+
`• Франшизы: **${partners.length}** действующих партнёров и **${reservations.length}** активных резервов`,
|
|
141
|
+
`• Техподдержка (MAX · TG · VK): **${openTickets.length}** открытых из **${tickets.length}** обращений`,
|
|
142
|
+
pendingOrders.length > 0 ? '\nНапишите «Проведи оплату заказов», чтобы подтвердить ожидающие платежи.' : '',
|
|
143
|
+
].filter(Boolean).join('\n'),
|
|
144
|
+
actions: [{ type: 'refresh_admin' }],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
71
148
|
// 1. Франшиза в конкретном субъекте РФ
|
|
72
149
|
const region = findRegionInText(text);
|
|
73
|
-
if (region && /(
|
|
150
|
+
if (region && /(франш|паушал|взнос|регион|вакант|квот|резерв|залог|партн|сколько|област|край|республ)/i.test(lower)) {
|
|
74
151
|
const q = this.store.getRegionPartnerQuotaStatus(region.code);
|
|
75
152
|
return {
|
|
76
153
|
reply: [
|
|
77
154
|
`**${q.regionName}** (${q.district})`,
|
|
78
155
|
`• Население: **${String(q.populationMillions).replace('.', ',')} млн чел.**`,
|
|
79
156
|
`• Вакантных франшиз: **${q.vacantFranchises} из ${q.maxPartnersQuota}** — квота пропорциональна населению`,
|
|
80
|
-
`•
|
|
81
|
-
`• Невозвратный залог за резерв: **${fmt(q.reservationDepositRub)}
|
|
157
|
+
`• Паушальный взнос: **${fmt(q.franchiseFeeRub)} ₽** (базовый взнос ${fmt(FRANCHISE_PROGRAM.baseFranchiseFeeRub)} ₽ × индекс ${q.economicIndex})`,
|
|
158
|
+
`• Невозвратный залог за резерв: **${fmt(q.reservationDepositRub)} ₽**, полная оплата остатка (**${fmt(q.remainingAfterDepositRub)} ₽**) — **в течение 1 года**`,
|
|
82
159
|
`• Скидка партнёра: от ${FRANCHISE_PROGRAM.minDiscountPercent}% до ${FRANCHISE_PROGRAM.maxDiscountPercent}% от годового объёма продаж`,
|
|
83
160
|
q.vacantFranchises > 0
|
|
84
161
|
? 'Я выделил субъект на карте — можно сразу нажать «Зарезервировать».'
|
|
@@ -146,7 +223,7 @@ export class LicenseServerAiAgent {
|
|
|
146
223
|
}
|
|
147
224
|
|
|
148
225
|
// 5. Комплекс и аппаратная платформа
|
|
149
|
-
if (/(ryzen|процессор|желез|характерист|radeon|8065|памят|192|модел|комплекс|продукт|сервер|(^|[^а-яё])ии([^а-яё]|$))/i.test(lower)) {
|
|
226
|
+
if (/(ryzen|процессор|желез|характерист|radeon|8065|памят|192|модел|комплекс|продукт|сервер|api|источник|(^|[^а-яё])ии([^а-яё]|$))/i.test(lower)) {
|
|
150
227
|
const H = PAK_PRODUCT.hardware;
|
|
151
228
|
return {
|
|
152
229
|
reply: [
|
|
@@ -156,25 +233,26 @@ export class LicenseServerAiAgent {
|
|
|
156
233
|
`• ИИ-ускорение: ${H.aiEngine}`,
|
|
157
234
|
`• Память и диск: ${H.ram} · ${H.storage}`,
|
|
158
235
|
`• Защита: ${H.security}`,
|
|
159
|
-
'• Внутри: система учёта, документооборот, управление процессами, ИИ-консультант, мультиагентная система и 4 типа локальных моделей (языковая, голосовая, генерация изображений, векторный поиск).',
|
|
160
|
-
|
|
236
|
+
'• Внутри: система учёта, документооборот, управление процессами, ИИ-консультант, мультиагентная система и 4 типа интегрированных локальных моделей (языковая, голосовая, генерация изображений, векторный поиск).',
|
|
237
|
+
'• **Гибридный контур моделей:** интегрированные локальные модели предназначены для базовых ежедневных операций; если требуются более мощные модели ИИ, их можно подключить через API из любого источника.',
|
|
238
|
+
`• **В комплекте:** вечная лицензия на **${PAK_PRODUCT.baseSeats} одновременных сессий** + **${PAK_PRODUCT.includedSupportYears} год поддержки уровня Enterprise** (24/7, реакция ≤ 15 мин). Дальнейшее продление поддержки и докупка мест — **по желанию**.`,
|
|
161
239
|
].join('\n'),
|
|
162
240
|
actions: [{ type: 'switch_stage', stage: 'product' }],
|
|
163
241
|
};
|
|
164
242
|
}
|
|
165
243
|
|
|
166
244
|
// 6. Партнёрская программа в целом
|
|
167
|
-
if (/(
|
|
245
|
+
if (/(франш|паушал|взнос|партн|залог|резерв|обучен|сертифик|nas|инференс)/i.test(lower)) {
|
|
168
246
|
const all = this.store.listRegionsWithQuotas().filter(r => !r.hidden);
|
|
169
247
|
const totalVacant = all.reduce((s, r) => s + r.vacantFranchises, 0);
|
|
170
248
|
return {
|
|
171
249
|
reply: [
|
|
172
250
|
'**Региональная франшиза ODANT-WORK:**',
|
|
173
|
-
`•
|
|
174
|
-
`• Резерв субъекта: невозвратный залог **${fmt(FRANCHISE_PROGRAM.reservationDepositRub)} ₽** через ЮKassa
|
|
251
|
+
`• Базовый паушальный взнос: **${fmt(FRANCHISE_PROGRAM.baseFranchiseFeeRub)} ₽** × экономический индекс субъекта`,
|
|
252
|
+
`• Резерв субъекта: невозвратный залог **${fmt(FRANCHISE_PROGRAM.reservationDepositRub)} ₽** через ЮKassa, полная оплата паушального взноса — **в течение 1 года**`,
|
|
175
253
|
`• Сейчас свободно **${totalVacant}** франшиз в **${all.length}** субъектах РФ (квота пропорциональна населению)`,
|
|
176
254
|
`• Скидка партнёра: от ${FRANCHISE_PROGRAM.minDiscountPercent}% до ${FRANCHISE_PROGRAM.maxDiscountPercent}% от годового объёма продаж`,
|
|
177
|
-
'• 100% дохода партнёра: оборудование и гарантия на него, NAS для резервных копий, серверы инференса, собственный API к моделям, поддержка, дообучение агентов и навыков.',
|
|
255
|
+
'• 100% дохода партнёра: оборудование и гарантия на него, NAS для резервных копий, серверы инференса, собственный API к мощным моделям, поддержка, дообучение агентов и навыков.',
|
|
178
256
|
].join('\n'),
|
|
179
257
|
actions: [{ type: 'switch_stage', stage: 'franchise' }],
|
|
180
258
|
};
|
|
@@ -184,10 +262,10 @@ export class LicenseServerAiAgent {
|
|
|
184
262
|
return {
|
|
185
263
|
reply: [
|
|
186
264
|
'Я ИИ-агент лицензионного центра **ODANT-WORK**. Коротко об условиях:',
|
|
187
|
-
`1. **Комплекс** — ${fmt(PAK_PRODUCT.basePriceRub)} ₽: AMD Ryzen™ AI Max+ PRO 495, Radeon™ 8065S, 192 ГБ, 4 ТБ,
|
|
265
|
+
`1. **Комплекс** — ${fmt(PAK_PRODUCT.basePriceRub)} ₽: AMD Ryzen™ AI Max+ PRO 495, Radeon™ 8065S, 192 ГБ, 4 ТБ, **${PAK_PRODUCT.baseSeats} вечных лицензий** и **1 год поддержки Enterprise** в комплекте (дальше — по желанию). Интегрированные модели — для базовых операций, более мощные ИИ подключаются через API из любого источника.`,
|
|
188
266
|
'2. **Доп. места на год:** Start 10 000 ₽ · Business 15 000 ₽ · Enterprise 20 000 ₽, скидка за количество до −25%.',
|
|
189
|
-
`3. **Франшиза:**
|
|
190
|
-
'Спросите, например: «Рассчитай 25 мест Business», «Франшиза в Рязанской
|
|
267
|
+
`3. **Франшиза:** паушальный взнос ${fmt(FRANCHISE_PROGRAM.baseFranchiseFeeRub)} ₽ × индекс субъекта, залог за резерв ${fmt(FRANCHISE_PROGRAM.reservationDepositRub)} ₽, полная оплата в течение 1 года.`,
|
|
268
|
+
'Спросите, например: «Рассчитай 25 мест Business», «Франшиза в Рязанской области», «Сводка по серверу» или «Выпусти лицензию».',
|
|
191
269
|
].join('\n'),
|
|
192
270
|
actions: [],
|
|
193
271
|
};
|
package/src/bots.js
CHANGED
|
@@ -56,7 +56,7 @@ export class SupportBotsHub {
|
|
|
56
56
|
'',
|
|
57
57
|
'Доступные команды:',
|
|
58
58
|
'• /bind <КОД> — привязать ПАК WORK к этому чату',
|
|
59
|
-
'• /status — проверить рабочие места (
|
|
59
|
+
'• /status — проверить рабочие места (10 вечных + годовые), SLA и остаток обращений',
|
|
60
60
|
'• /buy <start|business|enterprise> <кол-во_мест> — докупить годовые лицензии через ЮKassa',
|
|
61
61
|
'• Любое другое сообщение — создать обращение в инженерную поддержку по вашему SLA.',
|
|
62
62
|
].join('\n');
|
package/src/catalog.js
CHANGED
|
@@ -9,10 +9,12 @@ export const PAK_PRODUCT = Object.freeze({
|
|
|
9
9
|
name: 'ODANT-WORK',
|
|
10
10
|
domain: 'work.odant.org',
|
|
11
11
|
basePriceRub: 5_000_000,
|
|
12
|
-
baseSeats:
|
|
12
|
+
baseSeats: 10,
|
|
13
13
|
perpetualBase: true,
|
|
14
14
|
includedSupportYears: 1,
|
|
15
|
-
includedSupportTier: '
|
|
15
|
+
includedSupportTier: 'enterprise',
|
|
16
|
+
includedSupportTierName: 'Enterprise',
|
|
17
|
+
postYearPolicy: 'Дальнейшее продление поддержки и докупка мест — по желанию клиента (10 вечных лицензий работают бессрочно)',
|
|
16
18
|
hardware: Object.freeze({
|
|
17
19
|
cpu: 'AMD Ryzen™ AI Max+ PRO 495 (Zen 5, 16 ядер / 32 потока, до 5.2 ГГц, кэш 80 МБ)',
|
|
18
20
|
gpu: 'AMD Radeon™ 8065S Graphics (40 ядер, 3000 МГц, до 61 TFLOPS FP16)',
|
|
@@ -25,16 +27,17 @@ export const PAK_PRODUCT = Object.freeze({
|
|
|
25
27
|
}),
|
|
26
28
|
software: Object.freeze({
|
|
27
29
|
accounting: 'Встроенная система учёта, документооборота, управления процессами и приватный мультимодальный ИИ (справочники, план счетов, двойная запись, оборотно-сальдовая ведомость, анализ счёта, первичные документы и сквозные процессы)',
|
|
28
|
-
seatModel: 'По одновременным активным сессиям (
|
|
30
|
+
seatModel: 'По одновременным активным сессиям (10 вечных лицензий в базовом комплекте + 1 год поддержки Enterprise; дальше — продление и докупка мест по желанию)',
|
|
29
31
|
hardwareBinding: 'Привязка вечной лицензии к Hardware ID материнской платы/CPU AMD Ryzen + криптографическому ключу узла (#system/node-key.json)',
|
|
30
32
|
}),
|
|
33
|
+
modelsPolicy: 'Интегрированные локальные модели предназначены для базовых ежедневных операций внутри контура. Если требуются более мощные модели ИИ, их можно подключить через стандартный API из любого источника (облачные провайдеры, собственный GPU-кластер или API регионального партнёра).',
|
|
31
34
|
preinstalledModels: Object.freeze([
|
|
32
35
|
{
|
|
33
36
|
id: 'llm-core',
|
|
34
37
|
type: 'Большая языковая модель (LLM)',
|
|
35
|
-
title: '
|
|
36
|
-
badge: '
|
|
37
|
-
description: '
|
|
38
|
+
title: 'Базовое текстовое и учётное ядро',
|
|
39
|
+
badge: 'Базовые операции · Учёт · Документы',
|
|
40
|
+
description: 'Выполнение базовых операций: живой диалог, типовые хозяйственные проводки, шаблоны документов и повседневные задачи. Для глубокой аналитики и тяжёлых задач подключаются более мощные модели через API из любого источника.',
|
|
38
41
|
ramGb: 18.5,
|
|
39
42
|
},
|
|
40
43
|
{
|
|
@@ -77,7 +80,7 @@ export const AI_PLATFORM = Object.freeze({
|
|
|
77
80
|
'Действует в рамках роли и прав пользователя (ADMIN · BOSS · USER)',
|
|
78
81
|
'Инструменты учётного ядра: описание класса, поиск объектов, проведение операций, отчёты',
|
|
79
82
|
'Каждая консультация сохраняется задачей в личном кабинете и в ленте пользователя',
|
|
80
|
-
'
|
|
83
|
+
'Базовые операции выполняются интегрированными локальными моделями, а при необходимости более мощных ИИ — подключение через API из любого источника',
|
|
81
84
|
]),
|
|
82
85
|
}),
|
|
83
86
|
orchestration: Object.freeze([
|
|
@@ -236,7 +239,7 @@ export function calculateOrderQuote({
|
|
|
236
239
|
const items = [];
|
|
237
240
|
if (includeBasePak) {
|
|
238
241
|
items.push({
|
|
239
|
-
title: `${PAK_PRODUCT.name} (${PAK_PRODUCT.hardware.cpu}, ${PAK_PRODUCT.hardware.ramGb} ГБ RAM, SSD ${PAK_PRODUCT.hardware.ssdTb} ТБ, вечная лицензия на
|
|
242
|
+
title: `${PAK_PRODUCT.name} (${PAK_PRODUCT.hardware.cpu}, ${PAK_PRODUCT.hardware.ramGb} ГБ RAM, SSD ${PAK_PRODUCT.hardware.ssdTb} ТБ, вечная лицензия на ${PAK_PRODUCT.baseSeats} мест + 1 год поддержки Enterprise)`,
|
|
240
243
|
quantity: 1,
|
|
241
244
|
unitPriceRub: PAK_PRODUCT.basePriceRub,
|
|
242
245
|
totalRub: PAK_PRODUCT.basePriceRub,
|
|
@@ -277,14 +280,16 @@ export function calculateOrderQuote({
|
|
|
277
280
|
|
|
278
281
|
/**
|
|
279
282
|
* Параметры партнёрской программы и региональной франшизы ODANT-WORK:
|
|
280
|
-
* -
|
|
281
|
-
* - Сразу при резервировании франшизы вносится невозвратный залог = 100 000
|
|
283
|
+
* - Базовый паушальный взнос = 500 000 ₽ (корректируется пропорционально экономическому индексу региона).
|
|
284
|
+
* - Сразу при резервировании франшизы вносится невозвратный залог = 100 000 ₽, полная оплата паушального взноса — в течение 1 года (12 мес.).
|
|
282
285
|
* - Максимальное количество франшиз в регионе пропорционально населению региона (partnersPerMillion * populationMillions, мин. 1).
|
|
283
286
|
* - Партнёрская скидка растёт пропорционально годовому объёму продаж от начальных 10% до максимальных 50%.
|
|
284
287
|
*/
|
|
285
288
|
export const FRANCHISE_PROGRAM = Object.freeze({
|
|
286
|
-
baseFranchiseFeeRub:
|
|
289
|
+
baseFranchiseFeeRub: 500_000, // Базовый паушальный взнос (500 тыс. ₽ при экономическом индексе 1.0)
|
|
287
290
|
reservationDepositRub: 100_000, // Невозвратный залог сразу за резервирование региональной франшизы (100 тыс. ₽)
|
|
291
|
+
fullPaymentPeriodMonths: 12, // Срок полной оплаты паушального взноса — в течение 1 года (12 месяцев)
|
|
292
|
+
fullPaymentPeriodYears: 1, // 1 год на полную оплату паушального взноса после резервирования
|
|
288
293
|
partnersPerMillionPopulation: 1, // Норматив квоты партнёров: 1 франшиза на 1 млн населения региона
|
|
289
294
|
minDiscountPercent: 10, // Начальная партнёрская скидка (10%)
|
|
290
295
|
maxDiscountPercent: 50, // Максимальная партнёрская скидка (50%)
|
|
@@ -534,7 +539,7 @@ export const PARTNER_TRAINING_TRACKS = Object.freeze([
|
|
|
534
539
|
passScore: 75,
|
|
535
540
|
requiredForFranchise: true,
|
|
536
541
|
topics: [
|
|
537
|
-
'Экономическое обоснование внедрения ODANT-WORK (
|
|
542
|
+
'Экономическое обоснование внедрения ODANT-WORK (10 вечных лицензий + 1 год поддержки Enterprise + локальный ИИ в собственном контуре)',
|
|
538
543
|
'Лицензионная политика: расчёт конкурентных сессий со скидками от объёма и подбор тарифов SLA (Start / Business / Enterprise)',
|
|
539
544
|
'Регламент первой и второй линии поддержки через боты MAX, Telegram и VK',
|
|
540
545
|
],
|
|
@@ -588,6 +593,8 @@ export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopula
|
|
|
588
593
|
franchiseFeeRub,
|
|
589
594
|
reservationDepositRub,
|
|
590
595
|
remainingAfterDepositRub: Math.max(0, franchiseFeeRub - reservationDepositRub),
|
|
596
|
+
fullPaymentPeriodMonths: FRANCHISE_PROGRAM.fullPaymentPeriodMonths,
|
|
597
|
+
fullPaymentPeriodYears: FRANCHISE_PROGRAM.fullPaymentPeriodYears,
|
|
591
598
|
};
|
|
592
599
|
}
|
|
593
600
|
|
package/src/landing.js
CHANGED
|
@@ -207,8 +207,8 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
207
207
|
</div>
|
|
208
208
|
<div class="trust">
|
|
209
209
|
<span>${icon('shield')} Данные не покидают контур</span>
|
|
210
|
-
<span>${icon('key')}
|
|
211
|
-
<span>${icon('check')} 1 год поддержки
|
|
210
|
+
<span>${icon('key')} ${P.baseSeats} вечных лицензий</span>
|
|
211
|
+
<span>${icon('check')} 1 год поддержки Enterprise (далее — по желанию)</span>
|
|
212
212
|
</div>
|
|
213
213
|
</div>
|
|
214
214
|
<div class="showcase" aria-hidden="true">
|
|
@@ -250,7 +250,7 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
250
250
|
<span class="kicker">Паспорт комплекса</span>
|
|
251
251
|
<h2 class="h2">ODANT-WORK · ${esc(P.sku)}</h2>
|
|
252
252
|
<div class="price-big">${rub(P.basePriceRub)}</div>
|
|
253
|
-
<p class="note">Вечная лицензия на ${P.baseSeats} одновременных рабочих сессий, привязанная к оборудованию, и ${P.includedSupportYears} год поддержки уровня
|
|
253
|
+
<p class="note">Вечная лицензия на ${P.baseSeats} одновременных рабочих сессий, привязанная к оборудованию, и ${P.includedSupportYears} год максимальной поддержки уровня Enterprise (24/7, реакция ≤ 15 мин) — в комплекте. Дальнейшее продление поддержки и докупка мест — по желанию.</p>
|
|
254
254
|
<div class="hero-cta" style="margin-top:20px">
|
|
255
255
|
<button class="btn btn-primary" data-stage="order">Оформить заказ</button>
|
|
256
256
|
<button class="btn btn-ghost" data-stage="pricing">Тарифы поддержки</button>
|
|
@@ -270,12 +270,20 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
270
270
|
|
|
271
271
|
<div class="sec-head reveal">
|
|
272
272
|
<div>
|
|
273
|
-
<span class="kicker">Приватный
|
|
274
|
-
<h2 class="h2">Четыре типа моделей уже установлены</h2>
|
|
273
|
+
<span class="kicker">Приватный и гибридный ИИ</span>
|
|
274
|
+
<h2 class="h2">Четыре типа базовых моделей уже установлены</h2>
|
|
275
275
|
</div>
|
|
276
|
-
<p
|
|
276
|
+
<p>Интегрированные локальные модели предназначены для базовых ежедневных операций внутри контура. Если требуются более мощные модели ИИ, их можно подключить через API из любого источника.</p>
|
|
277
277
|
</div>
|
|
278
278
|
<div class="grid g4">${renderModels()}</div>
|
|
279
|
+
<div class="card reveal" style="display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap">
|
|
280
|
+
<div>
|
|
281
|
+
<span class="kicker">Подключение мощных моделей ИИ по API</span>
|
|
282
|
+
<h3 class="h3" style="margin:4px 0 6px">Нужны более мощные модели? Подключайте из любого источника</h3>
|
|
283
|
+
<p class="note" style="margin:0">Предустановленные на комплексе модели закрывают базовые операции учёта, голоса, иллюстраций и поиска без внешних каналов. Для сложной аналитики, больших контекстов и специализированных агентов вы можете в один клик подключить по стандартному API модели из любого источника: внешних облачных провайдеров, корпоративного GPU-кластера или коммерческого API регионального партнёра.</p>
|
|
284
|
+
</div>
|
|
285
|
+
<button class="btn btn-ghost btn-sm" data-stage="agents">Подробнее об ИИ-агентах →</button>
|
|
286
|
+
</div>
|
|
279
287
|
</section>
|
|
280
288
|
|
|
281
289
|
<!-- ============ ИИ-АГЕНТЫ ============ -->
|
|
@@ -333,9 +341,9 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
333
341
|
<div class="sec-head">
|
|
334
342
|
<div>
|
|
335
343
|
<span class="kicker">Лицензии и поддержка</span>
|
|
336
|
-
<h2 class="h2"
|
|
344
|
+
<h2 class="h2">10 вечных лицензий + 1 год Enterprise в базе, дальше — по желанию</h2>
|
|
337
345
|
</div>
|
|
338
|
-
<p>${P.baseSeats} рабочих мест входят в комплекс
|
|
346
|
+
<p>${P.baseSeats} рабочих мест входят в комплекс навсегда вместе с 1-м годом поддержки уровня Enterprise. Дальнейшее продление поддержки и докупка годовых мест — по желанию. Считаются одновременные активные сессии, а не учётные записи.</p>
|
|
339
347
|
</div>
|
|
340
348
|
<div class="tiers">${renderTiers()}</div>
|
|
341
349
|
<div class="card">
|
|
@@ -351,7 +359,7 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
351
359
|
<section class="stage" id="stage-order">
|
|
352
360
|
<div class="sec-head">
|
|
353
361
|
<div><span class="kicker">Заказ</span><h2 class="h2">Программно-аппаратный комплекс ODANT-WORK</h2></div>
|
|
354
|
-
<p>Сервер AMD Ryzen™ AI Max+ PRO 495 · 192 ГБ · 4 ТБ с установленной платформой, вечной лицензией на ${P.baseSeats} мест и годом
|
|
362
|
+
<p>Сервер AMD Ryzen™ AI Max+ PRO 495 · 192 ГБ · 4 ТБ с установленной платформой, вечной лицензией на ${P.baseSeats} мест и 1 годом поддержки Enterprise (дальше — по желанию). Оплата через ЮKassa с чеком по 54-ФЗ.</p>
|
|
355
363
|
</div>
|
|
356
364
|
<div class="split">
|
|
357
365
|
<div class="card">
|
|
@@ -420,12 +428,12 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
420
428
|
<section class="stage" id="stage-franchise">
|
|
421
429
|
<div class="sec-head">
|
|
422
430
|
<div><span class="kicker">Партнёрская программа</span><h2 class="h2">Региональная франшиза ODANT-WORK</h2></div>
|
|
423
|
-
<p>Число франшиз в субъекте пропорционально населению,
|
|
431
|
+
<p>Число франшиз в субъекте пропорционально населению, паушальный взнос — экономике региона. Резерв закрепляется сразу после невозвратного залога ${rub(FRANCHISE_PROGRAM.reservationDepositRub)}, полная оплата паушального взноса — в течение 1 года.</p>
|
|
424
432
|
</div>
|
|
425
433
|
<div class="stats">
|
|
426
434
|
<div class="stat"><b id="statVacant">${totalVacant}</b><span>вакантных франшиз</span></div>
|
|
427
435
|
<div class="stat"><b>${visibleRegions.length}</b><span>субъектов РФ</span></div>
|
|
428
|
-
<div class="stat"><b>${rub(FRANCHISE_PROGRAM.baseFranchiseFeeRub)}</b><span
|
|
436
|
+
<div class="stat"><b>${rub(FRANCHISE_PROGRAM.baseFranchiseFeeRub)}</b><span>базовый паушальный взнос</span></div>
|
|
429
437
|
<div class="stat"><b>${rub(FRANCHISE_PROGRAM.reservationDepositRub)}</b><span>залог за резерв</span></div>
|
|
430
438
|
<div class="stat"><b>${FRANCHISE_PROGRAM.minDiscountPercent}→${FRANCHISE_PROGRAM.maxDiscountPercent}%</b><span>скидка партнёра от объёма</span></div>
|
|
431
439
|
</div>
|
|
@@ -471,10 +479,10 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
471
479
|
<div><div class="small muted">Вакантно франшиз</div><div class="display" style="font-size:22px;font-weight:800" id="mapRegVacant">—</div><div class="small muted" id="mapRegOccupied"></div></div>
|
|
472
480
|
</div>
|
|
473
481
|
<div class="kv">
|
|
474
|
-
<div><span
|
|
482
|
+
<div><span>Паушальный взнос</span><b id="mapRegFee">—</b></div>
|
|
475
483
|
<div><span>Залог за резерв</span><b>${rub(FRANCHISE_PROGRAM.reservationDepositRub)}</b></div>
|
|
476
484
|
<div><span>Индекс экономики</span><b id="mapRegIndex">—</b></div>
|
|
477
|
-
<div><span>Остаток
|
|
485
|
+
<div><span>Остаток (в течение года)</span><b id="mapRegRest">—</b></div>
|
|
478
486
|
</div>
|
|
479
487
|
<div class="field"><label for="inpPartnerCompany">Компания</label><input type="text" id="inpPartnerCompany" value="ООО «УралТехИнтеграция»"></div>
|
|
480
488
|
<div class="row2">
|
|
@@ -498,7 +506,7 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
498
506
|
<th data-sort="district">Округ<span class="arr">↕</span></th>
|
|
499
507
|
<th data-sort="populationMillions" class="num">Население<span class="arr">↕</span></th>
|
|
500
508
|
<th data-sort="vacantFranchises">Вакантно<span class="arr">↕</span></th>
|
|
501
|
-
<th data-sort="franchiseFeeRub" class="num"
|
|
509
|
+
<th data-sort="franchiseFeeRub" class="num">Паушальный взнос<span class="arr">↕</span></th>
|
|
502
510
|
<th></th>
|
|
503
511
|
</tr></thead>
|
|
504
512
|
<tbody id="regionsTableBody"></tbody>
|
|
@@ -606,89 +614,130 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
606
614
|
<div class="stat"><b id="admStatTickets">0</b><span id="admStatTicketsSub">открытых тикетов SLA</span></div>
|
|
607
615
|
</div>
|
|
608
616
|
|
|
609
|
-
<div class="
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
<
|
|
617
|
+
<div class="admin-workspace" id="adminWorkspace">
|
|
618
|
+
<!-- Левая вкладка ИИ-агента и навигации админки -->
|
|
619
|
+
<aside class="admin-left">
|
|
620
|
+
<nav class="admin-nav" aria-label="Вкладки админ-панели">
|
|
621
|
+
<button class="adm-tab on" data-adm-tab="all">${icon('ai')}<span>ИИ-агент + Все разделы</span><i class="live-dot"></i></button>
|
|
622
|
+
<button class="adm-tab" data-adm-tab="agent">${icon('agents')}<span>Только ИИ-агент</span></button>
|
|
623
|
+
<button class="adm-tab" data-adm-tab="licenses">${icon('key')}<span>Лицензии (${P.baseSeats} вечн. + доп.)</span></button>
|
|
624
|
+
<button class="adm-tab" data-adm-tab="orders">${icon('cart')}<span>Заказы ЮKassa</span></button>
|
|
625
|
+
<button class="adm-tab" data-adm-tab="partners">${icon('map')}<span>Франшизы и партнёры</span></button>
|
|
626
|
+
<button class="adm-tab" data-adm-tab="tickets">${icon('chat')}<span>Тикеты SLA (MAX·TG·VK)</span></button>
|
|
627
|
+
</nav>
|
|
628
|
+
|
|
629
|
+
<div class="card adm-agent-card" id="admAgentCard">
|
|
630
|
+
<div class="ai-head" style="padding:0 0 12px;margin-bottom:10px">
|
|
631
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
632
|
+
<div>
|
|
633
|
+
<b>ИИ-агент администратора</b>
|
|
634
|
+
<span>● управление реестром · в сети</span>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
<div class="ai-msgs adm-ai-msgs" id="admAiMessages">
|
|
638
|
+
<div class="msg bot">Здравствуйте! Я ИИ-агент панели администратора ODANT-WORK. Помогу проанализировать реестр лицензий (${P.baseSeats} вечных мест + 1 год Enterprise), выпустить сертификат Ed25519, провести оплату заказов ЮKassa или проверить квоты франшиз по регионам РФ.</div>
|
|
639
|
+
</div>
|
|
640
|
+
<div class="ai-sugg" style="padding:8px 0;border-top:1px solid var(--line)">
|
|
641
|
+
<button data-adm-ask="Сводка по серверу лицензий">Сводка реестра</button>
|
|
642
|
+
<button data-adm-ask="Выпусти базовую лицензию для ООО «АгроТех» ИНН 6234001122">+ Выпустить лицензию</button>
|
|
643
|
+
<button data-adm-ask="Проведи оплату ожидающих заказов">Провести оплаты</button>
|
|
644
|
+
<button data-adm-ask="Сколько вакантных франшиз в Татарстане?">Квота Татарстана</button>
|
|
645
|
+
<button data-adm-ask="Какие модели входят в комплекс и как подключить мощные ИИ по API?">Модели и API</button>
|
|
646
|
+
</div>
|
|
647
|
+
<div class="ai-input" style="padding:10px 0 0">
|
|
648
|
+
<input type="text" id="admAiInput" placeholder="Команда или вопрос ИИ-агенту админки…" autocomplete="off">
|
|
649
|
+
<button class="btn btn-primary" data-action="adm-ai-send" aria-label="Отправить">${icon('send')}</button>
|
|
650
|
+
</div>
|
|
614
651
|
</div>
|
|
615
|
-
</
|
|
616
|
-
<div class="grid g4" style="align-items:end">
|
|
617
|
-
<div class="field" style="margin:0"><label for="admLicOrg">Организация</label><input type="text" id="admLicOrg" value="АО «РязаньАгроПром»"></div>
|
|
618
|
-
<div class="field" style="margin:0"><label for="admLicInn">ИНН</label><input type="text" id="admLicInn" value="6234109876"></div>
|
|
619
|
-
<div class="field" style="margin:0"><label for="admLicEmail">Email клиента</label><input type="email" id="admLicEmail" value="it@ryazan-agro.ru"></div>
|
|
620
|
-
<div class="field" style="margin:0"><label for="admLicHwid">HWID комплекса (необязательно)</label><input type="text" id="admLicHwid" placeholder="WORK-HWID-..."></div>
|
|
621
|
-
</div>
|
|
622
|
-
<div style="display:flex;align-items:center;gap:14px;margin-top:14px;flex-wrap:wrap">
|
|
623
|
-
<button class="btn btn-primary btn-sm" data-action="adm-issue-lic">${icon('key')} Выпустить и подписать сертификат</button>
|
|
624
|
-
<span class="small muted" id="admIssueStatus"></span>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
652
|
+
</aside>
|
|
627
653
|
|
|
628
|
-
|
|
629
|
-
<
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
<
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
654
|
+
<!-- Правая область разделов админки -->
|
|
655
|
+
<div class="admin-main" id="adminMain">
|
|
656
|
+
<div class="card" data-adm-sec="licenses">
|
|
657
|
+
<div class="sec-head" style="margin-bottom:14px">
|
|
658
|
+
<div>
|
|
659
|
+
<span class="kicker">Выпуск сертификата Ed25519</span>
|
|
660
|
+
<h3 class="h3" style="margin:4px 0 0">Выпустить базовую лицензию комплекса (${P.baseSeats} вечных лицензий + 1 год поддержки Enterprise, далее — по желанию)</h3>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
<div class="grid g4" style="align-items:end">
|
|
664
|
+
<div class="field" style="margin:0"><label for="admLicOrg">Организация</label><input type="text" id="admLicOrg" value="АО «РязаньАгроПром»"></div>
|
|
665
|
+
<div class="field" style="margin:0"><label for="admLicInn">ИНН</label><input type="text" id="admLicInn" value="6234109876"></div>
|
|
666
|
+
<div class="field" style="margin:0"><label for="admLicEmail">Email клиента</label><input type="email" id="admLicEmail" value="it@ryazan-agro.ru"></div>
|
|
667
|
+
<div class="field" style="margin:0"><label for="admLicHwid">HWID комплекса (необязательно)</label><input type="text" id="admLicHwid" placeholder="WORK-HWID-..."></div>
|
|
668
|
+
</div>
|
|
669
|
+
<div style="display:flex;align-items:center;gap:14px;margin-top:14px;flex-wrap:wrap">
|
|
670
|
+
<button class="btn btn-primary btn-sm" data-action="adm-issue-lic">${icon('key')} Выпустить и подписать сертификат</button>
|
|
671
|
+
<span class="small muted" id="admIssueStatus"></span>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
644
674
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
675
|
+
<div class="card" data-adm-sec="licenses">
|
|
676
|
+
<h3 class="h3" style="margin:0 0 12px">Выпущенные лицензии комплексов ODANT-WORK</h3>
|
|
677
|
+
<div class="table-wrap">
|
|
678
|
+
<table class="t">
|
|
679
|
+
<thead><tr>
|
|
680
|
+
<th>Лицензия / Код бота</th>
|
|
681
|
+
<th>Организация / ИНН</th>
|
|
682
|
+
<th>Привязка оборудования</th>
|
|
683
|
+
<th class="num">Сессии (вечные + доп.)</th>
|
|
684
|
+
<th>SLA и срок</th>
|
|
685
|
+
<th>Сертификат</th>
|
|
686
|
+
</tr></thead>
|
|
687
|
+
<tbody id="admLicensesBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
688
|
+
</table>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
661
691
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
692
|
+
<div class="card" data-adm-sec="orders">
|
|
693
|
+
<h3 class="h3" style="margin:0 0 12px">Заказы и платежи ЮKassa</h3>
|
|
694
|
+
<div class="table-wrap">
|
|
695
|
+
<table class="t">
|
|
696
|
+
<thead><tr>
|
|
697
|
+
<th>Заказ / Платёж</th>
|
|
698
|
+
<th>Назначение</th>
|
|
699
|
+
<th>Плательщик</th>
|
|
700
|
+
<th class="num">Сумма</th>
|
|
701
|
+
<th>Статус</th>
|
|
702
|
+
<th>Действия</th>
|
|
703
|
+
</tr></thead>
|
|
704
|
+
<tbody id="admOrdersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
705
|
+
</table>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
678
708
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
709
|
+
<div class="card" data-adm-sec="partners">
|
|
710
|
+
<h3 class="h3" style="margin:0 0 12px">Резервы региональных франшиз и действующие партнёры</h3>
|
|
711
|
+
<div class="table-wrap">
|
|
712
|
+
<table class="t">
|
|
713
|
+
<thead><tr>
|
|
714
|
+
<th>ID / Тип</th>
|
|
715
|
+
<th>Субъект РФ</th>
|
|
716
|
+
<th>Компания / ИНН</th>
|
|
717
|
+
<th>Условия / Скидка</th>
|
|
718
|
+
<th>Статус</th>
|
|
719
|
+
<th>Действия</th>
|
|
720
|
+
</tr></thead>
|
|
721
|
+
<tbody id="admPartnersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
722
|
+
</table>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
|
|
726
|
+
<div class="card" data-adm-sec="tickets">
|
|
727
|
+
<h3 class="h3" style="margin:0 0 12px">Обращения техподдержки (MAX · Telegram · VK)</h3>
|
|
728
|
+
<div class="table-wrap">
|
|
729
|
+
<table class="t">
|
|
730
|
+
<thead><tr>
|
|
731
|
+
<th>Тикет / Канал</th>
|
|
732
|
+
<th>Клиент / Лицензия</th>
|
|
733
|
+
<th>SLA</th>
|
|
734
|
+
<th>Переписка</th>
|
|
735
|
+
<th>Ответ инженера</th>
|
|
736
|
+
</tr></thead>
|
|
737
|
+
<tbody id="admTicketsBody"><tr><td colspan="5" class="muted">Загрузка…</td></tr></tbody>
|
|
738
|
+
</table>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
692
741
|
</div>
|
|
693
742
|
</div>
|
|
694
743
|
</section>
|
|
@@ -697,13 +746,13 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
697
746
|
<div class="footer">
|
|
698
747
|
<div>
|
|
699
748
|
<div class="brand" style="cursor:default"><img src="/assets/odant-logo.png" alt="" style="width:34px;height:34px"><span class="brand-name">ODANT<b>·</b>WORK</span></div>
|
|
700
|
-
<p style="margin-top:12px">Программно-аппаратный комплекс: встроенная система учёта, документооборот, управление процессами, ИИ-консультант и мультиагентная
|
|
749
|
+
<p style="margin-top:12px">Программно-аппаратный комплекс: встроенная система учёта, документооборот, управление процессами, ИИ-консультант и мультиагентная система. Интегрированные модели — для базовых операций, более мощные ИИ подключаются через API из любого источника.</p>
|
|
701
750
|
</div>
|
|
702
751
|
<div>
|
|
703
752
|
<h4>Комплекс</h4>
|
|
704
753
|
<p>AMD Ryzen™ AI Max+ PRO 495 · Radeon™ 8065S</p>
|
|
705
754
|
<p>192 ГБ LPDDR5x-8533 · SSD 4 ТБ</p>
|
|
706
|
-
<p>От ${rub(P.basePriceRub)} · ${P.baseSeats} вечных
|
|
755
|
+
<p>От ${rub(P.basePriceRub)} · ${P.baseSeats} вечных лицензий + 1 год Enterprise</p>
|
|
707
756
|
</div>
|
|
708
757
|
<div>
|
|
709
758
|
<h4>${esc(VENDOR_CONTACTS.company)}</h4>
|
package/src/store.js
CHANGED
|
@@ -138,7 +138,7 @@ export class LicenseStore {
|
|
|
138
138
|
perpetualBase: true,
|
|
139
139
|
addonSubscriptions: Array.isArray(existing?.addonSubscriptions) ? existing.addonSubscriptions : [],
|
|
140
140
|
support: {
|
|
141
|
-
tier: existing?.support?.tier || includedSupportTier || '
|
|
141
|
+
tier: existing?.support?.tier || includedSupportTier || PAK_PRODUCT.includedSupportTier || 'enterprise',
|
|
142
142
|
includedUntil: existing?.support?.includedUntil || (issuedAt + ONE_YEAR_MS),
|
|
143
143
|
ticketsUsed: Number(existing?.support?.ticketsUsed || 0),
|
|
144
144
|
bindCode,
|
|
@@ -247,9 +247,9 @@ export class LicenseStore {
|
|
|
247
247
|
const baseSupportActive = baseSupportUntil > now;
|
|
248
248
|
|
|
249
249
|
let addonSeats = 0;
|
|
250
|
-
let highestTier = baseSupportActive ? (lic.support?.tier || '
|
|
250
|
+
let highestTier = baseSupportActive ? (lic.support?.tier || PAK_PRODUCT.includedSupportTier || 'enterprise') : null;
|
|
251
251
|
let ticketsQuota = baseSupportActive
|
|
252
|
-
? baseSeats * (SLA_TIERS[lic.support?.tier || '
|
|
252
|
+
? baseSeats * (SLA_TIERS[lic.support?.tier || PAK_PRODUCT.includedSupportTier || 'enterprise']?.ticketsLimitPerSeatYear || 9999)
|
|
253
253
|
: 0;
|
|
254
254
|
|
|
255
255
|
const rank = { start: 1, business: 2, enterprise: 3 };
|
|
@@ -689,7 +689,7 @@ export class LicenseStore {
|
|
|
689
689
|
|
|
690
690
|
/**
|
|
691
691
|
* Зарезервировать региональную франшизу с выставлением счёта ЮKassa на невозвратный залог 100 000 ₽.
|
|
692
|
-
*
|
|
692
|
+
* Полный паушальный взнос (500 000 ₽ × индекс региона) фиксируется в договоре резерва, полная оплата — в течение 1 года.
|
|
693
693
|
*/
|
|
694
694
|
reserveFranchiseSlot({
|
|
695
695
|
regionCode = 'RU-SVE',
|
|
@@ -730,10 +730,12 @@ export class LicenseStore {
|
|
|
730
730
|
franchiseFeeRub: quotaStatus.franchiseFeeRub,
|
|
731
731
|
nonRefundableDepositRub: depositRub,
|
|
732
732
|
remainingFranchiseFeeRub: Math.max(0, quotaStatus.franchiseFeeRub - depositRub),
|
|
733
|
+
fullPaymentPeriodMonths: FRANCHISE_PROGRAM.fullPaymentPeriodMonths,
|
|
734
|
+
fullPaymentDeadlineAt: now + 365 * 24 * 3600_000,
|
|
733
735
|
status: 'reserved_pending_deposit',
|
|
734
736
|
depositPaid: false,
|
|
735
737
|
createdAt: now,
|
|
736
|
-
expiresAt: now +
|
|
738
|
+
expiresAt: now + 365 * 24 * 3600_000,
|
|
737
739
|
};
|
|
738
740
|
|
|
739
741
|
const order = {
|
|
@@ -750,7 +752,7 @@ export class LicenseStore {
|
|
|
750
752
|
franchiseFeeRub: quotaStatus.franchiseFeeRub,
|
|
751
753
|
items: [
|
|
752
754
|
{
|
|
753
|
-
title: `Невозвратный залог за резервирование региональной франшизы ODANT-WORK (${quotaStatus.regionName},
|
|
755
|
+
title: `Невозвратный залог за резервирование региональной франшизы ODANT-WORK (${quotaStatus.regionName}, паушальный взнос ${quotaStatus.franchiseFeeRub.toLocaleString('ru-RU')} ₽, полная оплата в течение 1 года)`,
|
|
754
756
|
quantity: 1,
|
|
755
757
|
unitPriceRub: depositRub,
|
|
756
758
|
totalRub: depositRub,
|
package/src/yookassa.js
CHANGED
|
@@ -39,7 +39,7 @@ export class YooKassaService {
|
|
|
39
39
|
buildYooKassaPaymentPayload(order) {
|
|
40
40
|
const valueStr = Number(order.amountRub || 0).toFixed(2);
|
|
41
41
|
const description = order.includeBasePak
|
|
42
|
-
? `Заказ ${order.orderId}: ПАК WORK (
|
|
42
|
+
? `Заказ ${order.orderId}: ПАК ODANT-WORK (10 вечных лицензий + 1 год Enterprise) + доп. места (${order.addonSeats} мест, ${order.tierName})`
|
|
43
43
|
: `Заказ ${order.orderId}: Годовая лицензия WORK (${order.addonSeats} польз., уровень ${order.tierName}, ${order.years} г.)`;
|
|
44
44
|
|
|
45
45
|
return {
|