odant-work 1.0.0 → 1.0.2
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/$server/$folder/$class/$structure/$server/handlers/pages/form/license/$handler/class.js +1 -1
- package/$server/$folder/$class/$structure/$server/handlers/pages/form/license/$handler/license.js +11 -8
- package/$server/$folder/$class/handlers/pages/form/object/$handler/class.js +27 -0
- package/$server/$folder/$class/handlers/pages/form/object/$handler/object.js +352 -0
- package/$server/$folder/$class/handlers/pages/form/objects/$handler/objects.js +39 -2
- package/$server/$folder/handlers/pages/form/$handler/form.js +427 -68
- package/$server/$folder/lib/tools/tools.js +9 -2
- package/MODULES/Register/REGISTER/$register/$folder/$class/$account/handlers/pages/form/postings/$handler/postings.js +388 -103
- package/MODULES/Register/REGISTER/$register/$folder/$class/$register/handlers/pages/form/journal/$handler/journal.js +84 -5
- package/MODULES/Register/REGISTER/$register/$folder/$class/$register/handlers/pages/form/sheet/$handler/sheet.js +84 -5
- package/data/authority-key.json +5 -0
- package/data/store.json +182 -0
- package/oda/components/packets/packets.js +6 -0
- package/package.json +2 -2
- package/sources/data/api.js +180 -1
- package/sources/data/progress.md +37 -0
- package/sources/modules/license/index.js +28 -25
|
@@ -51,6 +51,74 @@ function _derivePacketsKey(from, to, fallback = '*') {
|
|
|
51
51
|
return fallback || '*';
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function _daysInMonth(y, m) {
|
|
55
|
+
return new Date(y, m, 0).getDate();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _weekBounds(dt) {
|
|
59
|
+
const dow = dt.getDay();
|
|
60
|
+
const diffToMon = dow === 0 ? -6 : 1 - dow;
|
|
61
|
+
const mon = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate() + diffToMon);
|
|
62
|
+
const sun = new Date(mon.getFullYear(), mon.getMonth(), mon.getDate() + 6);
|
|
63
|
+
return { from: _day(mon), to: _day(sun) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _boundsFromPacketsKey(key, defaultMode = 'month') {
|
|
67
|
+
const k = String(key || '').trim();
|
|
68
|
+
if (!k || k === '*')
|
|
69
|
+
return { from: '', to: '' };
|
|
70
|
+
if (k.startsWith('period:')) {
|
|
71
|
+
const [, s = '', e = ''] = k.split(':');
|
|
72
|
+
return { from: s, to: e };
|
|
73
|
+
}
|
|
74
|
+
if (k === 'now' || k.startsWith('now:')) {
|
|
75
|
+
const gran = k.startsWith('now:') ? k.slice(4) : defaultMode;
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const y = now.getFullYear(), m = now.getMonth() + 1;
|
|
78
|
+
if (gran === 'day') {
|
|
79
|
+
const d = _day(now);
|
|
80
|
+
return { from: d, to: d };
|
|
81
|
+
}
|
|
82
|
+
if (gran === 'week')
|
|
83
|
+
return _weekBounds(now);
|
|
84
|
+
if (gran === 'quarter') {
|
|
85
|
+
const q = Math.floor((m - 1) / 3) + 1;
|
|
86
|
+
const m1 = String((q - 1) * 3 + 1).padStart(2, '0');
|
|
87
|
+
const m2 = (q - 1) * 3 + 3;
|
|
88
|
+
return { from: `${y}-${m1}-01`, to: `${y}-${String(m2).padStart(2, '0')}-${String(_daysInMonth(y, m2)).padStart(2, '0')}` };
|
|
89
|
+
}
|
|
90
|
+
if (gran === 'year')
|
|
91
|
+
return { from: `${y}-01-01`, to: `${y}-12-31` };
|
|
92
|
+
const ms = String(m).padStart(2, '0');
|
|
93
|
+
return { from: `${y}-${ms}-01`, to: `${y}-${ms}-${String(_daysInMonth(y, m)).padStart(2, '0')}` };
|
|
94
|
+
}
|
|
95
|
+
const clean = k.replace(/\*+$/, '');
|
|
96
|
+
if (/^\d{4}$/.test(clean))
|
|
97
|
+
return { from: `${clean}-01-01`, to: `${clean}-12-31` };
|
|
98
|
+
const qm = /^(\d{4})-q([1-4])$/i.exec(clean);
|
|
99
|
+
if (qm) {
|
|
100
|
+
const y = +qm[1], q = +qm[2];
|
|
101
|
+
const m1 = String((q - 1) * 3 + 1).padStart(2, '0');
|
|
102
|
+
const m2 = (q - 1) * 3 + 3;
|
|
103
|
+
return { from: `${y}-${m1}-01`, to: `${y}-${String(m2).padStart(2, '0')}-${String(_daysInMonth(y, m2)).padStart(2, '0')}` };
|
|
104
|
+
}
|
|
105
|
+
const mm = /^(\d{4})-(0[1-9]|1[0-2])$/.exec(clean);
|
|
106
|
+
if (mm) {
|
|
107
|
+
const y = +mm[1], m = +mm[2];
|
|
108
|
+
return { from: `${clean}-01`, to: `${clean}-${String(_daysInMonth(y, m)).padStart(2, '0')}` };
|
|
109
|
+
}
|
|
110
|
+
if (clean.startsWith('week:')) {
|
|
111
|
+
const monIso = clean.slice(5);
|
|
112
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(monIso)) {
|
|
113
|
+
const [y, m, d] = monIso.split('-').map(Number);
|
|
114
|
+
return _weekBounds(new Date(y, m - 1, d));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(clean))
|
|
118
|
+
return { from: clean, to: clean };
|
|
119
|
+
return { from: '', to: '' };
|
|
120
|
+
}
|
|
121
|
+
|
|
54
122
|
const money = v => (Number(v) ? Number(v).toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '');
|
|
55
123
|
|
|
56
124
|
export default {
|
|
@@ -449,14 +517,23 @@ export default {
|
|
|
449
517
|
if (!hasNavPeriod && (this.from || this.to))
|
|
450
518
|
return;
|
|
451
519
|
const saved = _readSavedPeriod();
|
|
452
|
-
const
|
|
520
|
+
const pk = typeof this.packetsKey === 'string' ? this.packetsKey : '';
|
|
521
|
+
const src = hasNavPeriod ? nav : (saved || (pk && pk !== '*' ? { packetsKey: pk } : null));
|
|
453
522
|
if (!src)
|
|
454
523
|
return;
|
|
455
524
|
this._initializingPackets = true;
|
|
456
525
|
try {
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
526
|
+
const rawKey = typeof src.packetsKey === 'string' && src.packetsKey ? src.packetsKey : (pk || '*');
|
|
527
|
+
let from = String(src.from || '');
|
|
528
|
+
let to = String(src.to || '');
|
|
529
|
+
if ((!from && !to && rawKey !== '*') || rawKey === 'now' || rawKey.startsWith('now:')) {
|
|
530
|
+
const derived = _boundsFromPacketsKey(rawKey, 'month');
|
|
531
|
+
if (derived.from && derived.to) {
|
|
532
|
+
from = derived.from;
|
|
533
|
+
to = derived.to;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const key = _derivePacketsKey(from, to, rawKey);
|
|
460
537
|
this.from = from;
|
|
461
538
|
this.to = to;
|
|
462
539
|
this._packetFiltered = !!(from || to);
|
|
@@ -526,7 +603,9 @@ export default {
|
|
|
526
603
|
this.packetsStart = r.start;
|
|
527
604
|
this.packetsEnd = r.end;
|
|
528
605
|
this.packetDays = Array.isArray(r.days) && r.days.length ? r.days : null;
|
|
529
|
-
|
|
606
|
+
const keyStr = typeof this.packetsKey === 'string' ? this.packetsKey : '';
|
|
607
|
+
const isNowKey = keyStr === 'now' || keyStr.startsWith('now:');
|
|
608
|
+
if (this._packetFiltered && !isNowKey && this.to && this.to < r.start) {
|
|
530
609
|
this._initializingPackets = true;
|
|
531
610
|
try {
|
|
532
611
|
this._packetFiltered = false;
|
|
@@ -53,6 +53,74 @@ function _derivePacketsKey(from, to, fallback = '*') {
|
|
|
53
53
|
return fallback || '*';
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function _daysInMonth(y, m) {
|
|
57
|
+
return new Date(y, m, 0).getDate();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function _weekBounds(dt) {
|
|
61
|
+
const dow = dt.getDay();
|
|
62
|
+
const diffToMon = dow === 0 ? -6 : 1 - dow;
|
|
63
|
+
const mon = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate() + diffToMon);
|
|
64
|
+
const sun = new Date(mon.getFullYear(), mon.getMonth(), mon.getDate() + 6);
|
|
65
|
+
return { from: _day(mon), to: _day(sun) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function _boundsFromPacketsKey(key, defaultMode = 'month') {
|
|
69
|
+
const k = String(key || '').trim();
|
|
70
|
+
if (!k || k === '*')
|
|
71
|
+
return { from: '', to: '' };
|
|
72
|
+
if (k.startsWith('period:')) {
|
|
73
|
+
const [, s = '', e = ''] = k.split(':');
|
|
74
|
+
return { from: s, to: e };
|
|
75
|
+
}
|
|
76
|
+
if (k === 'now' || k.startsWith('now:')) {
|
|
77
|
+
const gran = k.startsWith('now:') ? k.slice(4) : defaultMode;
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const y = now.getFullYear(), m = now.getMonth() + 1;
|
|
80
|
+
if (gran === 'day') {
|
|
81
|
+
const d = _day(now);
|
|
82
|
+
return { from: d, to: d };
|
|
83
|
+
}
|
|
84
|
+
if (gran === 'week')
|
|
85
|
+
return _weekBounds(now);
|
|
86
|
+
if (gran === 'quarter') {
|
|
87
|
+
const q = Math.floor((m - 1) / 3) + 1;
|
|
88
|
+
const m1 = String((q - 1) * 3 + 1).padStart(2, '0');
|
|
89
|
+
const m2 = (q - 1) * 3 + 3;
|
|
90
|
+
return { from: `${y}-${m1}-01`, to: `${y}-${String(m2).padStart(2, '0')}-${String(_daysInMonth(y, m2)).padStart(2, '0')}` };
|
|
91
|
+
}
|
|
92
|
+
if (gran === 'year')
|
|
93
|
+
return { from: `${y}-01-01`, to: `${y}-12-31` };
|
|
94
|
+
const ms = String(m).padStart(2, '0');
|
|
95
|
+
return { from: `${y}-${ms}-01`, to: `${y}-${ms}-${String(_daysInMonth(y, m)).padStart(2, '0')}` };
|
|
96
|
+
}
|
|
97
|
+
const clean = k.replace(/\*+$/, '');
|
|
98
|
+
if (/^\d{4}$/.test(clean))
|
|
99
|
+
return { from: `${clean}-01-01`, to: `${clean}-12-31` };
|
|
100
|
+
const qm = /^(\d{4})-q([1-4])$/i.exec(clean);
|
|
101
|
+
if (qm) {
|
|
102
|
+
const y = +qm[1], q = +qm[2];
|
|
103
|
+
const m1 = String((q - 1) * 3 + 1).padStart(2, '0');
|
|
104
|
+
const m2 = (q - 1) * 3 + 3;
|
|
105
|
+
return { from: `${y}-${m1}-01`, to: `${y}-${String(m2).padStart(2, '0')}-${String(_daysInMonth(y, m2)).padStart(2, '0')}` };
|
|
106
|
+
}
|
|
107
|
+
const mm = /^(\d{4})-(0[1-9]|1[0-2])$/.exec(clean);
|
|
108
|
+
if (mm) {
|
|
109
|
+
const y = +mm[1], m = +mm[2];
|
|
110
|
+
return { from: `${clean}-01`, to: `${clean}-${String(_daysInMonth(y, m)).padStart(2, '0')}` };
|
|
111
|
+
}
|
|
112
|
+
if (clean.startsWith('week:')) {
|
|
113
|
+
const monIso = clean.slice(5);
|
|
114
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(monIso)) {
|
|
115
|
+
const [y, m, d] = monIso.split('-').map(Number);
|
|
116
|
+
return _weekBounds(new Date(y, m - 1, d));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(clean))
|
|
120
|
+
return { from: clean, to: clean };
|
|
121
|
+
return { from: '', to: '' };
|
|
122
|
+
}
|
|
123
|
+
|
|
56
124
|
export default {
|
|
57
125
|
imports: 'oda//button, oda//packets',
|
|
58
126
|
template: /*html*/`
|
|
@@ -489,14 +557,23 @@ export default {
|
|
|
489
557
|
if (!hasNavPeriod && (this.from || this.to))
|
|
490
558
|
return;
|
|
491
559
|
const saved = _readSavedPeriod();
|
|
492
|
-
const
|
|
560
|
+
const pk = typeof this.packetsKey === 'string' ? this.packetsKey : '';
|
|
561
|
+
const src = hasNavPeriod ? nav : (saved || (pk && pk !== '*' ? { packetsKey: pk } : null));
|
|
493
562
|
if (!src)
|
|
494
563
|
return;
|
|
495
564
|
this._initializingPackets = true;
|
|
496
565
|
try {
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
566
|
+
const rawKey = typeof src.packetsKey === 'string' && src.packetsKey ? src.packetsKey : (pk || '*');
|
|
567
|
+
let from = String(src.from || '');
|
|
568
|
+
let to = String(src.to || '');
|
|
569
|
+
if ((!from && !to && rawKey !== '*') || rawKey === 'now' || rawKey.startsWith('now:')) {
|
|
570
|
+
const derived = _boundsFromPacketsKey(rawKey, 'month');
|
|
571
|
+
if (derived.from && derived.to) {
|
|
572
|
+
from = derived.from;
|
|
573
|
+
to = derived.to;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const key = _derivePacketsKey(from, to, rawKey);
|
|
500
577
|
this.from = from;
|
|
501
578
|
this.to = to;
|
|
502
579
|
this._packetFiltered = !!(from || to);
|
|
@@ -573,7 +650,9 @@ export default {
|
|
|
573
650
|
this.packetsStart = r.start;
|
|
574
651
|
this.packetsEnd = r.end;
|
|
575
652
|
this.packetDays = Array.isArray(r.days) && r.days.length ? r.days : null;
|
|
576
|
-
|
|
653
|
+
const keyStr = typeof this.packetsKey === 'string' ? this.packetsKey : '';
|
|
654
|
+
const isNowKey = keyStr === 'now' || keyStr.startsWith('now:');
|
|
655
|
+
if (this._packetFiltered && !isNowKey && this.to && this.to < r.start) {
|
|
577
656
|
this._initializingPackets = true;
|
|
578
657
|
try {
|
|
579
658
|
this._packetFiltered = false;
|
package/data/store.json
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
{
|
|
2
|
+
"licenses": {
|
|
3
|
+
"WORK-PAK-300A0DD0": {
|
|
4
|
+
"licenseId": "WORK-PAK-300A0DD0",
|
|
5
|
+
"issuedAt": 1793262666169,
|
|
6
|
+
"updatedAt": 1793271130179,
|
|
7
|
+
"issuer": "work.odant.org",
|
|
8
|
+
"customer": {
|
|
9
|
+
"name": "ООО «Маслоторг»",
|
|
10
|
+
"inn": "",
|
|
11
|
+
"email": "admin@maslotorg.ru",
|
|
12
|
+
"phone": ""
|
|
13
|
+
},
|
|
14
|
+
"hardware": {
|
|
15
|
+
"hwid": "PAK-3C564FAE8C0E",
|
|
16
|
+
"nodeFingerprint": "",
|
|
17
|
+
"sku": "WORK-PAK-495-192-4T",
|
|
18
|
+
"cpu": "AMD Ryzen™ AI Max+ PRO 495 (Zen 5, 16 ядер / 32 потока, до 5.2 ГГц, кэш 80 МБ)",
|
|
19
|
+
"ramGb": 192,
|
|
20
|
+
"ssdTb": 4,
|
|
21
|
+
"warrantyYears": 1,
|
|
22
|
+
"warrantyUntil": 1824798666169,
|
|
23
|
+
"warrantyProvider": "Партнёр-интегратор WORK"
|
|
24
|
+
},
|
|
25
|
+
"partner": null,
|
|
26
|
+
"baseSeats": 10,
|
|
27
|
+
"perpetualBase": true,
|
|
28
|
+
"addonSubscriptions": [],
|
|
29
|
+
"support": {
|
|
30
|
+
"tier": "business",
|
|
31
|
+
"includedUntil": 1824798666169,
|
|
32
|
+
"ticketsUsed": 1,
|
|
33
|
+
"bindCode": "BIND-4D86AF"
|
|
34
|
+
},
|
|
35
|
+
"protocol": "WORK-LIC-v1",
|
|
36
|
+
"issuerPublicKey": "MCowBQYDK2VwAyEAWUaWWCwtQMu6P1vyw67NV4k1n+u2rWlKsYzqwMZWLBs=",
|
|
37
|
+
"signature": "JJ4b3YNiKbXO4enxRDYCVrE0+ZOSCpnaG268vW/RE/ucAFx+tJi9Xy4dLjfHogBxVw9Y5sAdivwCovq3yKTMBg=="
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"orders": {
|
|
41
|
+
"ORD-MVTAMUDM-0F96": {
|
|
42
|
+
"orderId": "ORD-MVTAMUDM-0F96",
|
|
43
|
+
"licenseId": "WORK-PAK-300A0DD0",
|
|
44
|
+
"partnerId": "",
|
|
45
|
+
"partnerDiscountPercent": 0,
|
|
46
|
+
"partnerMarginRub": 0,
|
|
47
|
+
"includeBasePak": false,
|
|
48
|
+
"tier": "business",
|
|
49
|
+
"tierName": "Business",
|
|
50
|
+
"addonSeats": 10,
|
|
51
|
+
"years": 1,
|
|
52
|
+
"amountRub": 135000,
|
|
53
|
+
"items": [
|
|
54
|
+
{
|
|
55
|
+
"title": "Годовая лицензия ODANT-WORK на доп. рабочее место (уровень Business, 1 г., скидка −10% за объём)",
|
|
56
|
+
"quantity": 10,
|
|
57
|
+
"unitPriceRub": 13500,
|
|
58
|
+
"totalRub": 135000
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"customer": {
|
|
62
|
+
"name": "ООО «Маслоторг»",
|
|
63
|
+
"inn": "7701234567",
|
|
64
|
+
"email": "admin@maslotorg.ru",
|
|
65
|
+
"phone": ""
|
|
66
|
+
},
|
|
67
|
+
"status": "pending",
|
|
68
|
+
"paymentId": "yk_sandbox_2874a3734d40ab1b",
|
|
69
|
+
"confirmationUrl": "http://localhost:8020/pay/sandbox/ORD-MVTAMUDM-0F96",
|
|
70
|
+
"returnUrl": "https://work.odant.org",
|
|
71
|
+
"createdAt": 1793263243594,
|
|
72
|
+
"paidAt": null,
|
|
73
|
+
"yookassaMode": "sandbox"
|
|
74
|
+
},
|
|
75
|
+
"ORD-FR-MVTAMUET-FFAD": {
|
|
76
|
+
"orderId": "ORD-FR-MVTAMUET-FFAD",
|
|
77
|
+
"kind": "franchise_reservation",
|
|
78
|
+
"reservationId": "RES-TA-8EFAA5",
|
|
79
|
+
"licenseId": "RES-TA-8EFAA5",
|
|
80
|
+
"regionCode": "RU-TA",
|
|
81
|
+
"regionName": "Республика Татарстан",
|
|
82
|
+
"tierName": "Резерв франшизы (Республика Татарстан)",
|
|
83
|
+
"addonSeats": 1,
|
|
84
|
+
"years": 1,
|
|
85
|
+
"amountRub": 100000,
|
|
86
|
+
"franchiseFeeRub": 6500000,
|
|
87
|
+
"items": [
|
|
88
|
+
{
|
|
89
|
+
"title": "Невозвратный залог за резервирование региональной франшизы ODANT-WORK (Республика Татарстан, стоимость франшизы 6 500 000 ₽)",
|
|
90
|
+
"quantity": 1,
|
|
91
|
+
"unitPriceRub": 100000,
|
|
92
|
+
"totalRub": 100000
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"customer": {
|
|
96
|
+
"name": "ООО «Волга-Интеграция»",
|
|
97
|
+
"inn": "1655123456",
|
|
98
|
+
"email": "partner@volga-it.ru",
|
|
99
|
+
"phone": ""
|
|
100
|
+
},
|
|
101
|
+
"status": "pending",
|
|
102
|
+
"paymentId": "yk_sandbox_fae9588353cc1267",
|
|
103
|
+
"confirmationUrl": "http://localhost:8020/pay/sandbox/ORD-FR-MVTAMUET-FFAD",
|
|
104
|
+
"returnUrl": "https://work.odant.org",
|
|
105
|
+
"createdAt": 1793263243636,
|
|
106
|
+
"paidAt": null,
|
|
107
|
+
"yookassaMode": "sandbox"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"tickets": {
|
|
111
|
+
"TCK-0000-28BE": {
|
|
112
|
+
"ticketId": "TCK-0000-28BE",
|
|
113
|
+
"licenseId": "WORK-PAK-300A0DD0",
|
|
114
|
+
"customerName": "ООО «Маслоторг»",
|
|
115
|
+
"channel": "max",
|
|
116
|
+
"chatId": "client-101",
|
|
117
|
+
"userName": "Пользователь MAX",
|
|
118
|
+
"tier": "business",
|
|
119
|
+
"tierName": "Business",
|
|
120
|
+
"reactionMinutes": 120,
|
|
121
|
+
"reactionLabel": "до 2 часов",
|
|
122
|
+
"slaDeadlineAt": 1793270443784,
|
|
123
|
+
"status": "open",
|
|
124
|
+
"createdAt": 1793263243784,
|
|
125
|
+
"firstReplyAt": null,
|
|
126
|
+
"slaMet": null,
|
|
127
|
+
"messages": [
|
|
128
|
+
{
|
|
129
|
+
"from": "client",
|
|
130
|
+
"author": "Пользователь MAX",
|
|
131
|
+
"text": "Подскажите, как синхронизировать сертификат после докупки 10 мест?",
|
|
132
|
+
"time": 1793263243784
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"botBindings": {
|
|
138
|
+
"max:client-101": {
|
|
139
|
+
"channel": "max",
|
|
140
|
+
"chatId": "client-101",
|
|
141
|
+
"userName": "Пользователь MAX",
|
|
142
|
+
"licenseId": "WORK-PAK-300A0DD0",
|
|
143
|
+
"boundAt": 1793263243683
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"partners": {},
|
|
147
|
+
"reservations": {
|
|
148
|
+
"RES-TA-8EFAA5": {
|
|
149
|
+
"reservationId": "RES-TA-8EFAA5",
|
|
150
|
+
"orderId": "ORD-FR-MVTAMUET-FFAD",
|
|
151
|
+
"regionCode": "RU-TA",
|
|
152
|
+
"regionName": "Республика Татарстан",
|
|
153
|
+
"district": "ПФО",
|
|
154
|
+
"companyName": "ООО «Волга-Интеграция»",
|
|
155
|
+
"inn": "1655123456",
|
|
156
|
+
"email": "partner@volga-it.ru",
|
|
157
|
+
"phone": "",
|
|
158
|
+
"contactPerson": "",
|
|
159
|
+
"franchiseFeeRub": 6500000,
|
|
160
|
+
"nonRefundableDepositRub": 100000,
|
|
161
|
+
"remainingFranchiseFeeRub": 6400000,
|
|
162
|
+
"status": "reserved_pending_deposit",
|
|
163
|
+
"depositPaid": false,
|
|
164
|
+
"createdAt": 1793263243636,
|
|
165
|
+
"expiresAt": 1794472843636
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"certificates": {},
|
|
169
|
+
"users": {
|
|
170
|
+
"admin@maslotorg.ru": {
|
|
171
|
+
"userId": "USR-4DB3960551",
|
|
172
|
+
"email": "admin@maslotorg.ru",
|
|
173
|
+
"name": "ООО «Маслоторг»",
|
|
174
|
+
"companyName": "ООО «Маслоторг»",
|
|
175
|
+
"inn": "",
|
|
176
|
+
"licenseId": "WORK-PAK-300A0DD0",
|
|
177
|
+
"sessionKey": "wks_d0839169d8c9db42e8815c031140eb7c",
|
|
178
|
+
"createdAt": 1793262666168,
|
|
179
|
+
"lastLoginAt": 1793262666168
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -406,6 +406,12 @@ ODA({
|
|
|
406
406
|
this._nowItem.idleLabel = NOW_IDLE_LABELS[mode] || 'Месяц';
|
|
407
407
|
this._nowItem.label = this._nowItem.idleLabel;
|
|
408
408
|
}
|
|
409
|
+
},
|
|
410
|
+
_onValueChanged(value) {
|
|
411
|
+
if (!this._allItem) return;
|
|
412
|
+
const currentKey = this._selectionKey(this.selection);
|
|
413
|
+
if (value === currentKey) return;
|
|
414
|
+
this._applySelection(this._itemsByKey(value) ?? [this._allItem]);
|
|
409
415
|
}
|
|
410
416
|
},
|
|
411
417
|
_hasCustomDays: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odant-work",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "WORK — платформа цифровой организации (учётная система уровня 1С, документооборот, чаты и локальный мультимодальный ИИ)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "sources/work.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test": "node --test \"tests/**/*.test.js\"",
|
|
15
15
|
"test:ci": "npm test",
|
|
16
16
|
"setup-mirror": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/setup-svn-mirror.ps1",
|
|
17
|
-
"publish": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/svn-publish.ps1",
|
|
17
|
+
"svn:publish": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/svn-publish.ps1",
|
|
18
18
|
"sync": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/svn-sync.ps1",
|
|
19
19
|
"mirror": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/mirror-to-git.ps1",
|
|
20
20
|
"test:ui": "node tests/ui/run.mjs",
|
package/sources/data/api.js
CHANGED
|
@@ -473,6 +473,179 @@ function withTargetPaths(db, schema) {
|
|
|
473
473
|
return { ...schema, fields: walk(schema.fields), overlay: walk(schema.overlay) };
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
/**
|
|
477
|
+
* Собрать аналитические признаки по полям прилинкованного справочника слота счёта
|
|
478
|
+
* (Наименование + поля схемы $catalog, вложенных структур $struct и расширений OVERLAY у $virtual)
|
|
479
|
+
* со списком реально существующих значений для панели «Анализ счёта».
|
|
480
|
+
*/
|
|
481
|
+
function buildCatalogAnalyticFields(db, slotField) {
|
|
482
|
+
const slotId = slotField.id;
|
|
483
|
+
const slotLabel = slotField.label || slotId;
|
|
484
|
+
const targetPath = slotField.targetPath || slotField.target;
|
|
485
|
+
const targetSchema = slotField.target ? withTargetPaths(db, classSchema(db, slotField.target)) : null;
|
|
486
|
+
if (!targetSchema) {
|
|
487
|
+
return [{
|
|
488
|
+
id: slotId,
|
|
489
|
+
spec: slotId,
|
|
490
|
+
slotId,
|
|
491
|
+
fieldId: 'name',
|
|
492
|
+
label: 'Наименование',
|
|
493
|
+
slotLabel,
|
|
494
|
+
isObject: true,
|
|
495
|
+
type: 'link',
|
|
496
|
+
catalog: targetPath,
|
|
497
|
+
options: [],
|
|
498
|
+
}];
|
|
499
|
+
}
|
|
500
|
+
const isVirt = targetSchema.type === '$virtual';
|
|
501
|
+
const regCid = isVirt ? (targetSchema.source?.registry || targetSchema.cid) : targetSchema.cid;
|
|
502
|
+
const descCids = descendants(db, regCid);
|
|
503
|
+
|
|
504
|
+
let objRows = [];
|
|
505
|
+
if (descCids.length) {
|
|
506
|
+
objRows = db.prepare(
|
|
507
|
+
`SELECT id, cid, body FROM objects WHERE cid IN (${descCids.map(() => '?').join(',')}) AND status <> 'deleted' AND ext_of IS NULL ORDER BY updated DESC LIMIT 300`
|
|
508
|
+
).all(...descCids);
|
|
509
|
+
}
|
|
510
|
+
const extByObj = new Map();
|
|
511
|
+
if (isVirt) {
|
|
512
|
+
const virtCids = descendants(db, targetSchema.cid);
|
|
513
|
+
if (virtCids.length) {
|
|
514
|
+
for (const r of db.prepare(
|
|
515
|
+
`SELECT ext_of, body FROM objects WHERE cid IN (${virtCids.map(() => '?').join(',')}) AND status <> 'deleted' AND ext_of IS NOT NULL LIMIT 300`
|
|
516
|
+
).all(...virtCids)) {
|
|
517
|
+
try {
|
|
518
|
+
extByObj.set(r.ext_of, JSON.parse(r.body || '{}'));
|
|
519
|
+
}
|
|
520
|
+
catch { /* ignore */ }
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const parsedObjs = objRows.map(r => {
|
|
526
|
+
let body = {};
|
|
527
|
+
try { body = JSON.parse(r.body || '{}'); } catch { /* ignore */ }
|
|
528
|
+
const title = String(body.name || body.label || body.title || r.id).trim();
|
|
529
|
+
return { id: r.id, title, body, ext: extByObj.get(r.id) || null };
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
const objectOptions = parsedObjs
|
|
533
|
+
.map(o => ({ value: o.id, label: o.title || o.id }))
|
|
534
|
+
.sort((a, b) => a.label.localeCompare(b.label, 'ru', { numeric: true }));
|
|
535
|
+
|
|
536
|
+
const fieldDefs = [];
|
|
537
|
+
const seenFieldIds = new Set(['name', 'time']);
|
|
538
|
+
const collectFields = (list, isOverlay = false, prefix = '', prefixLabel = '') => {
|
|
539
|
+
for (const f of list || []) {
|
|
540
|
+
if (!f?.id || f.type === 'table')
|
|
541
|
+
continue;
|
|
542
|
+
const fullId = prefix ? `${prefix}.${f.id}` : f.id;
|
|
543
|
+
const fullLabel = prefixLabel ? `${prefixLabel}: ${f.label || f.id}` : (f.label || f.id);
|
|
544
|
+
if (f.type === 'struct' && Array.isArray(f.fields) && f.fields.length) {
|
|
545
|
+
collectFields(f.fields, isOverlay, fullId, fullLabel);
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (seenFieldIds.has(fullId))
|
|
549
|
+
continue;
|
|
550
|
+
seenFieldIds.add(fullId);
|
|
551
|
+
fieldDefs.push({
|
|
552
|
+
fieldId: fullId,
|
|
553
|
+
label: fullLabel,
|
|
554
|
+
type: f.type || 'string',
|
|
555
|
+
target: f.target,
|
|
556
|
+
values: f.values || f.options,
|
|
557
|
+
isOverlay,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
collectFields(targetSchema.fields, false);
|
|
563
|
+
if (isVirt)
|
|
564
|
+
collectFields(targetSchema.overlay, true);
|
|
565
|
+
for (const cid of descCids) {
|
|
566
|
+
if (cid === targetSchema.cid)
|
|
567
|
+
continue;
|
|
568
|
+
const cs = classSchema(db, cid);
|
|
569
|
+
if (cs)
|
|
570
|
+
collectFields(cs.fields, false);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const refIdsToResolve = new Set();
|
|
574
|
+
const rawValuesByField = new Map();
|
|
575
|
+
for (const fd of fieldDefs) {
|
|
576
|
+
const vals = new Set();
|
|
577
|
+
for (const o of parsedObjs) {
|
|
578
|
+
let v;
|
|
579
|
+
if (fd.isOverlay && o.ext) {
|
|
580
|
+
v = o.ext[fd.fieldId];
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
let cur = o.body;
|
|
584
|
+
for (const part of fd.fieldId.split('.')) {
|
|
585
|
+
if (!cur || typeof cur !== 'object') { cur = undefined; break; }
|
|
586
|
+
cur = cur[part];
|
|
587
|
+
}
|
|
588
|
+
v = cur;
|
|
589
|
+
}
|
|
590
|
+
if (v === undefined || v === null || v === '' || typeof v === 'object')
|
|
591
|
+
continue;
|
|
592
|
+
const strVal = String(v).trim();
|
|
593
|
+
if (!strVal)
|
|
594
|
+
continue;
|
|
595
|
+
vals.add(strVal);
|
|
596
|
+
if (fd.type === 'link')
|
|
597
|
+
refIdsToResolve.add(strVal);
|
|
598
|
+
}
|
|
599
|
+
rawValuesByField.set(fd.fieldId, [...vals]);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const refNames = refIdsToResolve.size ? namesOf(db, [...refIdsToResolve]).objects : {};
|
|
603
|
+
|
|
604
|
+
const out = [{
|
|
605
|
+
id: slotId,
|
|
606
|
+
spec: slotId,
|
|
607
|
+
slotId,
|
|
608
|
+
fieldId: 'name',
|
|
609
|
+
label: 'Наименование',
|
|
610
|
+
slotLabel,
|
|
611
|
+
isObject: true,
|
|
612
|
+
type: 'link',
|
|
613
|
+
catalog: targetPath,
|
|
614
|
+
options: objectOptions,
|
|
615
|
+
}];
|
|
616
|
+
|
|
617
|
+
for (const fd of fieldDefs) {
|
|
618
|
+
const rawVals = rawValuesByField.get(fd.fieldId) || [];
|
|
619
|
+
let options = [];
|
|
620
|
+
if (fd.type === 'boolean') {
|
|
621
|
+
options = [{ value: '1', label: 'Да' }, { value: '0', label: 'Нет' }];
|
|
622
|
+
}
|
|
623
|
+
else if (fd.type === 'enum' && Array.isArray(fd.values) && fd.values.length) {
|
|
624
|
+
options = fd.values.map(v => (v && typeof v === 'object'
|
|
625
|
+
? { value: String(v.id ?? v.value), label: String(v.label ?? v.id ?? v.value) }
|
|
626
|
+
: { value: String(v), label: String(v) }));
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
options = rawVals.map(v => ({
|
|
630
|
+
value: v,
|
|
631
|
+
label: fd.type === 'link' ? (refNames[v] || v) : v,
|
|
632
|
+
})).sort((a, b) => a.label.localeCompare(b.label, 'ru', { numeric: true }));
|
|
633
|
+
}
|
|
634
|
+
out.push({
|
|
635
|
+
id: `${slotId}.${fd.fieldId}`,
|
|
636
|
+
spec: `${slotId}.${fd.fieldId}`,
|
|
637
|
+
slotId,
|
|
638
|
+
fieldId: fd.fieldId,
|
|
639
|
+
label: fd.label,
|
|
640
|
+
slotLabel,
|
|
641
|
+
isObject: false,
|
|
642
|
+
type: fd.type,
|
|
643
|
+
options,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
|
|
476
649
|
/**
|
|
477
650
|
* Поддерево класса для форм: `classes` (cid → описание потомков), `leaves` (листья для создания
|
|
478
651
|
* объектов в группе) и `slots` (слоты аналитик счёта или журнала по всему поддереву счетов).
|
|
@@ -508,7 +681,13 @@ function schemaSubtree(db, cls) {
|
|
|
508
681
|
for (const f of analyticSlots(s || { fields: [] })) {
|
|
509
682
|
if (!seenSlots.has(f.id)) {
|
|
510
683
|
seenSlots.add(f.id);
|
|
511
|
-
slots.push({
|
|
684
|
+
slots.push({
|
|
685
|
+
id: f.id,
|
|
686
|
+
label: f.label || f.id,
|
|
687
|
+
target: f.target,
|
|
688
|
+
targetPath: f.targetPath || f.target,
|
|
689
|
+
fields: buildCatalogAnalyticFields(db, f),
|
|
690
|
+
});
|
|
512
691
|
}
|
|
513
692
|
}
|
|
514
693
|
}
|