work-license-server 1.0.18 → 1.0.20
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/assets/admin.js +7 -7
- package/assets/landing.js +444 -44
- package/bin/server.mjs +1 -1
- package/package.json +1 -1
- package/src/admin.js +7 -7
- package/src/catalog.js +1 -1
- package/src/landing.js +73 -0
- package/src/server.js +34 -2
- package/src/store.js +213 -44
package/bin/server.mjs
CHANGED
|
@@ -14,4 +14,4 @@ const server = await app.listen(port);
|
|
|
14
14
|
const addr = server.address();
|
|
15
15
|
const actualPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
16
16
|
console.log(`[work.odant.org] Витрина ODANT-WORK: http://localhost:${actualPort}/`);
|
|
17
|
-
console.log(`[work.odant.org] Панель
|
|
17
|
+
console.log(`[work.odant.org] Панель администратора: http://localhost:${actualPort}/admin`);
|
package/package.json
CHANGED
package/src/admin.js
CHANGED
|
@@ -58,14 +58,14 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
58
58
|
<div class="modal-head" style="margin-bottom:18px">
|
|
59
59
|
<img src="/assets/odant-logo.png" alt="ODANT">
|
|
60
60
|
<div>
|
|
61
|
-
<span class="kicker">Защищённый
|
|
61
|
+
<span class="kicker">Защищённый контур</span>
|
|
62
62
|
<h1 class="h3" style="margin:4px 0 2px;font-size:21px">Админ-панель ODANT-WORK</h1>
|
|
63
63
|
<div class="small muted">Вход по вашему Email администратора с 6-значным кодом из письма</div>
|
|
64
64
|
</div>
|
|
65
65
|
</div>
|
|
66
66
|
<div id="admStepEmail">
|
|
67
67
|
<div class="field">
|
|
68
|
-
<label for="admLoginEmail">Email
|
|
68
|
+
<label for="admLoginEmail">Email администратора</label>
|
|
69
69
|
<input type="email" id="admLoginEmail" value="" placeholder="Введите ваш email…" autocomplete="email" required>
|
|
70
70
|
</div>
|
|
71
71
|
<button type="button" class="btn btn-primary btn-lg" style="width:100%" id="btnAdmSendCode" data-action="adm-send-code">
|
|
@@ -115,7 +115,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
115
115
|
|
|
116
116
|
<div class="top-actions">
|
|
117
117
|
<button class="icon-btn" id="btnTheme" title="Светлая / тёмная тема" aria-label="Переключить тему">${icon('sun')}</button>
|
|
118
|
-
<button class="btn btn-primary btn-sm" data-action="open-adm-profile" title="Аккаунт администратора">${icon('user')}<span id="admUserLabel"
|
|
118
|
+
<button class="btn btn-primary btn-sm" data-action="open-adm-profile" title="Аккаунт администратора">${icon('user')}<span id="admUserLabel">Администратор</span></button>
|
|
119
119
|
</div>
|
|
120
120
|
</header>
|
|
121
121
|
|
|
@@ -127,7 +127,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
127
127
|
<button type="button" class="sec-nav-item" data-adm-tab="franchises" id="navAdmFranchises">Франшизы (0)</button>
|
|
128
128
|
<div class="sec-nav-sep"></div>
|
|
129
129
|
<button type="button" class="sec-nav-item" data-adm-tab="orders" id="navAdmOrders">Заказы и счета (0)</button>
|
|
130
|
-
<button type="button" class="sec-nav-item" data-adm-tab="users" id="navAdmUsers">Клиенты и
|
|
130
|
+
<button type="button" class="sec-nav-item" data-adm-tab="users" id="navAdmUsers">Клиенты и администраторы (0)</button>
|
|
131
131
|
<button type="button" class="sec-nav-item" data-adm-tab="tickets" id="navAdmTickets">Тикеты поддержки (0)</button>
|
|
132
132
|
</aside>
|
|
133
133
|
|
|
@@ -223,10 +223,10 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
223
223
|
</div>
|
|
224
224
|
</div>
|
|
225
225
|
|
|
226
|
-
<!-- 4. КЛИЕНТЫ И
|
|
226
|
+
<!-- 4. КЛИЕНТЫ И АДМИНИСТРАТОРЫ -->
|
|
227
227
|
<div data-adm-sec="users" style="display:none">
|
|
228
228
|
<div class="sec-bar" style="margin-bottom:12px">
|
|
229
|
-
<h2 class="sec-title">Зарегистрированные клиенты и
|
|
229
|
+
<h2 class="sec-title">Зарегистрированные клиенты и администраторы</h2>
|
|
230
230
|
<div class="sec-controls">
|
|
231
231
|
<input type="search" id="inpFilterUsers" placeholder="Поиск по Email, организации, ИНН…">
|
|
232
232
|
</div>
|
|
@@ -416,7 +416,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
416
416
|
<div style="padding:14px 16px;border-radius:12px;border:1px solid var(--line-strong);background:var(--field);margin-bottom:14px">
|
|
417
417
|
<div class="small muted">Вы вошли как</div>
|
|
418
418
|
<div style="font-size:16px;font-weight:700;margin-top:2px" id="admProfileEmail">—</div>
|
|
419
|
-
<div style="margin-top:6px"><span class="tag ok"
|
|
419
|
+
<div style="margin-top:6px"><span class="tag ok">Администратор портала</span></div>
|
|
420
420
|
</div>
|
|
421
421
|
<div style="display:flex;flex-direction:column;gap:8px">
|
|
422
422
|
<a class="btn btn-primary" href="/#addons" style="width:100%;text-decoration:none">Перейти в Личный кабинет</a>
|
package/src/catalog.js
CHANGED
|
@@ -315,7 +315,7 @@ export function calculateOrderQuote({
|
|
|
315
315
|
const discSuffix = volumeDiscountPercent > 0 ? `, скидка −${volumeDiscountPercent}% за объём` : '';
|
|
316
316
|
items.push({
|
|
317
317
|
sku: `WORK-LIC-ADDON-${tierSpec.id.toUpperCase()}`,
|
|
318
|
-
title:
|
|
318
|
+
title: `Дополнительные лицензии ODANT-WORK: ${seats} раб. мест × ${periodYears} г. (уровень ${tierSpec.name}, ${effectiveUnitPriceRub.toLocaleString('ru-RU')} ₽/место${discSuffix})`,
|
|
319
319
|
quantity: seats,
|
|
320
320
|
unitPriceRub: effectiveUnitPriceRub,
|
|
321
321
|
totalRub: addonsAmountRub,
|
package/src/landing.js
CHANGED
|
@@ -343,6 +343,8 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
343
343
|
<aside class="sec-nav" aria-label="Разделы Личного кабинета">
|
|
344
344
|
<button type="button" class="sec-nav-item active" data-cab-tab="servers" id="navCabServers">Серверы и устройства (0)</button>
|
|
345
345
|
<button type="button" class="sec-nav-item" data-cab-tab="franchises" id="navCabFranchises">Региональные франшизы (0)</button>
|
|
346
|
+
<div class="sec-nav-sep"></div>
|
|
347
|
+
<button type="button" class="sec-nav-item" data-cab-tab="requisites" id="navCabRequisites">Реквизиты организации</button>
|
|
346
348
|
</aside>
|
|
347
349
|
|
|
348
350
|
<div class="sec-main">
|
|
@@ -368,6 +370,77 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
368
370
|
</div>
|
|
369
371
|
<div class="sec-box" id="cabinetFranchisesRegistry"></div>
|
|
370
372
|
</div>
|
|
373
|
+
|
|
374
|
+
<!-- 1.3 РЕКВИЗИТЫ ОРГАНИЗАЦИИ (ЮРЛИЦА / ИП) ДЛЯ СЧЕТОВ И ДОГОВОРОВ -->
|
|
375
|
+
<div id="cabSecRequisites" style="display:none">
|
|
376
|
+
<div class="sec-bar" style="margin-bottom:12px">
|
|
377
|
+
<h2 class="sec-title">Реквизиты организации (Лицензиата)</h2>
|
|
378
|
+
<div class="sec-controls">
|
|
379
|
+
<button type="button" class="btn btn-primary btn-sm" data-action="save-requisites">Сохранить реквизиты</button>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="sec-box">
|
|
383
|
+
<div class="sec-item">
|
|
384
|
+
<div class="small muted" style="margin-bottom:14px">
|
|
385
|
+
Сохранённые реквизиты автоматически подставляются во все счета-договоры и закрывающие документы в Личном кабинете.
|
|
386
|
+
</div>
|
|
387
|
+
<div class="grid g2" style="gap:14px">
|
|
388
|
+
<div class="field" style="margin:0">
|
|
389
|
+
<label for="reqCompanyName">Наименование организации / ИП *</label>
|
|
390
|
+
<input type="text" id="reqCompanyName" placeholder="ООО «Маслоторг»">
|
|
391
|
+
</div>
|
|
392
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
|
393
|
+
<div class="field" style="margin:0">
|
|
394
|
+
<label for="reqInn">ИНН *</label>
|
|
395
|
+
<input type="text" id="reqInn" placeholder="10 или 12 цифр" maxlength="12">
|
|
396
|
+
</div>
|
|
397
|
+
<div class="field" style="margin:0">
|
|
398
|
+
<label for="reqKpp">КПП (для ООО)</label>
|
|
399
|
+
<input type="text" id="reqKpp" placeholder="9 цифр" maxlength="9">
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="field" style="margin:0;grid-column:1 / -1">
|
|
403
|
+
<label for="reqLegalAddress">Юридический адрес</label>
|
|
404
|
+
<input type="text" id="reqLegalAddress" placeholder="Индекс, город, улица, дом, офис">
|
|
405
|
+
</div>
|
|
406
|
+
<div class="field" style="margin:0">
|
|
407
|
+
<label for="reqSignerPosition">Должность руководителя (подписанта)</label>
|
|
408
|
+
<input type="text" id="reqSignerPosition" placeholder="Генеральный директор">
|
|
409
|
+
</div>
|
|
410
|
+
<div class="field" style="margin:0">
|
|
411
|
+
<label for="reqSignerName">ФИО руководителя (подписанта)</label>
|
|
412
|
+
<input type="text" id="reqSignerName" placeholder="Иванов Иван Иванович">
|
|
413
|
+
</div>
|
|
414
|
+
<div class="field" style="margin:0">
|
|
415
|
+
<label for="reqBankName">Наименование банка</label>
|
|
416
|
+
<input type="text" id="reqBankName" placeholder="ПАО Сбербанк">
|
|
417
|
+
</div>
|
|
418
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
|
419
|
+
<div class="field" style="margin:0">
|
|
420
|
+
<label for="reqBik">БИК</label>
|
|
421
|
+
<input type="text" id="reqBik" placeholder="9 цифр" maxlength="9">
|
|
422
|
+
</div>
|
|
423
|
+
<div class="field" style="margin:0">
|
|
424
|
+
<label for="reqPhone">Контактный телефон</label>
|
|
425
|
+
<input type="text" id="reqPhone" placeholder="+7 (___) ___-__-__">
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="field" style="margin:0">
|
|
429
|
+
<label for="reqBankAccount">Расчётный счёт (р/с)</label>
|
|
430
|
+
<input type="text" id="reqBankAccount" placeholder="40702810..." maxlength="20">
|
|
431
|
+
</div>
|
|
432
|
+
<div class="field" style="margin:0">
|
|
433
|
+
<label for="reqCorrAccount">Корреспондентский счёт (к/с)</label>
|
|
434
|
+
<input type="text" id="reqCorrAccount" placeholder="30101810..." maxlength="20">
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:16px;gap:12px;flex-wrap:wrap">
|
|
438
|
+
<div class="small" id="reqSaveStatus" style="color:var(--ok);font-weight:600"></div>
|
|
439
|
+
<button type="button" class="btn btn-primary" data-action="save-requisites">Сохранить реквизиты в Личном кабинете</button>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
371
444
|
</div>
|
|
372
445
|
</div>
|
|
373
446
|
</section>
|
package/src/server.js
CHANGED
|
@@ -539,6 +539,14 @@ export function createLicenseServerApp(options = {}) {
|
|
|
539
539
|
return sendJson(res, 200, result);
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
+
if (req.method === 'POST' && pathname === '/api/cabinet/profile') {
|
|
543
|
+
const body = await readJsonBody(req);
|
|
544
|
+
const key = extractBearerToken(req, body, url) || body.email || body.accountId || '';
|
|
545
|
+
const user = store.updateUserProfile(key, body);
|
|
546
|
+
res.setHeader('Set-Cookie', `odant_work_session=${encodeURIComponent(user.sessionKey)}; Path=/; Max-Age=31536000; SameSite=Lax`);
|
|
547
|
+
return sendJson(res, 200, { ok: true, user });
|
|
548
|
+
}
|
|
549
|
+
|
|
542
550
|
if (req.method === 'POST' && pathname === '/api/auth/accept-oferta') {
|
|
543
551
|
const body = await readJsonBody(req);
|
|
544
552
|
const key = extractBearerToken(req, body, url) || body.email || '';
|
|
@@ -641,8 +649,16 @@ export function createLicenseServerApp(options = {}) {
|
|
|
641
649
|
createNewServer: Boolean(body.createNewServer || body.licenseId === 'NEW'),
|
|
642
650
|
ownerAccountId: user?.userId || '',
|
|
643
651
|
customer: {
|
|
644
|
-
name: body.customer?.name || user?.companyName ||
|
|
652
|
+
name: body.customer?.name || user?.companyName || '',
|
|
645
653
|
inn: body.customer?.inn || user?.inn || '',
|
|
654
|
+
kpp: body.customer?.kpp || user?.kpp || '',
|
|
655
|
+
legalAddress: body.customer?.legalAddress || user?.legalAddress || '',
|
|
656
|
+
signerName: body.customer?.signerName || user?.signerName || '',
|
|
657
|
+
signerPosition: body.customer?.signerPosition || user?.signerPosition || '',
|
|
658
|
+
bankName: body.customer?.bankName || user?.bankName || '',
|
|
659
|
+
bik: body.customer?.bik || user?.bik || '',
|
|
660
|
+
bankAccount: body.customer?.bankAccount || user?.bankAccount || '',
|
|
661
|
+
corrAccount: body.customer?.corrAccount || user?.corrAccount || '',
|
|
646
662
|
email: body.customer?.email || user?.email || '',
|
|
647
663
|
phone: body.customer?.phone || user?.phone || '',
|
|
648
664
|
},
|
|
@@ -652,6 +668,10 @@ export function createLicenseServerApp(options = {}) {
|
|
|
652
668
|
return sendJson(res, 200, {
|
|
653
669
|
ok: true,
|
|
654
670
|
...(payment || {}),
|
|
671
|
+
...(order?.paymentMethod === 'invoice' ? {
|
|
672
|
+
invoiceUrl: `${reqOrigin}${order.invoiceUrl}`,
|
|
673
|
+
confirmationUrl: `${reqOrigin}${order.invoiceUrl}`,
|
|
674
|
+
} : {}),
|
|
655
675
|
order,
|
|
656
676
|
reservation,
|
|
657
677
|
user: updatedUser,
|
|
@@ -715,8 +735,20 @@ export function createLicenseServerApp(options = {}) {
|
|
|
715
735
|
paymentMethod: body.paymentMethod || 'yookassa',
|
|
716
736
|
companyName: body.companyName || user?.companyName || '',
|
|
717
737
|
inn: body.inn || user?.inn || '',
|
|
738
|
+
kpp: body.kpp ?? user?.kpp ?? '',
|
|
739
|
+
legalAddress: body.legalAddress ?? user?.legalAddress ?? '',
|
|
740
|
+
signerName: body.signerName ?? user?.signerName ?? '',
|
|
741
|
+
signerPosition: body.signerPosition ?? user?.signerPosition ?? '',
|
|
742
|
+
bankName: body.bankName ?? user?.bankName ?? '',
|
|
743
|
+
bik: body.bik ?? user?.bik ?? '',
|
|
744
|
+
bankAccount: body.bankAccount ?? user?.bankAccount ?? '',
|
|
745
|
+
corrAccount: body.corrAccount ?? user?.corrAccount ?? '',
|
|
746
|
+
phone: body.phone ?? user?.phone ?? '',
|
|
718
747
|
});
|
|
719
748
|
const updatedUser = store.findUserBySessionKey(user?.sessionKey);
|
|
749
|
+
if (updatedUser?.sessionKey) {
|
|
750
|
+
res.setHeader('Set-Cookie', `odant_work_session=${encodeURIComponent(updatedUser.sessionKey)}; Path=/; Max-Age=31536000; SameSite=Lax`);
|
|
751
|
+
}
|
|
720
752
|
if (order.paymentMethod === 'invoice') {
|
|
721
753
|
return sendJson(res, 200, {
|
|
722
754
|
ok: true,
|
|
@@ -940,7 +972,7 @@ export function createLicenseServerApp(options = {}) {
|
|
|
940
972
|
const body = await readJsonBody(req);
|
|
941
973
|
const tok = extractBearerToken(req, body, url);
|
|
942
974
|
if (!store.verifyAdminToken(tok))
|
|
943
|
-
return sendJson(res, 401, { error: 'Требуется авторизация администратора
|
|
975
|
+
return sendJson(res, 401, { error: 'Требуется авторизация администратора' });
|
|
944
976
|
const updated = store.setUserAdminRole(body.userId || body.email, body.isAdmin !== false);
|
|
945
977
|
return sendJson(res, 200, { ok: true, user: updated });
|
|
946
978
|
}
|