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/src/store.js
CHANGED
|
@@ -149,14 +149,14 @@ export class LicenseStore {
|
|
|
149
149
|
login: userFromTok.email,
|
|
150
150
|
email: userFromTok.email,
|
|
151
151
|
userId: userFromTok.userId,
|
|
152
|
-
role: '
|
|
152
|
+
role: 'ADMIN',
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
if (clean === this.adminPassword && this.adminPassword.length >= 6) {
|
|
156
156
|
return {
|
|
157
157
|
authenticated: true,
|
|
158
158
|
login: this.adminLogin,
|
|
159
|
-
role: '
|
|
159
|
+
role: 'ADMIN',
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
const sess = this.adminSessions.get(clean);
|
|
@@ -169,12 +169,12 @@ export class LicenseStore {
|
|
|
169
169
|
return {
|
|
170
170
|
authenticated: true,
|
|
171
171
|
login: sess.login || this.adminLogin,
|
|
172
|
-
role: '
|
|
172
|
+
role: 'ADMIN',
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
* Гарантировать, что первый зарегистрированный на сервере пользователь является
|
|
177
|
+
* Гарантировать, что первый зарегистрированный на сервере пользователь является администратором портала.
|
|
178
178
|
*/
|
|
179
179
|
_ensureBootstrapAdmin() {
|
|
180
180
|
const allUsers = Object.values(this.state.users || {});
|
|
@@ -182,19 +182,19 @@ export class LicenseStore {
|
|
|
182
182
|
return null;
|
|
183
183
|
const existingAdmin = allUsers.find(u => u.isAdmin);
|
|
184
184
|
if (existingAdmin) {
|
|
185
|
-
existingAdmin.role = '
|
|
185
|
+
existingAdmin.role = 'ADMIN';
|
|
186
186
|
return existingAdmin;
|
|
187
187
|
}
|
|
188
188
|
const firstUser = [...allUsers].sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0))[0];
|
|
189
189
|
if (firstUser) {
|
|
190
190
|
firstUser.isAdmin = true;
|
|
191
|
-
firstUser.role = '
|
|
191
|
+
firstUser.role = 'ADMIN';
|
|
192
192
|
}
|
|
193
193
|
return firstUser || null;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
|
-
* Назначить или снять права администратора
|
|
197
|
+
* Назначить или снять права администратора у пользователя по Email или userId.
|
|
198
198
|
*/
|
|
199
199
|
setUserAdminRole(emailOrUserId = '', isAdmin = true) {
|
|
200
200
|
const key = String(emailOrUserId || '').trim();
|
|
@@ -207,11 +207,11 @@ export class LicenseStore {
|
|
|
207
207
|
if (!nextAdmin && user.isAdmin) {
|
|
208
208
|
const otherAdmins = Object.values(this.state.users || {}).filter(u => u.isAdmin && u.userId !== user.userId);
|
|
209
209
|
if (!otherAdmins.length) {
|
|
210
|
-
throw new Error('Нельзя снять права
|
|
210
|
+
throw new Error('Нельзя снять права у единственного администратора сервера');
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
user.isAdmin = nextAdmin;
|
|
214
|
-
user.role = nextAdmin ? '
|
|
214
|
+
user.role = nextAdmin ? 'ADMIN' : 'CLIENT';
|
|
215
215
|
this._save();
|
|
216
216
|
return this._formatUserResponse(user);
|
|
217
217
|
}
|
|
@@ -789,10 +789,16 @@ export class LicenseStore {
|
|
|
789
789
|
contractNumber: method === 'invoice' ? `ДОГ-${dateTag}-${shortRand}` : '',
|
|
790
790
|
invoiceUrl: method === 'invoice' ? `/api/orders/${orderId}/invoice` : '',
|
|
791
791
|
customer: {
|
|
792
|
-
name: customer.name
|
|
792
|
+
name: (customer.name && !String(customer.name).includes('@')) ? customer.name : ((targetLic.customer?.name && !String(targetLic.customer.name).includes('@')) ? targetLic.customer.name : ''),
|
|
793
793
|
inn: customer.inn || targetLic.customer?.inn || '',
|
|
794
|
-
kpp: customer.kpp || '',
|
|
795
|
-
legalAddress: customer.legalAddress || '',
|
|
794
|
+
kpp: customer.kpp || targetLic.customer?.kpp || '',
|
|
795
|
+
legalAddress: customer.legalAddress || targetLic.customer?.legalAddress || '',
|
|
796
|
+
signerName: customer.signerName || targetLic.customer?.signerName || '',
|
|
797
|
+
signerPosition: customer.signerPosition || targetLic.customer?.signerPosition || '',
|
|
798
|
+
bankName: customer.bankName || targetLic.customer?.bankName || '',
|
|
799
|
+
bik: customer.bik || targetLic.customer?.bik || '',
|
|
800
|
+
bankAccount: customer.bankAccount || targetLic.customer?.bankAccount || '',
|
|
801
|
+
corrAccount: customer.corrAccount || targetLic.customer?.corrAccount || '',
|
|
796
802
|
email: customer.email || targetLic.customer?.email || 'billing@odant.org',
|
|
797
803
|
phone: customer.phone || targetLic.customer?.phone || '',
|
|
798
804
|
},
|
|
@@ -977,11 +983,13 @@ export class LicenseStore {
|
|
|
977
983
|
if (!ord)
|
|
978
984
|
return null;
|
|
979
985
|
const dateStr = new Date(ord.createdAt || Date.now()).toLocaleDateString('ru-RU');
|
|
986
|
+
const c = ord.customer || {};
|
|
987
|
+
const custName = (c.name && !String(c.name).includes('@')) ? c.name : 'Организация (не указана)';
|
|
980
988
|
const rowsHtml = (ord.items || []).map((it, idx) => `
|
|
981
989
|
<tr>
|
|
982
990
|
<td style="text-align:center;">${idx + 1}</td>
|
|
983
991
|
<td>${it.title}</td>
|
|
984
|
-
<td style="text-align:center;">${it.quantity}</td>
|
|
992
|
+
<td style="text-align:center;font-weight:700;">${it.quantity}</td>
|
|
985
993
|
<td style="text-align:right;">${Number(it.unitPriceRub || 0).toLocaleString('ru-RU')} ₽</td>
|
|
986
994
|
<td style="text-align:right;font-weight:700;">${Number(it.totalRub || 0).toLocaleString('ru-RU')} ₽</td>
|
|
987
995
|
</tr>
|
|
@@ -993,50 +1001,81 @@ export class LicenseStore {
|
|
|
993
1001
|
<meta charset="utf-8">
|
|
994
1002
|
<title>Счёт-договор № ${ord.invoiceNumber || ord.orderId} от ${dateStr}</title>
|
|
995
1003
|
<style>
|
|
996
|
-
body { font-family: Arial, sans-serif; color: #111827; max-width:
|
|
997
|
-
h1 { font-size: 20px; margin
|
|
1004
|
+
body { font-family: Arial, sans-serif; color: #111827; max-width: 880px; margin: 20px auto; padding: 20px; line-height: 1.45; }
|
|
1005
|
+
h1 { font-size: 20px; margin: 0 0 4px; }
|
|
998
1006
|
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
|
|
999
1007
|
th, td { border: 1px solid #cbd5e1; padding: 8px 10px; }
|
|
1000
1008
|
th { background: #f1f5f9; text-align: left; }
|
|
1001
|
-
.meta { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding:
|
|
1002
|
-
.status { display: inline-block; padding:
|
|
1009
|
+
.meta { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 14px 16px; font-size: 13px; }
|
|
1010
|
+
.status { display: inline-block; padding: 5px 12px; border-radius: 999px; font-size: 12px; font-weight: 700; background: ${ord.status === 'paid' ? '#dcfce7' : '#fef3c7'}; color: ${ord.status === 'paid' ? '#166534' : '#92400e'}; }
|
|
1011
|
+
.toolbar { display: flex; justify-content: space-between; align-items: center; background: #0f172a; color: #f8fafc; padding: 10px 16px; border-radius: 10px; margin-bottom: 18px; }
|
|
1012
|
+
.toolbar button { background: #2563eb; color: #fff; border: none; border-radius: 6px; padding: 8px 14px; font-size: 13px; font-weight: 700; cursor: pointer; }
|
|
1013
|
+
.toolbar button.sec { background: #334155; margin-left: 8px; }
|
|
1014
|
+
.sig-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 28px; font-size: 13px; }
|
|
1015
|
+
.sig-box { border-top: 1px solid #cbd5e1; padding-top: 10px; }
|
|
1016
|
+
@media print { .no-print { display: none !important; } body { margin: 0; padding: 10px; } }
|
|
1003
1017
|
</style>
|
|
1004
1018
|
</head>
|
|
1005
1019
|
<body>
|
|
1006
|
-
<div
|
|
1020
|
+
<div class="toolbar no-print">
|
|
1021
|
+
<div style="font-size:13.5px;font-weight:700;">Счёт-договор № ${ord.invoiceNumber || ord.orderId} · ODANT-WORK</div>
|
|
1022
|
+
<div>
|
|
1023
|
+
<button type="button" onclick="window.print()">Печать / Сохранить в PDF</button>
|
|
1024
|
+
<button type="button" class="sec" onclick="window.close()">Закрыть вкладку</button>
|
|
1025
|
+
</div>
|
|
1026
|
+
</div>
|
|
1027
|
+
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:16px;">
|
|
1007
1028
|
<div>
|
|
1008
1029
|
<h1>Счёт-договор № ${ord.invoiceNumber || ord.orderId} от ${dateStr}</h1>
|
|
1009
|
-
<div>Договор № ${ord.contractNumber || ord.orderId} · Заказ № ${ord.orderId}</div>
|
|
1030
|
+
<div style="font-size:13px;color:#475569;">Договор № ${ord.contractNumber || ord.orderId} · Заказ № ${ord.orderId} · Сервер: ${ord.licenseId || '—'}</div>
|
|
1010
1031
|
</div>
|
|
1011
1032
|
<span class="status">${ord.status === 'paid' ? 'ОПЛАЧЕН' : 'ОЖИДАЕТ ОПЛАТЫ ПО СЧЁТУ'}</span>
|
|
1012
1033
|
</div>
|
|
1013
1034
|
<div class="meta" style="margin-top:14px;">
|
|
1014
|
-
<div><strong>Поставщик (
|
|
1015
|
-
<div style="margin-top:
|
|
1035
|
+
<div><strong>Поставщик (Лицензиар):</strong> ООО «${VENDOR_CONTACTS.company}», адрес: ${VENDOR_CONTACTS.address}, тел.: ${VENDOR_CONTACTS.phones.join(', ')}</div>
|
|
1036
|
+
<div style="margin-top:8px;">
|
|
1037
|
+
<strong>Покупатель (Лицензиат):</strong> ${custName}, ИНН: ${c.inn || '—'}${c.kpp ? ', КПП: ' + c.kpp : ''}
|
|
1038
|
+
${c.legalAddress ? `<br><strong>Юридический адрес:</strong> ${c.legalAddress}` : ''}
|
|
1039
|
+
${c.bankName || c.bankAccount ? `<br><strong>Банковские реквизиты:</strong> р/с ${c.bankAccount || '—'} в ${c.bankName || '—'}${c.bik ? ', БИК ' + c.bik : ''}${c.corrAccount ? ', к/с ' + c.corrAccount : ''}` : ''}
|
|
1040
|
+
${c.signerName ? `<br><strong>Уполномоченное лицо:</strong> ${c.signerPosition ? c.signerPosition + ' ' : ''}${c.signerName}` : ''}
|
|
1041
|
+
<br><strong>Контакты:</strong> Email: ${c.email || '—'}${c.phone ? ', тел.: ' + c.phone : ''}
|
|
1042
|
+
</div>
|
|
1016
1043
|
</div>
|
|
1017
1044
|
<table>
|
|
1018
1045
|
<thead>
|
|
1019
1046
|
<tr>
|
|
1020
1047
|
<th style="width:40px;">№</th>
|
|
1021
|
-
<th>Наименование
|
|
1022
|
-
<th style="width:
|
|
1023
|
-
<th style="width:130px;"
|
|
1024
|
-
<th style="width:140px;">Сумма</th>
|
|
1048
|
+
<th>Наименование лицензии / программно-аппаратного комплекса / услуги</th>
|
|
1049
|
+
<th style="width:85px;text-align:center;">Кол-во мест / шт.</th>
|
|
1050
|
+
<th style="width:130px;text-align:right;">Цена за ед.</th>
|
|
1051
|
+
<th style="width:140px;text-align:right;">Сумма</th>
|
|
1025
1052
|
</tr>
|
|
1026
1053
|
</thead>
|
|
1027
1054
|
<tbody>${rowsHtml}</tbody>
|
|
1028
1055
|
<tfoot>
|
|
1029
1056
|
<tr>
|
|
1030
|
-
<td colspan="4" style="text-align:right;font-weight:700;">ИТОГО К ОПЛАТЕ (
|
|
1057
|
+
<td colspan="4" style="text-align:right;font-weight:700;">ИТОГО К ОПЛАТЕ (НДС не облагается на основании пп. 26 п. 2 ст. 149 НК РФ / УСН):</td>
|
|
1031
1058
|
<td style="text-align:right;font-weight:800;font-size:15px;">${Number(ord.amountRub || 0).toLocaleString('ru-RU')} ₽</td>
|
|
1032
1059
|
</tr>
|
|
1033
1060
|
</tfoot>
|
|
1034
1061
|
</table>
|
|
1035
1062
|
<p style="font-size:12.5px;color:#475569;">
|
|
1036
|
-
Оплата настоящего счёта-договора означает
|
|
1037
|
-
|
|
1038
|
-
|
|
1063
|
+
1. Оплата настоящего счёта-договора означает акцепт Лицензиатом условий Публичного договора-оферты ODANT-WORK (${PUBLIC_OFERTA.version}).<br>
|
|
1064
|
+
2. В назначении платежа обязательно укажите: «Оплата по счёту-договору № ${ord.invoiceNumber || ord.orderId} от ${dateStr} за лицензии ODANT-WORK (заказ ${ord.orderId}). Без НДС».<br>
|
|
1065
|
+
3. После зачисления средств файлы лицензий (<code>.lic.json</code>) автоматически активируются в Личном кабинете (${c.email || ''}) для сервера <code>${ord.licenseId || ''}</code>.
|
|
1039
1066
|
</p>
|
|
1067
|
+
<div class="sig-grid">
|
|
1068
|
+
<div class="sig-box">
|
|
1069
|
+
<strong>От Поставщика (Лицензиара):</strong><br>
|
|
1070
|
+
ООО «${VENDOR_CONTACTS.company}»<br><br>
|
|
1071
|
+
______________________ / Руководитель /
|
|
1072
|
+
</div>
|
|
1073
|
+
<div class="sig-box">
|
|
1074
|
+
<strong>От Покупателя (Лицензиата):</strong><br>
|
|
1075
|
+
${custName}<br><br>
|
|
1076
|
+
______________________ / ${c.signerName || 'Руководитель'} /
|
|
1077
|
+
</div>
|
|
1078
|
+
</div>
|
|
1040
1079
|
</body>
|
|
1041
1080
|
</html>`;
|
|
1042
1081
|
}
|
|
@@ -1229,7 +1268,7 @@ export class LicenseStore {
|
|
|
1229
1268
|
if (user) {
|
|
1230
1269
|
if (verifiedPayload.isAdmin) {
|
|
1231
1270
|
user.isAdmin = true;
|
|
1232
|
-
user.role = '
|
|
1271
|
+
user.role = 'ADMIN';
|
|
1233
1272
|
}
|
|
1234
1273
|
user.sessionKey = key;
|
|
1235
1274
|
try { this._save(); } catch { /* ignore */ }
|
|
@@ -1295,8 +1334,10 @@ export class LicenseStore {
|
|
|
1295
1334
|
ownerEmail: user.email,
|
|
1296
1335
|
hubUrl: hubUrl || user.hubUrl || 'https://work.odant.org',
|
|
1297
1336
|
customer: {
|
|
1298
|
-
name: user.companyName
|
|
1337
|
+
name: (user.companyName && !String(user.companyName).includes('@')) ? user.companyName : '',
|
|
1299
1338
|
inn: user.inn || '',
|
|
1339
|
+
kpp: user.kpp || '',
|
|
1340
|
+
legalAddress: user.legalAddress || '',
|
|
1300
1341
|
email: user.email,
|
|
1301
1342
|
phone: user.phone || '',
|
|
1302
1343
|
},
|
|
@@ -1452,16 +1493,25 @@ export class LicenseStore {
|
|
|
1452
1493
|
.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
1453
1494
|
const userFranchises = this.listUserFranchises(user, now);
|
|
1454
1495
|
const userServers = this.listUserServers(user, now);
|
|
1496
|
+
const cleanCompany = (user.companyName && !String(user.companyName).includes('@')) ? user.companyName : '';
|
|
1455
1497
|
return {
|
|
1456
1498
|
userId: user.userId,
|
|
1457
1499
|
email: user.email,
|
|
1458
1500
|
isAdmin: Boolean(user.isAdmin),
|
|
1459
|
-
role: user.isAdmin ? '
|
|
1501
|
+
role: user.isAdmin ? 'ADMIN' : 'CLIENT',
|
|
1460
1502
|
authProvider: user.authProvider || 'email',
|
|
1461
1503
|
name: user.name,
|
|
1462
1504
|
position: user.position || '',
|
|
1463
|
-
companyName:
|
|
1464
|
-
inn: user.inn,
|
|
1505
|
+
companyName: cleanCompany,
|
|
1506
|
+
inn: user.inn || '',
|
|
1507
|
+
kpp: user.kpp || '',
|
|
1508
|
+
legalAddress: user.legalAddress || '',
|
|
1509
|
+
signerName: user.signerName || '',
|
|
1510
|
+
signerPosition: user.signerPosition || user.position || '',
|
|
1511
|
+
bankName: user.bankName || '',
|
|
1512
|
+
bik: user.bik || '',
|
|
1513
|
+
bankAccount: user.bankAccount || '',
|
|
1514
|
+
corrAccount: user.corrAccount || '',
|
|
1465
1515
|
phone: user.phone || '',
|
|
1466
1516
|
licenseId: user.licenseId,
|
|
1467
1517
|
sessionKey: user.sessionKey,
|
|
@@ -1483,6 +1533,89 @@ export class LicenseStore {
|
|
|
1483
1533
|
};
|
|
1484
1534
|
}
|
|
1485
1535
|
|
|
1536
|
+
/**
|
|
1537
|
+
* Обновить и сохранить реквизиты организации (юридического лица / ИП) в профиле Личного кабинета.
|
|
1538
|
+
*/
|
|
1539
|
+
updateUserProfile(sessionKeyOrEmail = '', {
|
|
1540
|
+
companyName = undefined,
|
|
1541
|
+
inn = undefined,
|
|
1542
|
+
kpp = undefined,
|
|
1543
|
+
legalAddress = undefined,
|
|
1544
|
+
signerName = undefined,
|
|
1545
|
+
signerPosition = undefined,
|
|
1546
|
+
bankName = undefined,
|
|
1547
|
+
bik = undefined,
|
|
1548
|
+
bankAccount = undefined,
|
|
1549
|
+
corrAccount = undefined,
|
|
1550
|
+
phone = undefined,
|
|
1551
|
+
} = {}) {
|
|
1552
|
+
const user = this._findUser(sessionKeyOrEmail);
|
|
1553
|
+
if (!user)
|
|
1554
|
+
throw new Error('Пользователь не найден. Пожалуйста, войдите в Личный кабинет.');
|
|
1555
|
+
if (companyName !== undefined) {
|
|
1556
|
+
const c = String(companyName || '').trim();
|
|
1557
|
+
user.companyName = c.includes('@') ? '' : c;
|
|
1558
|
+
}
|
|
1559
|
+
if (inn !== undefined) user.inn = String(inn || '').trim();
|
|
1560
|
+
if (kpp !== undefined) user.kpp = String(kpp || '').trim();
|
|
1561
|
+
if (legalAddress !== undefined) user.legalAddress = String(legalAddress || '').trim();
|
|
1562
|
+
if (signerName !== undefined) user.signerName = String(signerName || '').trim();
|
|
1563
|
+
if (signerPosition !== undefined) {
|
|
1564
|
+
user.signerPosition = String(signerPosition || '').trim();
|
|
1565
|
+
user.position = user.signerPosition;
|
|
1566
|
+
}
|
|
1567
|
+
if (bankName !== undefined) user.bankName = String(bankName || '').trim();
|
|
1568
|
+
if (bik !== undefined) user.bik = String(bik || '').trim();
|
|
1569
|
+
if (bankAccount !== undefined) user.bankAccount = String(bankAccount || '').trim();
|
|
1570
|
+
if (corrAccount !== undefined) user.corrAccount = String(corrAccount || '').trim();
|
|
1571
|
+
if (phone !== undefined) user.phone = String(phone || '').trim();
|
|
1572
|
+
|
|
1573
|
+
// Синхронизируем реквизиты со всеми серверами и неоплаченными заказами этого клиента
|
|
1574
|
+
for (const lic of Object.values(this.state.licenses || {})) {
|
|
1575
|
+
if (!lic) continue;
|
|
1576
|
+
if (lic.ownerAccountId === user.userId || String(lic.ownerEmail || '').toLowerCase() === user.email) {
|
|
1577
|
+
lic.customer = lic.customer || {};
|
|
1578
|
+
if (user.companyName) lic.customer.name = user.companyName;
|
|
1579
|
+
if (user.inn) lic.customer.inn = user.inn;
|
|
1580
|
+
if (user.kpp !== undefined) lic.customer.kpp = user.kpp;
|
|
1581
|
+
if (user.legalAddress !== undefined) lic.customer.legalAddress = user.legalAddress;
|
|
1582
|
+
if (user.phone !== undefined) lic.customer.phone = user.phone;
|
|
1583
|
+
this.state.licenses[lic.licenseId] = signCertificate(lic, this.authorityKeys);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
for (const ord of Object.values(this.state.orders || {})) {
|
|
1587
|
+
if (!ord || ord.status === 'paid') continue;
|
|
1588
|
+
if (ord.ownerAccountId === user.userId || String(ord.customer?.email || '').toLowerCase() === user.email) {
|
|
1589
|
+
ord.customer = ord.customer || {};
|
|
1590
|
+
if (user.companyName) ord.customer.name = user.companyName;
|
|
1591
|
+
if (user.inn) ord.customer.inn = user.inn;
|
|
1592
|
+
if (user.kpp !== undefined) ord.customer.kpp = user.kpp;
|
|
1593
|
+
if (user.legalAddress !== undefined) ord.customer.legalAddress = user.legalAddress;
|
|
1594
|
+
if (user.signerName !== undefined) ord.customer.signerName = user.signerName;
|
|
1595
|
+
if (user.signerPosition !== undefined) ord.customer.signerPosition = user.signerPosition;
|
|
1596
|
+
if (user.bankName !== undefined) ord.customer.bankName = user.bankName;
|
|
1597
|
+
if (user.bik !== undefined) ord.customer.bik = user.bik;
|
|
1598
|
+
if (user.bankAccount !== undefined) ord.customer.bankAccount = user.bankAccount;
|
|
1599
|
+
if (user.corrAccount !== undefined) ord.customer.corrAccount = user.corrAccount;
|
|
1600
|
+
if (user.phone !== undefined) ord.customer.phone = user.phone;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// Обновляем подписанный токен сессии с новыми реквизитами
|
|
1605
|
+
user.sessionKey = createSessionToken({
|
|
1606
|
+
userId: user.userId,
|
|
1607
|
+
email: user.email,
|
|
1608
|
+
name: user.name,
|
|
1609
|
+
companyName: user.companyName,
|
|
1610
|
+
inn: user.inn,
|
|
1611
|
+
isAdmin: Boolean(user.isAdmin),
|
|
1612
|
+
issuedAt: Date.now(),
|
|
1613
|
+
}, this.authorityKeys);
|
|
1614
|
+
|
|
1615
|
+
this._save();
|
|
1616
|
+
return this._formatUserResponse(user);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1486
1619
|
/**
|
|
1487
1620
|
* Зарегистрировать или авторизовать пользователя личного кабинета ODANT-WORK
|
|
1488
1621
|
* (по email, через OAuth или через мессенджеры Telegram / MAX / VK).
|
|
@@ -1515,6 +1648,7 @@ export class LicenseStore {
|
|
|
1515
1648
|
const userId = user?.userId || ('USR-' + crypto.createHash('sha256').update(cleanEmail).digest('hex').slice(0, 10).toUpperCase());
|
|
1516
1649
|
|
|
1517
1650
|
if (!user) {
|
|
1651
|
+
const safeCompany = (companyName && !String(companyName).includes('@')) ? String(companyName).trim() : '';
|
|
1518
1652
|
let lic = licenseId ? this.findLicense(licenseId) : null;
|
|
1519
1653
|
if (!lic) {
|
|
1520
1654
|
const summary = this.issueOrUpdateLicense({
|
|
@@ -1522,7 +1656,7 @@ export class LicenseStore {
|
|
|
1522
1656
|
ownerEmail: cleanEmail,
|
|
1523
1657
|
baseActivated: false,
|
|
1524
1658
|
customer: {
|
|
1525
|
-
name:
|
|
1659
|
+
name: safeCompany,
|
|
1526
1660
|
inn: inn || '',
|
|
1527
1661
|
email: cleanEmail,
|
|
1528
1662
|
phone: phone || '',
|
|
@@ -1541,10 +1675,18 @@ export class LicenseStore {
|
|
|
1541
1675
|
userId,
|
|
1542
1676
|
email: cleanEmail,
|
|
1543
1677
|
authProvider: cleanProvider,
|
|
1544
|
-
name: String(name ||
|
|
1678
|
+
name: String(name || safeCompany || cleanEmail.split('@')[0]).trim(),
|
|
1545
1679
|
position: String(position || '').trim(),
|
|
1546
|
-
companyName:
|
|
1680
|
+
companyName: safeCompany,
|
|
1547
1681
|
inn: String(inn || '').trim(),
|
|
1682
|
+
kpp: '',
|
|
1683
|
+
legalAddress: '',
|
|
1684
|
+
signerName: '',
|
|
1685
|
+
signerPosition: String(position || '').trim(),
|
|
1686
|
+
bankName: '',
|
|
1687
|
+
bik: '',
|
|
1688
|
+
bankAccount: '',
|
|
1689
|
+
corrAccount: '',
|
|
1548
1690
|
phone: String(phone || '').trim(),
|
|
1549
1691
|
licenseId: lic.licenseId,
|
|
1550
1692
|
sessionKey: 'wks_' + crypto.randomBytes(16).toString('hex'),
|
|
@@ -1590,7 +1732,7 @@ export class LicenseStore {
|
|
|
1590
1732
|
const isFirstAdmin = !Object.values(this.state.users || {}).some(u => u.isAdmin);
|
|
1591
1733
|
if (isFirstAdmin) {
|
|
1592
1734
|
user.isAdmin = true;
|
|
1593
|
-
user.role = '
|
|
1735
|
+
user.role = 'ADMIN';
|
|
1594
1736
|
}
|
|
1595
1737
|
|
|
1596
1738
|
// Выпускаем криптографически подписанный бессрочный токен сессии (wks_1...), устойчивый к перезапускам сервера
|
|
@@ -2285,7 +2427,7 @@ export class LicenseStore {
|
|
|
2285
2427
|
}
|
|
2286
2428
|
|
|
2287
2429
|
/**
|
|
2288
|
-
* Обновить способ оплаты и реквизиты юрлица для существующего заказа из Личного
|
|
2430
|
+
* Обновить способ оплаты и реквизиты юрлица для существующего заказа из Личного кабинета (и запомнить реквизиты в ЛК).
|
|
2289
2431
|
*/
|
|
2290
2432
|
prepareOrderPayment(orderId, {
|
|
2291
2433
|
paymentMethod = 'yookassa',
|
|
@@ -2293,16 +2435,31 @@ export class LicenseStore {
|
|
|
2293
2435
|
inn = '',
|
|
2294
2436
|
kpp = '',
|
|
2295
2437
|
legalAddress = '',
|
|
2438
|
+
signerName = '',
|
|
2439
|
+
signerPosition = '',
|
|
2440
|
+
bankName = '',
|
|
2441
|
+
bik = '',
|
|
2442
|
+
bankAccount = '',
|
|
2443
|
+
corrAccount = '',
|
|
2444
|
+
phone = '',
|
|
2296
2445
|
} = {}) {
|
|
2297
2446
|
const order = this.state.orders[orderId];
|
|
2298
2447
|
if (!order)
|
|
2299
2448
|
throw new Error(`Заказ ${orderId} не найден`);
|
|
2300
2449
|
order.paymentMethod = paymentMethod === 'invoice' ? 'invoice' : 'yookassa';
|
|
2301
2450
|
order.customer = order.customer || {};
|
|
2302
|
-
|
|
2451
|
+
const cleanComp = (companyName && !String(companyName).includes('@')) ? String(companyName).trim() : '';
|
|
2452
|
+
if (cleanComp) order.customer.name = cleanComp;
|
|
2303
2453
|
if (inn) order.customer.inn = String(inn).trim();
|
|
2304
|
-
if (kpp) order.customer.kpp = String(kpp).trim();
|
|
2305
|
-
if (legalAddress) order.customer.legalAddress = String(legalAddress).trim();
|
|
2454
|
+
if (kpp !== undefined && kpp !== '') order.customer.kpp = String(kpp).trim();
|
|
2455
|
+
if (legalAddress !== undefined && legalAddress !== '') order.customer.legalAddress = String(legalAddress).trim();
|
|
2456
|
+
if (signerName !== undefined && signerName !== '') order.customer.signerName = String(signerName).trim();
|
|
2457
|
+
if (signerPosition !== undefined && signerPosition !== '') order.customer.signerPosition = String(signerPosition).trim();
|
|
2458
|
+
if (bankName !== undefined && bankName !== '') order.customer.bankName = String(bankName).trim();
|
|
2459
|
+
if (bik !== undefined && bik !== '') order.customer.bik = String(bik).trim();
|
|
2460
|
+
if (bankAccount !== undefined && bankAccount !== '') order.customer.bankAccount = String(bankAccount).trim();
|
|
2461
|
+
if (corrAccount !== undefined && corrAccount !== '') order.customer.corrAccount = String(corrAccount).trim();
|
|
2462
|
+
if (phone !== undefined && phone !== '') order.customer.phone = String(phone).trim();
|
|
2306
2463
|
if (!order.invoiceNumber) {
|
|
2307
2464
|
order.invoiceNumber = 'СЧ-' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + '-' + orderId.slice(-4);
|
|
2308
2465
|
}
|
|
@@ -2313,14 +2470,26 @@ export class LicenseStore {
|
|
|
2313
2470
|
order.invoiceUrl = `/api/orders/${encodeURIComponent(orderId)}/invoice`;
|
|
2314
2471
|
}
|
|
2315
2472
|
if (order.reservationId && this.state.reservations[order.reservationId]) {
|
|
2316
|
-
if (
|
|
2473
|
+
if (cleanComp) this.state.reservations[order.reservationId].companyName = cleanComp;
|
|
2317
2474
|
if (inn) this.state.reservations[order.reservationId].inn = String(inn).trim();
|
|
2318
2475
|
}
|
|
2319
2476
|
if (order.ownerAccountId) {
|
|
2320
2477
|
const u = Object.values(this.state.users).find(usr => usr.userId === order.ownerAccountId);
|
|
2321
2478
|
if (u) {
|
|
2322
|
-
if (
|
|
2479
|
+
if (cleanComp) u.companyName = cleanComp;
|
|
2323
2480
|
if (inn) u.inn = String(inn).trim();
|
|
2481
|
+
if (kpp) u.kpp = String(kpp).trim();
|
|
2482
|
+
if (legalAddress) u.legalAddress = String(legalAddress).trim();
|
|
2483
|
+
if (signerName) u.signerName = String(signerName).trim();
|
|
2484
|
+
if (signerPosition) {
|
|
2485
|
+
u.signerPosition = String(signerPosition).trim();
|
|
2486
|
+
u.position = u.signerPosition;
|
|
2487
|
+
}
|
|
2488
|
+
if (bankName) u.bankName = String(bankName).trim();
|
|
2489
|
+
if (bik) u.bik = String(bik).trim();
|
|
2490
|
+
if (bankAccount) u.bankAccount = String(bankAccount).trim();
|
|
2491
|
+
if (corrAccount) u.corrAccount = String(corrAccount).trim();
|
|
2492
|
+
if (phone) u.phone = String(phone).trim();
|
|
2324
2493
|
}
|
|
2325
2494
|
}
|
|
2326
2495
|
this._save();
|