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/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] Панель администратора (BOSS): http://localhost:${actualPort}/admin (первый вошедший по Email становится BOSS)`);
17
+ console.log(`[work.odant.org] Панель администратора: http://localhost:${actualPort}/admin`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "work-license-server",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Лицензионный сервер, биллинг ЮKassa, боты поддержки (MAX, Telegram, VK) и лендинг ПАК WORK (work.odant.org)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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">Защищённый контур BOSS</span>
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 администратора (BOSS)</label>
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">BOSS</span></button>
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">Клиенты и роли (0)</button>
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. КЛИЕНТЫ И РОЛИ BOSS -->
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">Зарегистрированные клиенты и права администраторов (BOSS)</h2>
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">BOSS · Главный администратор</span></div>
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: `Годовая лицензия ODANT-WORK на доп. рабочее место (уровень ${tierSpec.name}, ${periodYears} г.${discSuffix})`,
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 || user?.email || '',
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: 'Требуется авторизация администратора (BOSS)' });
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
  }