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.
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  icon: 'carbon:certificate',
3
- label: 'Лицензия ПАК',
3
+ label: 'Лицензия ODANT-WORK',
4
4
  roles: ['ADMIN', 'BOSS'],
5
5
  order: 3,
6
6
  allowUse($context) {
@@ -181,7 +181,7 @@ export default {
181
181
  <div class="kv-list">
182
182
  <div class="kv-row">
183
183
  <span class="kv-label">Платформа ПАК</span>
184
- <span class="kv-val">{{status?.pakSpec?.cpu || 'AMD Ryzen 495 8085'}} · {{status?.pakSpec?.ramGb || 192}} ГБ RAM · SSD {{status?.pakSpec?.ssdTb || 4}} ТБ</span>
184
+ <span class="kv-val">{{status?.pakSpec?.cpu || 'AMD Ryzen™ AI Max+ PRO 495'}} · {{status?.pakSpec?.ramGb || 192}} ГБ RAM · SSD {{status?.pakSpec?.ssdTb || 4}} ТБ</span>
185
185
  </div>
186
186
  <div class="kv-row">
187
187
  <span class="kv-label">Hardware ID (HWID)</span>
@@ -204,19 +204,19 @@ export default {
204
204
  <oda-icon icon="carbon:user-multiple" :icon-size="20"></oda-icon>
205
205
  <span>Одновременные сессии (рабочие места)</span>
206
206
  </div>
207
- <span class="badge">Вечная база: {{status?.baseSeats || 5}} мест</span>
207
+ <span class="badge">Вечная база: {{status?.baseSeats || 10}} мест</span>
208
208
  </div>
209
209
  <div class="seats-meter">
210
- <span class="seats-big">{{status?.activeSeatsCount || 0}} / {{status?.totalSeats || 5}}</span>
211
- <span>активных сессий ({{status?.baseSeats || 5}} вечных + {{status?.addonSeats || 0}} срочных годовых)</span>
210
+ <span class="seats-big">{{status?.activeSeatsCount || 0}} / {{status?.totalSeats || 10}}</span>
211
+ <span>активных сессий ({{status?.baseSeats || 10}} вечных + {{status?.addonSeats || 0}} срочных годовых)</span>
212
212
  </div>
213
213
  <div class="kv-list">
214
214
  <div class="kv-row">
215
215
  <span class="kv-label">Уровень поддержки (SLA)</span>
216
- <span class="kv-val">{{status?.support?.tierName || 'Business'}} ({{status?.support?.reactionText || 'до 2 часов'}})</span>
216
+ <span class="kv-val">{{status?.support?.tierName || 'Enterprise'}} ({{status?.support?.reactionText || 'до 15 минут'}})</span>
217
217
  </div>
218
218
  <div class="kv-row">
219
- <span class="kv-label">Включённый 1-й год поддержки ПАК</span>
219
+ <span class="kv-label">Включённый 1-й год поддержки ПАК (Enterprise, далее — по желанию)</span>
220
220
  <span class="kv-val">{{baseSupportText}}</span>
221
221
  </div>
222
222
  <div class="kv-row">
@@ -238,9 +238,12 @@ export default {
238
238
  <div class="card-head">
239
239
  <div class="card-title">
240
240
  <oda-icon icon="carbon:machine-learning-model" :icon-size="20"></oda-icon>
241
- <span>Предустановленный локальный стек ИИ ПАК WORK</span>
241
+ <span>Предустановленный локальный стек ИИ ODANT-WORK</span>
242
242
  </div>
243
- <span class="badge">4 контура ИИ + Типовая конфигурация (1С-класс)</span>
243
+ <span class="badge">Базовые операции локально · мощные ИИ — через API из любого источника</span>
244
+ </div>
245
+ <div style="font-size:12.5px; color:var(--text-muted, #64748b); margin-bottom:10px;">
246
+ Интегрированные модели предназначены для базовых ежедневных операций. Если требуются более мощные модели ИИ, их можно подключить через API из любого внешнего или корпоративного источника в разделе <code>/MODELS</code>.
244
247
  </div>
245
248
  <div class="models-grid">
246
249
  <div class="model-box" ~for="status?.preinstalledModels || []">
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Мета хендлера form/object — стандартное представление редактора объекта/документа на форме.
3
+ * allowTools: false отключает верхний тулбар с кнопками хендлеров (Скрин 6).
4
+ */
5
+ export default {
6
+ icon: 'icons:description',
7
+ label: 'Объект',
8
+ allowTools: false,
9
+ allowSave: true,
10
+ hiddenInViewList: true,
11
+ roles: ['BOSS', 'USER'],
12
+ async allowUse($context, params) {
13
+ const t = $context?.type;
14
+ if (t && t !== '$catalog' && t !== '$operation' && t !== '$class' && !($context?.DATA?.METADATA?.FIELDS?.length))
15
+ return false;
16
+ try {
17
+ const s = (await $context?.db_schema?.({ session: params?.session }))?.schema;
18
+ if (s)
19
+ return (s.family === 'operation' || s.family === 'catalog') && s.type !== '$virtual' && (s.fields.length > 0 || !s.leaf);
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ const md = await $context?.METADATA;
25
+ return Array.isArray(md?.FIELDS) && md.FIELDS.length > 0;
26
+ },
27
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * form/object — стандартная вьюха редактора объекта/документа на стандартной форме work-form (Скрин 6).
3
+ * Верхний тулбар хендлеров отключён через allowTools: false у хендлера.
4
+ * Сервисные действия (Провести / Проведен / Отменить, Печать, Удалить / Восстановить) выводятся
5
+ * в левой части подвала формы (#focusedTools), а кнопки OK / Сохранить / Закрыть и вкладки — в правой.
6
+ */
7
+ import objectsProto from '../../objects/$handler/objects.js';
8
+
9
+ function _day(d) {
10
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
11
+ }
12
+ const _today = () => _day(new Date());
13
+
14
+ const proto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(objectsProto));
15
+
16
+ Object.assign(proto, {
17
+ imports: 'oda//button, oda//icon, ~/lib//editor-form',
18
+ isObjectView: true,
19
+ template: /*html*/`
20
+ <style>
21
+ :host {
22
+ @apply --vertical;
23
+ @apply --flex;
24
+ background: var(--content-background, #fff);
25
+ color: var(--content-color, #222);
26
+ overflow: hidden;
27
+ font-size: 13px;
28
+ }
29
+ .obj-header {
30
+ @apply --horizontal;
31
+ align-items: center;
32
+ justify-content: space-between;
33
+ gap: 12px;
34
+ padding: 8px 16px 4px;
35
+ flex-shrink: 0;
36
+ }
37
+ .obj-title {
38
+ font-size: 16px;
39
+ font-weight: 700;
40
+ color: light-dark(#3b28a8, #93c5fd);
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ white-space: nowrap;
44
+ }
45
+ .context {
46
+ @apply --horizontal;
47
+ align-items: center;
48
+ gap: 6px;
49
+ font-size: 12px;
50
+ flex-shrink: 0;
51
+ }
52
+ .context select {
53
+ padding: 2px 6px;
54
+ font-size: 12px;
55
+ border: 1px solid var(--border-color);
56
+ border-radius: 3px;
57
+ background: var(--content-background);
58
+ color: var(--content-color);
59
+ }
60
+ .obj-scroll {
61
+ @apply --vertical;
62
+ flex: 1;
63
+ min-height: 0;
64
+ overflow-y: auto;
65
+ padding-bottom: 8px;
66
+ }
67
+ oda-editor-form {
68
+ flex-shrink: 0;
69
+ }
70
+ .error {
71
+ padding: 6px 16px;
72
+ color: var(--error-color, #c53030);
73
+ background: light-dark(#fff5f5, rgba(229, 62, 62, 0.15));
74
+ border-top: 1px solid var(--error-color, #feb2b2);
75
+ border-bottom: 1px solid var(--error-color, #feb2b2);
76
+ white-space: break-spaces;
77
+ font-size: 12.5px;
78
+ }
79
+ .entries {
80
+ @apply --vertical;
81
+ gap: 3px;
82
+ margin: 6px 16px 0;
83
+ padding: 8px 10px;
84
+ font-size: 12px;
85
+ background: light-dark(#f8f7f2, #22232a);
86
+ border: 1px solid light-dark(#d8d7ce, #3e4049);
87
+ border-radius: 4px;
88
+ }
89
+ .entries-title {
90
+ font-weight: 700;
91
+ color: light-dark(#444, #cbd5e1);
92
+ margin-bottom: 2px;
93
+ }
94
+ .trajectory {
95
+ @apply --vertical;
96
+ gap: 4px;
97
+ margin: 6px 16px 0;
98
+ padding: 8px 10px;
99
+ font-size: 12px;
100
+ background: light-dark(#f8f7f2, #22232a);
101
+ border: 1px solid light-dark(#d8d7ce, #3e4049);
102
+ border-radius: 4px;
103
+ }
104
+ .trajectory-title {
105
+ font-weight: 700;
106
+ color: light-dark(#444, #cbd5e1);
107
+ }
108
+ .traj-row {
109
+ @apply --horizontal;
110
+ gap: 8px;
111
+ align-items: center;
112
+ }
113
+ .traj-link {
114
+ cursor: pointer;
115
+ text-decoration: underline;
116
+ color: light-dark(#1d4ed8, #93c5fd);
117
+ }
118
+ .traj-num {
119
+ margin-left: auto;
120
+ text-align: right;
121
+ font-variant-numeric: tabular-nums;
122
+ }
123
+ </style>
124
+ <div class="obj-header">
125
+ <div class="obj-title" :title="editorTitle">{{editorTitle}}</div>
126
+ <label class="context" ~if="showContext && !editing?.id">От места:
127
+ <select @change="contextKey = $this.value">
128
+ <option ~for="contexts" :value="contextKeyOf($for.item)"
129
+ :selected="contextKeyOf($for.item) === contextKeyOf(context)">{{contextTitle($for.item)}}</option>
130
+ </select>
131
+ </label>
132
+ </div>
133
+ <div class="error" ~if="error">{{error}}</div>
134
+ <div class="obj-scroll">
135
+ <oda-editor-form ~if="editing" :metadata="activeSchema" :data="editing" :field-errors="fieldErrors"
136
+ @input="onFormInput" @change="onFormInput" @cell-changed="onFormInput"></oda-editor-form>
137
+ <div class="entries" ~if="entries?.length">
138
+ <div class="entries-title">Проводки:</div>
139
+ <div class="entry" ~for="entries">{{entryText($for.item)}}</div>
140
+ </div>
141
+ <div class="trajectory" ~if="objectBalances?.length || trajectory?.length">
142
+ <div class="trajectory-title" ~if="objectBalances?.length">Остатки по счетам</div>
143
+ <div class="traj-row" ~for="objectBalances">
144
+ <span class="traj-link" @tap="openBalanceAccount($for.item)">{{$for.item.label}}</span>
145
+ <span class="traj-num">{{$for.item.balance}}{{$for.item.qty ? ' (' + $for.item.qty + ')' : ''}}</span>
146
+ </div>
147
+ <div class="trajectory-title" ~if="trajectory?.length">Движения ({{trajectory.length}})</div>
148
+ <div class="traj-row" ~for="trajectory">
149
+ <span>{{$for.item.date}}</span>
150
+ <span class="traj-link" @tap="openTrajectoryOp($for.item)">{{$for.item.op}}</span>
151
+ <span>{{$for.item.account}}</span>
152
+ <span class="traj-num">{{$for.item.debit ? '+' + $for.item.debit : ($for.item.credit ? '−' + $for.item.credit : $for.item.qty)}} → {{$for.item.balance}}</span>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ `,
157
+ mountToolbarSlots() {
158
+ // У формы объекта верхнего тулбара нет (allowTools: false)
159
+ },
160
+ getItemButtons() {
161
+ return [];
162
+ },
163
+ getFocusedButtons() {
164
+ return [];
165
+ },
166
+ getFocusedServices() {
167
+ const list = [];
168
+ if (this.isOperation) {
169
+ if (this.canRegister) {
170
+ list.push({
171
+ id: 'post',
172
+ label: 'Провести',
173
+ icon: 'icons:settings',
174
+ isService: true,
175
+ service: true,
176
+ order: 1,
177
+ disabled: this.busy,
178
+ execute: async () => {
179
+ await this.post();
180
+ this._notifyForm();
181
+ },
182
+ });
183
+ }
184
+ else if (this.canCancel) {
185
+ list.push({
186
+ id: 'cancel',
187
+ label: 'Проведен (Отменить)',
188
+ icon: 'icons:settings',
189
+ isService: true,
190
+ service: true,
191
+ order: 1,
192
+ disabled: this.busy,
193
+ execute: async () => {
194
+ await this.cancelOperation();
195
+ this._notifyForm();
196
+ },
197
+ });
198
+ }
199
+ for (let i = 0; i < (this.printForms || []).length; i++) {
200
+ const pf = this.printForms[i];
201
+ list.push({
202
+ id: `print_${pf.name}`,
203
+ label: pf.label || pf.name || 'Печать',
204
+ icon: 'icons:print',
205
+ isService: true,
206
+ service: true,
207
+ order: 2 + i,
208
+ disabled: this.busy,
209
+ execute: () => this.printForm(pf.name),
210
+ });
211
+ }
212
+ }
213
+ if (this.canRemove) {
214
+ list.push({
215
+ id: 'remove',
216
+ label: 'Удалить',
217
+ icon: 'icons:delete',
218
+ isService: true,
219
+ service: true,
220
+ order: 10,
221
+ disabled: this.busy,
222
+ execute: async () => {
223
+ if (await this.removeObject()) {
224
+ this._findFormHost()?.closeActiveTab?.(true);
225
+ }
226
+ },
227
+ });
228
+ }
229
+ if (this.canRestore) {
230
+ list.push({
231
+ id: 'restore',
232
+ label: 'Восстановить',
233
+ icon: 'icons:restore',
234
+ isService: true,
235
+ service: true,
236
+ order: 11,
237
+ disabled: this.busy,
238
+ execute: async () => {
239
+ await this.restoreObject();
240
+ this._notifyForm();
241
+ },
242
+ });
243
+ }
244
+ return list;
245
+ },
246
+ onFormInput() {
247
+ if (this.fieldErrors && Object.keys(this.fieldErrors).length)
248
+ this.fieldErrors = {};
249
+ this.schedulePreview?.();
250
+ const host = this._findFormHost();
251
+ host?.updateActiveTabLabel?.(this.editorTitle);
252
+ },
253
+ async attached() {
254
+ try {
255
+ await Promise.all([this.loadSchema(), this.loadScope()]);
256
+ }
257
+ catch (e) {
258
+ this.error = String(e?.message || e);
259
+ return;
260
+ }
261
+ if (this._pendingNav) {
262
+ const nav = this._pendingNav;
263
+ this._pendingNav = null;
264
+ await this.applyNav(nav);
265
+ }
266
+ else if (!this.editing) {
267
+ await this.initFromNav({});
268
+ }
269
+ },
270
+ async applyNav(nav = {}) {
271
+ if (!this.schema) {
272
+ this._pendingNav = nav;
273
+ return;
274
+ }
275
+ await this.initFromNav(nav);
276
+ },
277
+ async initFromNav(nav = {}) {
278
+ this.clearError();
279
+ if (nav.leafCid) {
280
+ await this.loadLeafSchema(nav.leafCid);
281
+ }
282
+ else {
283
+ this.leafCid = '';
284
+ this.leafSchema = null;
285
+ }
286
+ if (nav.id) {
287
+ await this.select({ id: String(nav.id), cid: nav.leafCid || '' });
288
+ }
289
+ else if (nav.copyFrom) {
290
+ const found = await this.$item.fetch('db_read', { id: String(nav.copyFrom) });
291
+ if (found?.names && typeof found.names === 'object')
292
+ this.names = { ...(this.names || {}), ...found.names };
293
+ await this.loadLeafSchema(found?.cid || nav.leafCid || '');
294
+ const baseBody = { ...(found?.body || {}) };
295
+ delete baseBody.id;
296
+ delete baseBody.version;
297
+ delete baseBody.status;
298
+ this.editing = this.isOperation ? { ...baseBody, time: _today() } : baseBody;
299
+ this.entries = [];
300
+ this.entryNames = { objects: {}, classes: {} };
301
+ this.objectBalances = [];
302
+ this.trajectory = [];
303
+ this.printForms = [];
304
+ if (this.isOperation)
305
+ this.schedulePreview();
306
+ }
307
+ else {
308
+ this.editing = this.isOperation ? { time: _today(), ...(nav.initialData || {}) } : { ...(nav.initialData || {}) };
309
+ this.entries = [];
310
+ this.entryNames = { objects: {}, classes: {} };
311
+ this.objectBalances = [];
312
+ this.trajectory = [];
313
+ this.printForms = [];
314
+ if (this.isOperation)
315
+ this.schedulePreview();
316
+ }
317
+ this.focusedItem = this.editing || { id: nav.id || 'new' };
318
+ this.snapshot();
319
+ this.async(() => {
320
+ if (this.isConnected)
321
+ this.snapshot();
322
+ });
323
+ const host = this._findFormHost();
324
+ host?.updateActiveTabLabel?.(this.editorTitle);
325
+ this._notifyForm();
326
+ },
327
+ async saveAndStay() {
328
+ const ok = await this.save();
329
+ if (ok) {
330
+ if (this.editing?.id) {
331
+ await this.loadEntries();
332
+ await this.loadPrints();
333
+ await this.loadTrajectory();
334
+ }
335
+ const host = this._findFormHost();
336
+ host?.updateActiveTabLabel?.(this.editorTitle);
337
+ this._notifyForm();
338
+ }
339
+ return ok;
340
+ },
341
+ async saveAndClose() {
342
+ if (this.canSave) {
343
+ const ok = await this.save();
344
+ if (!ok)
345
+ return false;
346
+ }
347
+ this._findFormHost()?.closeActiveTab?.(true);
348
+ return true;
349
+ },
350
+ });
351
+
352
+ export default proto;
@@ -1117,10 +1117,21 @@ export default {
1117
1117
  const qty = e.qty ? ` (${e.doc_qty ?? e.qty}${e.doc_unit ? ' ' + e.doc_unit : ''})` : '';
1118
1118
  return `Дт ${side(e.debit, e.debit_analytics)} — Кт ${side(e.credit, e.credit_analytics)}: ${Number(e.amount || 0).toLocaleString('ru-RU', { minimumFractionDigits: 2 })}${qty}`;
1119
1119
  },
1120
- /** Загрузить объект и открыть окно редактора. */
1120
+ /** Загрузить объект и открыть вьюху редактора (внутри work-form — на вкладке form/object, автономно — модально). */
1121
1121
  async openEditor(row) {
1122
1122
  if (!row?.id || this.editorOpen)
1123
1123
  return;
1124
+ const formHost = this._findFormHost();
1125
+ if (formHost && typeof formHost.openInForm === 'function') {
1126
+ const target = await this.targetItem(row.cid || '');
1127
+ await formHost.openInForm(target || this.$item, {
1128
+ view: 'object',
1129
+ id: row.id,
1130
+ leafCid: row.cid || '',
1131
+ label: row.name || row.title || row.id,
1132
+ });
1133
+ return;
1134
+ }
1124
1135
  this.editorOpen = true;
1125
1136
  this.editing = null;
1126
1137
  try {
@@ -1287,10 +1298,10 @@ export default {
1287
1298
  return;
1288
1299
  if (!this.schema)
1289
1300
  await this.loadSchema().catch(() => {});
1301
+ let chosenCid = '';
1290
1302
  if (!this.schema?.leaf) {
1291
1303
  if (!this.leaves?.length)
1292
1304
  return;
1293
- let chosenCid = '';
1294
1305
  if (typeof WORK?.showModal === 'function' && typeof document !== 'undefined') {
1295
1306
  chosenCid = await this.pickTargetLeafClass();
1296
1307
  if (!chosenCid)
@@ -1299,6 +1310,21 @@ export default {
1299
1310
  else {
1300
1311
  chosenCid = this.leafCid || this.leaves[0].cid;
1301
1312
  }
1313
+ }
1314
+ const formHost = this._findFormHost();
1315
+ if (formHost && typeof formHost.openInForm === 'function') {
1316
+ const target = chosenCid ? await this.targetItem(chosenCid) : this.$item;
1317
+ const leafLabel = chosenCid ? (this.classes?.[chosenCid]?.label || this.leaves?.find(l => l.cid === chosenCid)?.label || '') : '';
1318
+ const title = leafLabel || this.schema?.title || 'Объект';
1319
+ await formHost.openInForm(target || this.$item, {
1320
+ view: 'object',
1321
+ create: true,
1322
+ leafCid: chosenCid || '',
1323
+ label: `${title} · новый объект`,
1324
+ });
1325
+ return;
1326
+ }
1327
+ if (chosenCid) {
1302
1328
  this.editorOpen = true;
1303
1329
  await this.loadLeafSchema(chosenCid);
1304
1330
  }
@@ -1326,6 +1352,17 @@ export default {
1326
1352
  async copyRow(row = this.focusedRow) {
1327
1353
  if (!row?.id || this.editorOpen || !this.canCreate)
1328
1354
  return;
1355
+ const formHost = this._findFormHost();
1356
+ if (formHost && typeof formHost.openInForm === 'function') {
1357
+ const target = await this.targetItem(row.cid || '');
1358
+ await formHost.openInForm(target || this.$item, {
1359
+ view: 'object',
1360
+ copyFrom: row.id,
1361
+ leafCid: row.cid || '',
1362
+ label: `${row.name || 'Объект'} (копия)`,
1363
+ });
1364
+ return;
1365
+ }
1329
1366
  this.editorOpen = true;
1330
1367
  this.clearError();
1331
1368
  try {