newportsite 1.1.3
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/newportsite-1.1.3.tgz +0 -0
- package/ng-package.json +7 -0
- package/obfuscate.js +70 -0
- package/package.json +15 -0
- package/src/lib/app.component.ts +47 -0
- package/src/lib/app.routing.ts +38 -0
- package/src/lib/auth/alert.component.html +5 -0
- package/src/lib/auth/alert.component.ts +24 -0
- package/src/lib/auth/auth.component.html +1 -0
- package/src/lib/auth/auth.component.ts +10 -0
- package/src/lib/auth/auth.routes.ts +16 -0
- package/src/lib/auth/index.ts +4 -0
- package/src/lib/auth/login.component.html +87 -0
- package/src/lib/auth/login.component.ts +158 -0
- package/src/lib/auth/models/index.ts +1 -0
- package/src/lib/auth/models/user.ts +25 -0
- package/src/lib/auth/register.component.html +157 -0
- package/src/lib/auth/register.component.ts +219 -0
- package/src/lib/auth/services/alert.service.ts +47 -0
- package/src/lib/auth/services/auth.service.ts +28 -0
- package/src/lib/auth/services/index.ts +3 -0
- package/src/lib/auth/services/user.service.spec.ts +112 -0
- package/src/lib/auth/services/user.service.ts +47 -0
- package/src/lib/common/card.component.html +72 -0
- package/src/lib/common/card.component.ts +102 -0
- package/src/lib/common/commands.component.html +8 -0
- package/src/lib/common/commands.component.ts +42 -0
- package/src/lib/common/context.component.html +9 -0
- package/src/lib/common/context.component.ts +38 -0
- package/src/lib/common/grid.component.html +20 -0
- package/src/lib/common/grid.component.ts +747 -0
- package/src/lib/common/index.ts +9 -0
- package/src/lib/common/loader.component.html +5 -0
- package/src/lib/common/loader.component.ts +27 -0
- package/src/lib/common/lookup.component.html +29 -0
- package/src/lib/common/lookup.component.ts +115 -0
- package/src/lib/common/messagebox.component.html +39 -0
- package/src/lib/common/messagebox.component.ts +74 -0
- package/src/lib/common/theme-toggle.component.ts +139 -0
- package/src/lib/config.ts +62 -0
- package/src/lib/containers/default-layout/default-layout.component.html +191 -0
- package/src/lib/containers/default-layout/default-layout.component.ts +158 -0
- package/src/lib/containers/default-layout/index.ts +1 -0
- package/src/lib/containers/index.ts +1 -0
- package/src/lib/directives/component.draggable.ts +80 -0
- package/src/lib/directives/index.ts +2 -0
- package/src/lib/directives/input.directive.spec.ts +158 -0
- package/src/lib/directives/input.directive.ts +210 -0
- package/src/lib/home/dashboard/dashboard.component.html +38 -0
- package/src/lib/home/dashboard/dashboard.component.ts +50 -0
- package/src/lib/home/dashboard/index.ts +1 -0
- package/src/lib/home/index.component.html +1 -0
- package/src/lib/home/index.component.ts +10 -0
- package/src/lib/home/index.routes.ts +29 -0
- package/src/lib/home/index.ts +1 -0
- package/src/lib/home/info/index.ts +1 -0
- package/src/lib/home/info/info.component.css +476 -0
- package/src/lib/home/info/info.component.html +174 -0
- package/src/lib/home/info/info.component.ts +287 -0
- package/src/lib/home/model/article.component.html +10 -0
- package/src/lib/home/model/article.component.ts +50 -0
- package/src/lib/home/model/barchart.component.html +8 -0
- package/src/lib/home/model/barchart.component.ts +59 -0
- package/src/lib/home/model/index.ts +7 -0
- package/src/lib/home/model/itemdetail.component.html +25 -0
- package/src/lib/home/model/itemdetail.component.ts +93 -0
- package/src/lib/home/model/itemtab.component.html +25 -0
- package/src/lib/home/model/itemtab.component.ts +105 -0
- package/src/lib/home/model/model.component.html +121 -0
- package/src/lib/home/model/model.component.ts +510 -0
- package/src/lib/home/model/modeltoolbar.component.html +111 -0
- package/src/lib/home/model/modeltoolbar.component.ts +157 -0
- package/src/lib/home/model/navigation.component.html +86 -0
- package/src/lib/home/model/navigation.component.ts +247 -0
- package/src/lib/home/model/services/index.ts +1 -0
- package/src/lib/home/model/services/model.service.spec.ts +423 -0
- package/src/lib/home/model/services/model.service.ts +319 -0
- package/src/lib/home/modelsearch/index.ts +1 -0
- package/src/lib/home/modelsearch/modelsearch.component.html +124 -0
- package/src/lib/home/modelsearch/modelsearch.component.ts +453 -0
- package/src/lib/interfaces/data.interface.ts +131 -0
- package/src/lib/interfaces/index.ts +2 -0
- package/src/lib/interfaces/item.interface.ts +438 -0
- package/src/lib/players/lookup/lookup.directive.ts +6 -0
- package/src/lib/players/lookup/lookup.item.component.ts +37 -0
- package/src/lib/players/lookup/lookup.item.ts +9 -0
- package/src/lib/players/lookup/lookup.player.component.ts +59 -0
- package/src/lib/players/lookup/lookup.selector.component.ts +41 -0
- package/src/lib/players/model/model.directive.ts +6 -0
- package/src/lib/players/model/model.item.component.spec.ts +311 -0
- package/src/lib/players/model/model.item.component.ts +3457 -0
- package/src/lib/players/model/model.item.ts +9 -0
- package/src/lib/players/model/model.player.component.ts +109 -0
- package/src/lib/players/model/model.selector.component.ts +59 -0
- package/src/lib/scheduler/scheduler.component.html +13 -0
- package/src/lib/scheduler/scheduler.component.scss +6 -0
- package/src/lib/scheduler/scheduler.component.ts +296 -0
- package/src/lib/scheduler/scheduler.routes.ts +15 -0
- package/src/lib/scheduler/schedulerdialog.component.html +72 -0
- package/src/lib/scheduler/schedulerdialog.component.ts +208 -0
- package/src/lib/scheduler/services/scheduler.service.ts +133 -0
- package/src/lib/services/auth-state.service.ts +129 -0
- package/src/lib/services/auth.interceptor.spec.ts +144 -0
- package/src/lib/services/auth.interceptor.ts +44 -0
- package/src/lib/services/cache.service.spec.ts +143 -0
- package/src/lib/services/cache.service.ts +71 -0
- package/src/lib/services/global-error-handler.spec.ts +39 -0
- package/src/lib/services/global-error-handler.ts +28 -0
- package/src/lib/services/global.service.spec.ts +801 -0
- package/src/lib/services/global.service.ts +724 -0
- package/src/lib/services/message.service.ts +556 -0
- package/src/lib/services/theme.service.ts +96 -0
- package/src/lib/template/authtemplate.component.html +6 -0
- package/src/lib/template/authtemplate.component.ts +13 -0
- package/src/lib/template/basetemplate.component.html +7 -0
- package/src/lib/template/basetemplate.component.ts +13 -0
- package/src/lib/template/index.ts +3 -0
- package/src/lib/template/modeltemplate.component.html +7 -0
- package/src/lib/template/modeltemplate.component.ts +21 -0
- package/src/lib/utils/piva.spec.ts +56 -0
- package/src/lib/utils/piva.ts +29 -0
- package/src/lib/validators/email.validator.spec.ts +57 -0
- package/src/lib/validators/email.validator.ts +17 -0
- package/src/lib/validators/equalPasswords.validator.spec.ts +54 -0
- package/src/lib/validators/equalPasswords.validator.ts +17 -0
- package/src/lib/validators/index.ts +2 -0
- package/src/lib/version.ts +1 -0
- package/src/public-api.ts +64 -0
- package/src/typings.d.ts +2 -0
- package/tsconfig.lib.json +18 -0
- package/tsconfig.lib.prod.json +9 -0
|
@@ -0,0 +1,3457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectorRef,
|
|
3
|
+
Component,
|
|
4
|
+
viewChild,
|
|
5
|
+
ElementRef,
|
|
6
|
+
HostListener,
|
|
7
|
+
OnDestroy,
|
|
8
|
+
AfterViewInit,
|
|
9
|
+
DestroyRef,
|
|
10
|
+
inject,
|
|
11
|
+
signal,
|
|
12
|
+
} from '@angular/core';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ItemInterface,
|
|
16
|
+
Member,
|
|
17
|
+
ItemComponentInterface,
|
|
18
|
+
FieldType,
|
|
19
|
+
TransportInterface,
|
|
20
|
+
ItemLinkInterface,
|
|
21
|
+
FieldState,
|
|
22
|
+
FieldAttr,
|
|
23
|
+
ItemState,
|
|
24
|
+
ItemFieldChangedInterface,
|
|
25
|
+
DialogType,
|
|
26
|
+
ItemCommandInterface,
|
|
27
|
+
PadType,
|
|
28
|
+
ManagmentInterface,
|
|
29
|
+
EntitiesData,
|
|
30
|
+
Entities,
|
|
31
|
+
Entity,
|
|
32
|
+
EntityData,
|
|
33
|
+
ModuleData,
|
|
34
|
+
LabelAction,
|
|
35
|
+
Functions,
|
|
36
|
+
MemberData,
|
|
37
|
+
LookupResultEvent,
|
|
38
|
+
DialogResultEvent,
|
|
39
|
+
FixedField,
|
|
40
|
+
FieldFontSize,
|
|
41
|
+
FieldPaddingTop,
|
|
42
|
+
} from '../../interfaces/index';
|
|
43
|
+
|
|
44
|
+
import { GlobalService, Languages } from '../../services/global.service';
|
|
45
|
+
|
|
46
|
+
import { AppMessageService } from '../../services/message.service';
|
|
47
|
+
|
|
48
|
+
import { NEWPORT_CONFIG } from '../../config';
|
|
49
|
+
|
|
50
|
+
import { ManagmentService } from '../../home/model/services/index';
|
|
51
|
+
|
|
52
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
53
|
+
import { firstValueFrom } from 'rxjs';
|
|
54
|
+
|
|
55
|
+
import CodiceFiscale from 'codice-fiscale-js';
|
|
56
|
+
|
|
57
|
+
import { PartitaIVA } from '../../utils/piva';
|
|
58
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
59
|
+
|
|
60
|
+
import { saveAs } from 'file-saver';
|
|
61
|
+
|
|
62
|
+
import { HttpEventType } from '@angular/common/http';
|
|
63
|
+
|
|
64
|
+
import { CommandsComponent } from '../../common/commands.component';
|
|
65
|
+
import { NgClass } from '@angular/common';
|
|
66
|
+
import { InputDirective } from '../../directives/input.directive';
|
|
67
|
+
import { BarChartComponent } from '../../home/model/barchart.component';
|
|
68
|
+
import { TooltipDirective } from 'ngx-bootstrap/tooltip';
|
|
69
|
+
|
|
70
|
+
@Component({
|
|
71
|
+
selector: 'app-model-item',
|
|
72
|
+
template: `
|
|
73
|
+
<div class="position-absolute" #innercontainer>
|
|
74
|
+
<div>
|
|
75
|
+
<commands
|
|
76
|
+
[(show)]="showcommands"
|
|
77
|
+
[(text)]="textcommands"
|
|
78
|
+
[(title)]="texttitle"
|
|
79
|
+
(commandsResult)="commandsResult($event)"
|
|
80
|
+
[(width)]="commandsWidth"
|
|
81
|
+
[(height)]="commandsHeight"
|
|
82
|
+
[(top)]="commandsTop"
|
|
83
|
+
[(left)]="commandsLeft"></commands>
|
|
84
|
+
|
|
85
|
+
<img
|
|
86
|
+
[class.field-loading]="!loaded"
|
|
87
|
+
[class.field-cache]="model?.cache"
|
|
88
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
89
|
+
id="bkgwhite"
|
|
90
|
+
[hidden]="entity.svg === null || entity.svg.length === 0"
|
|
91
|
+
[src]="svg"
|
|
92
|
+
[style.width]="entity.width"
|
|
93
|
+
[style.height]="entity.height"
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
@for (
|
|
97
|
+
field of entity.members;
|
|
98
|
+
track trackByField($index, field);
|
|
99
|
+
let i = $index
|
|
100
|
+
) {
|
|
101
|
+
@switch (field.type) {
|
|
102
|
+
@case (fieldType.Label) {
|
|
103
|
+
<div
|
|
104
|
+
[hidden]="field.graphic !== 'label'"
|
|
105
|
+
[ngClass]="labelClasses(field)"
|
|
106
|
+
[class.field-loading]="!loaded"
|
|
107
|
+
[class.field-cache]="model?.cache"
|
|
108
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
109
|
+
nvg
|
|
110
|
+
[id]="field.name"
|
|
111
|
+
[style.line-height]="field.height"
|
|
112
|
+
[style.left]="field.x"
|
|
113
|
+
[style.top]="field.y"
|
|
114
|
+
[style.width]="field.width"
|
|
115
|
+
class="position-absolute text-capitalize field-z0"
|
|
116
|
+
[attr.data-type]="field.type"
|
|
117
|
+
[attr.data-state]="field.state">
|
|
118
|
+
{{ defaultLanguage() ? field.caption : field.captiongb }}
|
|
119
|
+
</div>
|
|
120
|
+
}
|
|
121
|
+
@case (fieldType.Image) {
|
|
122
|
+
<input
|
|
123
|
+
type="image"
|
|
124
|
+
[ngClass]="{
|
|
125
|
+
'has-hidden': field?.state === fieldState.Hidden,
|
|
126
|
+
}"
|
|
127
|
+
[class.field-loading]="!loaded"
|
|
128
|
+
[class.field-cache]="model?.cache"
|
|
129
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
130
|
+
[id]="field.name"
|
|
131
|
+
[style.line-height]="field.height"
|
|
132
|
+
[style.left]="field.x"
|
|
133
|
+
[style.top]="field.y"
|
|
134
|
+
[style.width]="field.width"
|
|
135
|
+
class="position-absolute field-z0"
|
|
136
|
+
[attr.data-type]="field.type"
|
|
137
|
+
[attr.data-state]="field.state"
|
|
138
|
+
[src]="field.value.toLowerCase()"
|
|
139
|
+
disabled="true" />
|
|
140
|
+
}
|
|
141
|
+
@case (fieldType.Video) {
|
|
142
|
+
<video
|
|
143
|
+
playsinline
|
|
144
|
+
autoplay
|
|
145
|
+
controls
|
|
146
|
+
#video
|
|
147
|
+
[ngClass]="{
|
|
148
|
+
'has-hidden': field?.state === fieldState.Hidden,
|
|
149
|
+
}"
|
|
150
|
+
[class.field-loading]="!loaded"
|
|
151
|
+
[class.field-cache]="model?.cache"
|
|
152
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
153
|
+
[id]="field.name"
|
|
154
|
+
[style.line-height]="field.height"
|
|
155
|
+
[style.left]="field.x"
|
|
156
|
+
[style.top]="field.y"
|
|
157
|
+
[style.width]="field.width"
|
|
158
|
+
class="position-absolute field-z0"
|
|
159
|
+
[attr.data-type]="field.type"
|
|
160
|
+
[attr.data-state]="field.state">
|
|
161
|
+
<source [src]="field.value.toLowerCase()" type="video/mp4" />
|
|
162
|
+
</video>
|
|
163
|
+
}
|
|
164
|
+
@case (fieldType.Map) {
|
|
165
|
+
<input
|
|
166
|
+
type="image"
|
|
167
|
+
[ngClass]="{
|
|
168
|
+
'has-hidden': field?.state === fieldState.Hidden,
|
|
169
|
+
}"
|
|
170
|
+
[class.field-loading]="!loaded"
|
|
171
|
+
[class.field-cache]="model?.cache"
|
|
172
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
173
|
+
[id]="field.name"
|
|
174
|
+
[style.line-height]="field.height"
|
|
175
|
+
[style.left]="field.x"
|
|
176
|
+
[style.top]="field.y"
|
|
177
|
+
[style.width]="field.width"
|
|
178
|
+
class="position-absolute field-z0"
|
|
179
|
+
[attr.data-type]="field.type"
|
|
180
|
+
[attr.data-state]="field.state"
|
|
181
|
+
[src]="field.value.toLowerCase()"
|
|
182
|
+
disabled="true" />
|
|
183
|
+
}
|
|
184
|
+
@case (fieldType.Button) {
|
|
185
|
+
<input
|
|
186
|
+
type="button"
|
|
187
|
+
(click)="buttonClick(field.name, field.action)"
|
|
188
|
+
[ngClass]="buttonClasses(field)"
|
|
189
|
+
[class.field-loading]="!loaded"
|
|
190
|
+
[class.field-cache]="model?.cache"
|
|
191
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
192
|
+
nvg
|
|
193
|
+
[id]="field.name"
|
|
194
|
+
[style.line-height]="field.height"
|
|
195
|
+
[style.left]="field.x"
|
|
196
|
+
[style.top]="field.y"
|
|
197
|
+
[style.width]="field.width"
|
|
198
|
+
class="position-absolute text-capitalize p-0 m-0 field-z0"
|
|
199
|
+
[attr.data-type]="field.type"
|
|
200
|
+
[attr.data-state]="field.state"
|
|
201
|
+
value="{{
|
|
202
|
+
defaultLanguage() ? field.caption : field.captiongb
|
|
203
|
+
}}" />
|
|
204
|
+
}
|
|
205
|
+
@case (fieldType.Combo) {
|
|
206
|
+
<select
|
|
207
|
+
[ngClass]="fieldClasses(field)"
|
|
208
|
+
[class.field-loading]="!loaded"
|
|
209
|
+
[class.field-cache]="model?.cache"
|
|
210
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
211
|
+
nvg
|
|
212
|
+
[id]="field.name"
|
|
213
|
+
[style.left]="field.x"
|
|
214
|
+
[style.top]="field.y"
|
|
215
|
+
[style.width]="field.width"
|
|
216
|
+
[style.height]="field.height"
|
|
217
|
+
class="position-absolute"
|
|
218
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
219
|
+
[class.field-border-left]="
|
|
220
|
+
field.state !== 0 || entity.svg.length === 0
|
|
221
|
+
"
|
|
222
|
+
[tabindex]="field.tabindex"
|
|
223
|
+
[attr.data-type]="field.type"
|
|
224
|
+
[attr.data-state]="field.state"
|
|
225
|
+
[attr.data-mandatory]="field.mandatory">
|
|
226
|
+
@for (
|
|
227
|
+
c of field.combo.split(',');
|
|
228
|
+
track trackByCombo($index, c)
|
|
229
|
+
) {
|
|
230
|
+
<option
|
|
231
|
+
selected="{{
|
|
232
|
+
c === field.value.toLocaleLowerCase() ? 'selected' : ''
|
|
233
|
+
}}">
|
|
234
|
+
{{ c.toUpperCase() }}
|
|
235
|
+
</option>
|
|
236
|
+
}
|
|
237
|
+
</select>
|
|
238
|
+
}
|
|
239
|
+
@case (fieldType.Chart) {
|
|
240
|
+
<barchart
|
|
241
|
+
[dataChart]="
|
|
242
|
+
gV(field.id.replace('barchart', '')).toLocaleLowerCase()
|
|
243
|
+
"
|
|
244
|
+
[ngClass]="{
|
|
245
|
+
'has-hidden': field?.state === fieldState.Hidden,
|
|
246
|
+
}"
|
|
247
|
+
[class.field-loading]="!loaded"
|
|
248
|
+
[id]="field.name"
|
|
249
|
+
[style.line-height]="field.height"
|
|
250
|
+
[style.left]="field.x"
|
|
251
|
+
[style.top]="field.y"
|
|
252
|
+
[style.width]="field.width"
|
|
253
|
+
class="position-absolute field-z0"
|
|
254
|
+
[attr.data-type]="field.type"
|
|
255
|
+
[attr.data-state]="field.state"></barchart>
|
|
256
|
+
}
|
|
257
|
+
@case (fieldType.Date) {
|
|
258
|
+
<input
|
|
259
|
+
type="text"
|
|
260
|
+
autocomplete="off"
|
|
261
|
+
[ngClass]="fieldClasses(field)"
|
|
262
|
+
[class.field-loading]="!loaded"
|
|
263
|
+
[class.field-cache]="model?.cache"
|
|
264
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
265
|
+
[style.paddingTop]="
|
|
266
|
+
entity.svg.length === 0
|
|
267
|
+
? fieldPaddingTop.none
|
|
268
|
+
: gsv.getInt(field.height) > fieldPaddingTop.tallThreshold
|
|
269
|
+
? fieldPaddingTop.tall
|
|
270
|
+
: fieldPaddingTop.short
|
|
271
|
+
"
|
|
272
|
+
nvg
|
|
273
|
+
[id]="field.name"
|
|
274
|
+
[style.left]="field.x"
|
|
275
|
+
[style.top]="field.y"
|
|
276
|
+
[style.width]="field.width"
|
|
277
|
+
[style.height]="field.height"
|
|
278
|
+
class="position-absolute"
|
|
279
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
280
|
+
[class.field-border-left]="
|
|
281
|
+
field.state !== 0 || entity.svg.length === 0
|
|
282
|
+
"
|
|
283
|
+
[tabindex]="field.tabindex"
|
|
284
|
+
[attr.data-type]="field.type"
|
|
285
|
+
[attr.data-state]="field.state"
|
|
286
|
+
tooltip="{{ field.title }}"
|
|
287
|
+
[containerClass]="tooltipClass(field)"
|
|
288
|
+
[value]="field.value"
|
|
289
|
+
[disabled]="
|
|
290
|
+
!field.enabled &&
|
|
291
|
+
field?.state !== fieldState.Error &&
|
|
292
|
+
field?.state !== fieldState.Warning
|
|
293
|
+
"
|
|
294
|
+
[hidden]="field.state === fieldState.Hidden || !field.visible"
|
|
295
|
+
[attr.data-initialvalue]="field.initialvalue"
|
|
296
|
+
[attr.data-match]="field.match"
|
|
297
|
+
[attr.data-replace]="field.replace"
|
|
298
|
+
[attr.data-tag]="field.tag"
|
|
299
|
+
[attr.data-autoformatter]="field.autoformatter"
|
|
300
|
+
[attr.data-state]="field.state"
|
|
301
|
+
[attr.data-min]="field.min"
|
|
302
|
+
[attr.data-max]="field.max"
|
|
303
|
+
[attr.data-mandatory]="field.mandatory"
|
|
304
|
+
[attr.data-viewlist]="field.viewlist"
|
|
305
|
+
[attr.data-lookup]="field.lookup" />
|
|
306
|
+
}
|
|
307
|
+
@case (fieldType.Number) {
|
|
308
|
+
<input
|
|
309
|
+
type="text"
|
|
310
|
+
autocomplete="off"
|
|
311
|
+
placement="right"
|
|
312
|
+
container="innercontainer"
|
|
313
|
+
adaptivePosition="true"
|
|
314
|
+
[ngClass]="fieldClasses(field)"
|
|
315
|
+
[class.field-loading]="!loaded"
|
|
316
|
+
[class.field-cache]="model?.cache"
|
|
317
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
318
|
+
[style.fontSize]="
|
|
319
|
+
field.value.length * fieldFontSize.compactPx > parseWidth(field.width) &&
|
|
320
|
+
!field.autoformatter
|
|
321
|
+
? fieldFontSize.compact
|
|
322
|
+
: fieldFontSize.default
|
|
323
|
+
"
|
|
324
|
+
[style.paddingTop]="
|
|
325
|
+
entity.svg.length === 0
|
|
326
|
+
? fieldPaddingTop.none
|
|
327
|
+
: gsv.getInt(field.height) > fieldPaddingTop.tallThreshold
|
|
328
|
+
? fieldPaddingTop.tall
|
|
329
|
+
: fieldPaddingTop.short
|
|
330
|
+
"
|
|
331
|
+
nvg
|
|
332
|
+
[id]="field.name"
|
|
333
|
+
[style.left]="field.x"
|
|
334
|
+
[style.top]="field.y"
|
|
335
|
+
[style.width]="field.width"
|
|
336
|
+
[style.height]="field.height"
|
|
337
|
+
class="position-absolute"
|
|
338
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
339
|
+
[class.field-border-left]="
|
|
340
|
+
field.state !== 0 || entity.svg.length === 0
|
|
341
|
+
"
|
|
342
|
+
[tabindex]="field.tabindex"
|
|
343
|
+
[attr.data-type]="field.type"
|
|
344
|
+
[value]="field.value === 0 ? '' : field.value"
|
|
345
|
+
tooltip="{{ field.title }}"
|
|
346
|
+
[containerClass]="tooltipClass(field)"
|
|
347
|
+
[disabled]="
|
|
348
|
+
!field.enabled &&
|
|
349
|
+
field?.state !== fieldState.Error &&
|
|
350
|
+
field?.state !== fieldState.Warning
|
|
351
|
+
"
|
|
352
|
+
[hidden]="field.state === fieldState.Hidden || !field.visible"
|
|
353
|
+
[attr.data-initialvalue]="field.initialvalue"
|
|
354
|
+
[attr.data-length]="field.precision"
|
|
355
|
+
[attr.data-decimal]="field.scale"
|
|
356
|
+
[attr.data-sign]="field.sign"
|
|
357
|
+
[attr.data-thousandseparator]="field.thousandseparator"
|
|
358
|
+
[attr.data-match]="field.match"
|
|
359
|
+
[attr.data-replace]="field.replace"
|
|
360
|
+
[attr.data-tag]="field.tag"
|
|
361
|
+
[attr.data-state]="field.state"
|
|
362
|
+
[attr.data-min]="field.min"
|
|
363
|
+
[attr.data-max]="field.max"
|
|
364
|
+
[attr.data-mandatory]="field.mandatory"
|
|
365
|
+
[attr.data-viewlist]="field.viewlist"
|
|
366
|
+
[attr.data-lookup]="field.lookup" />
|
|
367
|
+
}
|
|
368
|
+
@case (fieldType.TextualCheck) {
|
|
369
|
+
@if (field.graphic === 'text') {
|
|
370
|
+
<div>
|
|
371
|
+
<input
|
|
372
|
+
type="text"
|
|
373
|
+
autocomplete="off"
|
|
374
|
+
placement="right"
|
|
375
|
+
container="innercontainer"
|
|
376
|
+
adaptivePosition="true"
|
|
377
|
+
[ngClass]="fieldClasses(field)"
|
|
378
|
+
[class.field-loading]="!loaded"
|
|
379
|
+
[class.field-cache]="model?.cache"
|
|
380
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
381
|
+
[style.paddingTop]="
|
|
382
|
+
entity.svg.length === 0
|
|
383
|
+
? fieldPaddingTop.none
|
|
384
|
+
: gsv.getInt(field.height) > fieldPaddingTop.tallThreshold
|
|
385
|
+
? fieldPaddingTop.tall
|
|
386
|
+
: fieldPaddingTop.short
|
|
387
|
+
"
|
|
388
|
+
nvg
|
|
389
|
+
[id]="field.name"
|
|
390
|
+
[style.left]="field.x"
|
|
391
|
+
[style.top]="field.y"
|
|
392
|
+
[style.width]="field.width"
|
|
393
|
+
[style.height]="field.height"
|
|
394
|
+
class="position-absolute"
|
|
395
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
396
|
+
[class.field-border-left]="
|
|
397
|
+
field.state !== 0 || entity.svg.length === 0
|
|
398
|
+
"
|
|
399
|
+
[tabindex]="field.tabindex"
|
|
400
|
+
[attr.data-type]="field.type"
|
|
401
|
+
[value]="field.value"
|
|
402
|
+
tooltip="{{ field.title }}"
|
|
403
|
+
[containerClass]="tooltipClass(field)"
|
|
404
|
+
[disabled]="
|
|
405
|
+
!field.enabled &&
|
|
406
|
+
field?.state !== fieldState.Error &&
|
|
407
|
+
field?.state !== fieldState.Warning
|
|
408
|
+
"
|
|
409
|
+
[hidden]="
|
|
410
|
+
field.state === fieldState.Hidden || !field.visible
|
|
411
|
+
"
|
|
412
|
+
[attr.data-allowedchars]="field.allowedchars"
|
|
413
|
+
[attr.data-autoformatter]="field.autoformatter"
|
|
414
|
+
[attr.data-padleftchar]="field.padleftchar"
|
|
415
|
+
[attr.data-padrightchar]="field.padrightchar"
|
|
416
|
+
[attr.data-initialvalue]="field.initialvalue"
|
|
417
|
+
[attr.data-state]="field.state"
|
|
418
|
+
[attr.data-mandatory]="field.mandatory"
|
|
419
|
+
[attr.data-viewlist]="field.viewlist"
|
|
420
|
+
[attr.data-lookup]="field.lookup" />
|
|
421
|
+
</div>
|
|
422
|
+
}
|
|
423
|
+
@if (field.graphic === 'check') {
|
|
424
|
+
<div>
|
|
425
|
+
<input
|
|
426
|
+
type="checkbox"
|
|
427
|
+
autocomplete="off"
|
|
428
|
+
placement="right"
|
|
429
|
+
container="innercontainer"
|
|
430
|
+
adaptivePosition="true"
|
|
431
|
+
[ngClass]="fieldClasses(field)"
|
|
432
|
+
[class.field-loading]="!loaded"
|
|
433
|
+
[class.field-cache]="model?.cache"
|
|
434
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
435
|
+
[class.field-appearance-auto]="field.tag === 'X'"
|
|
436
|
+
[class.field-appearance-none]="field.tag !== 'X'"
|
|
437
|
+
nvg
|
|
438
|
+
[id]="field.name"
|
|
439
|
+
[style.left]="field.x"
|
|
440
|
+
[style.top]="field.y"
|
|
441
|
+
[style.width]="field.width"
|
|
442
|
+
[style.height]="field.height"
|
|
443
|
+
class="position-absolute"
|
|
444
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
445
|
+
[class.field-border-left]="
|
|
446
|
+
field.state !== 0 || entity.svg.length === 0
|
|
447
|
+
"
|
|
448
|
+
[tabindex]="field.tabindex"
|
|
449
|
+
[attr.data-type]="field.type"
|
|
450
|
+
[value]="field.value"
|
|
451
|
+
[checked]="field.value === 'X' ? 'checked' : ''"
|
|
452
|
+
tooltip="{{ field.title }}"
|
|
453
|
+
[containerClass]="tooltipClass(field)"
|
|
454
|
+
[disabled]="
|
|
455
|
+
(!field.enabled &&
|
|
456
|
+
field?.state !== fieldState.Error &&
|
|
457
|
+
field?.state !== fieldState.Warning) ||
|
|
458
|
+
field?.state === fieldState.ReadOnly
|
|
459
|
+
"
|
|
460
|
+
[hidden]="
|
|
461
|
+
field.state === fieldState.Hidden || !field.visible
|
|
462
|
+
"
|
|
463
|
+
[attr.data-allowedchars]="field.allowedchars"
|
|
464
|
+
[attr.data-autoformatter]="field.autoformatter"
|
|
465
|
+
[attr.data-padleftchar]="field.padleftchar"
|
|
466
|
+
[attr.data-padrightchar]="field.padrightchar"
|
|
467
|
+
[attr.data-initialvalue]="field.initialvalue"
|
|
468
|
+
[attr.data-state]="field.state"
|
|
469
|
+
[attr.data-mandatory]="field.mandatory"
|
|
470
|
+
[attr.data-viewlist]="field.viewlist"
|
|
471
|
+
[attr.data-lookup]="field.lookup" />
|
|
472
|
+
</div>
|
|
473
|
+
}
|
|
474
|
+
@if (field.graphic === 'radio') {
|
|
475
|
+
<div>
|
|
476
|
+
<input
|
|
477
|
+
type="radio"
|
|
478
|
+
autocomplete="off"
|
|
479
|
+
placement="right"
|
|
480
|
+
container="innercontainer"
|
|
481
|
+
adaptivePosition="true"
|
|
482
|
+
[ngClass]="fieldClasses(field)"
|
|
483
|
+
[class.field-loading]="!loaded"
|
|
484
|
+
[class.field-cache]="model?.cache"
|
|
485
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
486
|
+
nvg
|
|
487
|
+
[id]="field.name"
|
|
488
|
+
[style.left]="field.x"
|
|
489
|
+
[style.top]="field.y"
|
|
490
|
+
[style.width]="field.width"
|
|
491
|
+
[style.height]="field.height"
|
|
492
|
+
class="position-absolute"
|
|
493
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
494
|
+
[class.field-border-left]="
|
|
495
|
+
field.state !== 0 || entity.svg.length === 0
|
|
496
|
+
"
|
|
497
|
+
[tabindex]="field.tabindex"
|
|
498
|
+
[attr.data-type]="field.type"
|
|
499
|
+
[value]="field.value"
|
|
500
|
+
[checked]="field.value === 'X' ? 'checked' : ''"
|
|
501
|
+
tooltip="{{ field.title }}"
|
|
502
|
+
[containerClass]="tooltipClass(field)"
|
|
503
|
+
[disabled]="
|
|
504
|
+
(!field.enabled &&
|
|
505
|
+
field?.state !== fieldState.Error &&
|
|
506
|
+
field?.state !== fieldState.Warning) ||
|
|
507
|
+
field?.state === fieldState.ReadOnly
|
|
508
|
+
"
|
|
509
|
+
[hidden]="
|
|
510
|
+
field.state === fieldState.Hidden || !field.visible
|
|
511
|
+
"
|
|
512
|
+
[attr.data-allowedchars]="field.allowedchars"
|
|
513
|
+
[attr.data-autoformatter]="field.autoformatter"
|
|
514
|
+
[attr.data-padleftchar]="field.padleftchar"
|
|
515
|
+
[attr.data-padrightchar]="field.padrightchar"
|
|
516
|
+
[attr.data-initialvalue]="field.initialvalue"
|
|
517
|
+
[attr.data-state]="field.state"
|
|
518
|
+
[attr.data-mandatory]="field.mandatory"
|
|
519
|
+
[attr.data-viewlist]="field.viewlist"
|
|
520
|
+
[attr.data-lookup]="field.lookup" />
|
|
521
|
+
</div>
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
@case (fieldType.Text) {
|
|
525
|
+
<input
|
|
526
|
+
type="text"
|
|
527
|
+
autocomplete="off"
|
|
528
|
+
placement="right"
|
|
529
|
+
container="innercontainer"
|
|
530
|
+
adaptivePosition="true"
|
|
531
|
+
[ngClass]="fieldClasses(field)"
|
|
532
|
+
[class.field-loading]="!loaded"
|
|
533
|
+
[class.field-cache]="model?.cache"
|
|
534
|
+
[class.pe-none]="showlookup || showdialog || model?.cache"
|
|
535
|
+
[style.fontSize]="
|
|
536
|
+
field.value.length * fieldFontSize.compactPx > parseWidth(field.width) &&
|
|
537
|
+
!field.autoformatter
|
|
538
|
+
? fieldFontSize.compact
|
|
539
|
+
: fieldFontSize.default
|
|
540
|
+
"
|
|
541
|
+
[style.paddingTop]="
|
|
542
|
+
entity.svg.length === 0
|
|
543
|
+
? fieldPaddingTop.none
|
|
544
|
+
: gsv.getInt(field.height) > fieldPaddingTop.tallThreshold
|
|
545
|
+
? fieldPaddingTop.tall
|
|
546
|
+
: fieldPaddingTop.short
|
|
547
|
+
"
|
|
548
|
+
nvg
|
|
549
|
+
[id]="field.name"
|
|
550
|
+
[style.left]="field.x"
|
|
551
|
+
[style.top]="field.y"
|
|
552
|
+
[style.width]="field.width"
|
|
553
|
+
[style.height]="field.height"
|
|
554
|
+
class="position-absolute"
|
|
555
|
+
[class.field-border-svg]="entity.svg.length > 0"
|
|
556
|
+
[class.field-border-left]="
|
|
557
|
+
field.state !== 0 || entity.svg.length === 0
|
|
558
|
+
"
|
|
559
|
+
[tabindex]="field.tabindex"
|
|
560
|
+
[attr.data-type]="field.type"
|
|
561
|
+
[value]="field.value"
|
|
562
|
+
tooltip="{{ field.title }}"
|
|
563
|
+
[containerClass]="tooltipClass(field)"
|
|
564
|
+
[disabled]="
|
|
565
|
+
!field.enabled &&
|
|
566
|
+
field?.state !== fieldState.Error &&
|
|
567
|
+
field?.state !== fieldState.Warning
|
|
568
|
+
"
|
|
569
|
+
[hidden]="field.state === fieldState.Hidden || !field.visible"
|
|
570
|
+
[attr.data-initialvalue]="field.initialvalue"
|
|
571
|
+
[attr.data-length]="field.length"
|
|
572
|
+
[attr.data-match]="field.match"
|
|
573
|
+
[attr.data-replace]="field.replace"
|
|
574
|
+
[attr.data-tag]="field.tag"
|
|
575
|
+
[attr.data-allowedchars]="field.allowedchars"
|
|
576
|
+
[attr.data-autoformatter]="field.autoformatter"
|
|
577
|
+
[attr.data-padleftchar]="field.padleftchar"
|
|
578
|
+
[attr.data-padrightchar]="field.padrightchar"
|
|
579
|
+
[attr.data-state]="field.state"
|
|
580
|
+
[attr.data-mandatory]="field.mandatory"
|
|
581
|
+
[attr.data-viewlist]="field.viewlist"
|
|
582
|
+
[attr.data-lookup]="field.lookup" />
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
`,
|
|
589
|
+
imports: [
|
|
590
|
+
CommandsComponent,
|
|
591
|
+
InputDirective,
|
|
592
|
+
NgClass,
|
|
593
|
+
BarChartComponent,
|
|
594
|
+
TooltipDirective,
|
|
595
|
+
],
|
|
596
|
+
})
|
|
597
|
+
export class ModelItemComponent
|
|
598
|
+
implements AfterViewInit, ItemComponentInterface, OnDestroy
|
|
599
|
+
{
|
|
600
|
+
gsv = inject(GlobalService);
|
|
601
|
+
msg = inject(AppMessageService);
|
|
602
|
+
msv = inject(ManagmentService);
|
|
603
|
+
private config = inject(NEWPORT_CONFIG);
|
|
604
|
+
private cdr = inject(ChangeDetectorRef);
|
|
605
|
+
|
|
606
|
+
public model!: ItemInterface;
|
|
607
|
+
public managment!: ManagmentInterface;
|
|
608
|
+
public insertMode!: boolean;
|
|
609
|
+
public fieldType = FieldType;
|
|
610
|
+
public fieldState = FieldState;
|
|
611
|
+
public fieldFontSize = FieldFontSize;
|
|
612
|
+
public fieldPaddingTop = FieldPaddingTop;
|
|
613
|
+
public logic!: any;
|
|
614
|
+
public titledialog = '';
|
|
615
|
+
public textdialog = '';
|
|
616
|
+
public showconfirm = false;
|
|
617
|
+
public dialogHeight = 0;
|
|
618
|
+
public dialogWidth = 0;
|
|
619
|
+
public dialogTop = 0;
|
|
620
|
+
public dialogLeft = 0;
|
|
621
|
+
public dialogType: DialogType = 0;
|
|
622
|
+
public showdialog = false;
|
|
623
|
+
private _dialogTarget = '';
|
|
624
|
+
public titlelookup = '';
|
|
625
|
+
public textlookup = '';
|
|
626
|
+
public lookupHeight = 0;
|
|
627
|
+
public lookupWidth = 0;
|
|
628
|
+
public lookupTop = 0;
|
|
629
|
+
public lookupLeft = 0;
|
|
630
|
+
public lookup = '';
|
|
631
|
+
public lookupfilter = '';
|
|
632
|
+
public showlookup = false;
|
|
633
|
+
//
|
|
634
|
+
// Commands
|
|
635
|
+
public showcommands = false;
|
|
636
|
+
public textcommands = '';
|
|
637
|
+
public texttitle = '';
|
|
638
|
+
public commandsHeight = 0;
|
|
639
|
+
public commandsWidth = 0;
|
|
640
|
+
public commandsTop = 0;
|
|
641
|
+
public commandsLeft = 0;
|
|
642
|
+
//
|
|
643
|
+
public currentFieldId: string = '';
|
|
644
|
+
private destroyRef = inject(DestroyRef);
|
|
645
|
+
|
|
646
|
+
public viewList!: boolean;
|
|
647
|
+
public lookupField!: Member;
|
|
648
|
+
|
|
649
|
+
public bplus = true;
|
|
650
|
+
public bminus = true;
|
|
651
|
+
public bfirst = true;
|
|
652
|
+
public bprevious = true;
|
|
653
|
+
public bnext = true;
|
|
654
|
+
public blast = true;
|
|
655
|
+
public bdetail = true;
|
|
656
|
+
public bintodetail = true;
|
|
657
|
+
public bdownload = true;
|
|
658
|
+
public bupload = true;
|
|
659
|
+
public bprint = true;
|
|
660
|
+
public btable = true;
|
|
661
|
+
public save = true;
|
|
662
|
+
public refreshgrid = true;
|
|
663
|
+
public svg: string = '';
|
|
664
|
+
public data!: EntitiesData;
|
|
665
|
+
public loaded = false;
|
|
666
|
+
public entity!: Entity;
|
|
667
|
+
private _memberMap: Map<string, Member> | null = null;
|
|
668
|
+
private _readonlySet = new Set<string>();
|
|
669
|
+
private _cachedKeyArray: number[] | null = null;
|
|
670
|
+
private _cachedKeyString = '';
|
|
671
|
+
public lastKey!: number;
|
|
672
|
+
public fieldsId = '';
|
|
673
|
+
public closing = false;
|
|
674
|
+
public resetData = true;
|
|
675
|
+
public root!: Entities;
|
|
676
|
+
|
|
677
|
+
readonly container = viewChild.required<ElementRef>('innercontainer');
|
|
678
|
+
private readonly video = viewChild<ElementRef<HTMLVideoElement>>('video');
|
|
679
|
+
constructor() {
|
|
680
|
+
this.managment = this.gsv.getManagmentInterface();
|
|
681
|
+
|
|
682
|
+
this.gsv
|
|
683
|
+
.getFieldChanged()
|
|
684
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
685
|
+
.subscribe(async currentFieldChanged => {
|
|
686
|
+
if (currentFieldChanged.function === 'setForced') {
|
|
687
|
+
if (this.gS(currentFieldChanged.id) === FieldState.Forced) {
|
|
688
|
+
this.sS(currentFieldChanged.id, FieldState.None);
|
|
689
|
+
} else {
|
|
690
|
+
this.sS(currentFieldChanged.id, FieldState.Forced);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
await this.fieldChanged({
|
|
694
|
+
id: currentFieldChanged.id,
|
|
695
|
+
value: currentFieldChanged.value,
|
|
696
|
+
function: currentFieldChanged.function,
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
this.gsv
|
|
701
|
+
.getCurrentField()
|
|
702
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
703
|
+
.subscribe(currentFieldChanged => {
|
|
704
|
+
this.logic.function(
|
|
705
|
+
Functions.lostfocus,
|
|
706
|
+
this.getIndex(),
|
|
707
|
+
this.getKeyArray(),
|
|
708
|
+
this.currentFieldId
|
|
709
|
+
);
|
|
710
|
+
this.currentField(currentFieldChanged);
|
|
711
|
+
this.setFocus(currentFieldChanged.id);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/** Lifecycle: fetches entity data on first render. */
|
|
716
|
+
async ngAfterViewInit() {
|
|
717
|
+
await this.getData(this.model.id);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** Lifecycle: saves cached data and calls close() on component teardown. */
|
|
721
|
+
public async ngOnDestroy() {
|
|
722
|
+
if (this.data && this.data.entities) {
|
|
723
|
+
this.svg = null!;
|
|
724
|
+
this.gsv.setData(this.fieldsId, this.data);
|
|
725
|
+
if (
|
|
726
|
+
this.gsv.getProject().type !== 'D' &&
|
|
727
|
+
!this.closing &&
|
|
728
|
+
!this.model.preview
|
|
729
|
+
) {
|
|
730
|
+
await this.close();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Persists any unsaved changes via putDataPromise (save) before the component is
|
|
737
|
+
* removed from the view, and optionally resets the in-memory data cache.
|
|
738
|
+
*/
|
|
739
|
+
public async close() {
|
|
740
|
+
const idSchemaJson = this.managment.appId + '.' + this.managment.year;
|
|
741
|
+
this.closing = true;
|
|
742
|
+
// const root: Entities = this.gsv.getSchemaJson(idSchemaJson);
|
|
743
|
+
if (this.data && this.data.entities) {
|
|
744
|
+
this.data.entities.forEach(entity => {
|
|
745
|
+
this.fieldsId =
|
|
746
|
+
this.managment.appId +
|
|
747
|
+
'.' +
|
|
748
|
+
entity.name.toUpperCase() +
|
|
749
|
+
'.' +
|
|
750
|
+
this.managment.year;
|
|
751
|
+
const schemaEntity = this.root.entities.find(
|
|
752
|
+
e => e.name == entity.name.toLocaleLowerCase()
|
|
753
|
+
);
|
|
754
|
+
if (schemaEntity && !schemaEntity.cache && this.resetData) {
|
|
755
|
+
this.gsv.removeData(this.fieldsId);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
this.gsv.setExtendedDescription('');
|
|
759
|
+
const json = this.getJsonData();
|
|
760
|
+
if (this.model.cache || !json) {
|
|
761
|
+
return;
|
|
762
|
+
} else {
|
|
763
|
+
return await this.throwSave(json).then(e => {
|
|
764
|
+
if (this.resetData) {
|
|
765
|
+
this.data.entities = [];
|
|
766
|
+
this.data = null!;
|
|
767
|
+
this.entity = null!;
|
|
768
|
+
this._memberMap = null;
|
|
769
|
+
this.root = null!;
|
|
770
|
+
this.gsv.setData(this.fieldsId, null!);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/** Sends a PUT request to persist the given JSON payload; shows a dialog on failure. */
|
|
780
|
+
public async throwSave(json: string) {
|
|
781
|
+
if (!json || json === '') return;
|
|
782
|
+
this.managment.data = json;
|
|
783
|
+
this.managment.functionId = 'save';
|
|
784
|
+
await this.msv.putDataPromise(this.managment).then(
|
|
785
|
+
(data: TransportInterface) => {
|
|
786
|
+
this.save = true;
|
|
787
|
+
this.sendCommands();
|
|
788
|
+
},
|
|
789
|
+
error => {
|
|
790
|
+
this.showMessage(
|
|
791
|
+
this.msg.get('app.saveerror') || this.msg.get('app.unabletosave')
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
@HostListener('document:keydown.escape', ['$event']) onKeydownHandlerEscape(
|
|
798
|
+
event: KeyboardEvent
|
|
799
|
+
) {
|
|
800
|
+
if (this.gsv.getModalActive()) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
event.stopImmediatePropagation();
|
|
804
|
+
this.sendCommand({ id: 'exit', state: true });
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
@HostListener('window:keydown', ['$event']) onKeydownHandler(
|
|
808
|
+
event: KeyboardEvent
|
|
809
|
+
) {
|
|
810
|
+
if (event.shiftKey && event.key === 'N') {
|
|
811
|
+
event.preventDefault();
|
|
812
|
+
event.stopImmediatePropagation();
|
|
813
|
+
this.sendCommand({ id: 'plus', state: true });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/** Returns true if the active locale is Italian. Used in template bindings. */
|
|
818
|
+
public defaultLanguage(): boolean {
|
|
819
|
+
return this.gsv?.getLng() === Languages.IT;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** Returns the EntityData for the given entity name and optional entity index. */
|
|
823
|
+
public getDataEntity(name: string, entity: number = 0): EntityData {
|
|
824
|
+
return this.data?.entities.find(e => e.name === name)!;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Navigates to the MemberData for a field within the currently active module,
|
|
829
|
+
* using the same modulefiltered logic as commandData/fieldBusiness.
|
|
830
|
+
* Returns undefined if the entity or module cannot be resolved.
|
|
831
|
+
*/
|
|
832
|
+
private getModuleMemberData(
|
|
833
|
+
fieldId: string,
|
|
834
|
+
entityName?: string
|
|
835
|
+
): MemberData | undefined {
|
|
836
|
+
const entName = entityName ?? this.managment.entId.toLocaleLowerCase();
|
|
837
|
+
const entity = this.getDataEntity(entName);
|
|
838
|
+
if (!entity?.modules?.length) return undefined;
|
|
839
|
+
const prgLength = entity.modules[0].prg.length - 1;
|
|
840
|
+
const modulefiltered =
|
|
841
|
+
prgLength > 0
|
|
842
|
+
? entity.modules.filter(
|
|
843
|
+
e => e.prg[0] === this.getKeyArray()[0] && e.state === 1
|
|
844
|
+
)
|
|
845
|
+
: entity.modules;
|
|
846
|
+
const index = entity.modules.indexOf(modulefiltered[this.lastKey]);
|
|
847
|
+
if (index < 0) return undefined;
|
|
848
|
+
return entity.modules[index].members.find(m => m.name === fieldId);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/** Returns the first ModuleData matching key and keyElement within the given EntityData. */
|
|
852
|
+
public getDataModule(
|
|
853
|
+
data: EntityData,
|
|
854
|
+
key: number,
|
|
855
|
+
keyElement: number,
|
|
856
|
+
module: number = 0
|
|
857
|
+
): ModuleData {
|
|
858
|
+
return data.modules.filter(e => e.prg[keyElement] === key && e.state === 1)[
|
|
859
|
+
module
|
|
860
|
+
];
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** Returns a nested ModuleData by entity name, key, and keyElement indices (utility for logic classes). */
|
|
864
|
+
public getDataEntityModule(
|
|
865
|
+
name: string,
|
|
866
|
+
key: number,
|
|
867
|
+
keyElement: number,
|
|
868
|
+
entity: number = 0,
|
|
869
|
+
module: number = 0
|
|
870
|
+
): ModuleData {
|
|
871
|
+
let data = this.data.entities.filter(e => e.name === name)[entity];
|
|
872
|
+
return data.modules.filter(e => e.prg[keyElement] === key && e.state === 1)[
|
|
873
|
+
module
|
|
874
|
+
];
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/** Propagates the global UI scale factor to GlobalService. */
|
|
878
|
+
public setScale(scale: number) {
|
|
879
|
+
this.gsv.setScale(scale);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/** Handles a row selected in the lookup popup: maps output fields and resets the panel. */
|
|
883
|
+
public lookupResult(rsp: LookupResultEvent) {
|
|
884
|
+
// Parse lookup definition before clearing this.lookup below.
|
|
885
|
+
let output: string[] = [];
|
|
886
|
+
let input: string[] = [];
|
|
887
|
+
if (rsp.value !== null) {
|
|
888
|
+
const lookupdata = this.lookup.split('#');
|
|
889
|
+
output = lookupdata[4].split(',');
|
|
890
|
+
input = lookupdata[3].split(',');
|
|
891
|
+
for (let i = 0; i < input.length; i++) {
|
|
892
|
+
if (rsp.value[0][input[i]] === undefined) {
|
|
893
|
+
rsp.value[0][input[i]] = '';
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Dismiss the modal FIRST so that pe-none is lifted immediately.
|
|
899
|
+
// Also required: showlookup=false lets the lookup block inside fieldChanged
|
|
900
|
+
// run when the user next blurs a field (it checks !this.showlookup).
|
|
901
|
+
this.titlelookup = '';
|
|
902
|
+
this.textlookup = '';
|
|
903
|
+
this.lookupHeight = 0;
|
|
904
|
+
this.lookupWidth = 0;
|
|
905
|
+
this.lookupTop = 0;
|
|
906
|
+
this.lookupLeft = 0;
|
|
907
|
+
this.lookup = '';
|
|
908
|
+
this.lookupfilter = '';
|
|
909
|
+
this.textcommands = '...';
|
|
910
|
+
this.showcommands = true;
|
|
911
|
+
this.bdetail = false;
|
|
912
|
+
this.showlookup = false;
|
|
913
|
+
this.sendCommands();
|
|
914
|
+
|
|
915
|
+
if (
|
|
916
|
+
rsp.value !== null &&
|
|
917
|
+
this.lookupField &&
|
|
918
|
+
this.lookupField.state !== FieldState.ReadOnly
|
|
919
|
+
) {
|
|
920
|
+
for (let i = 0; i < input.length; i++) {
|
|
921
|
+
// Skip only Forced (server-immutable) fields.
|
|
922
|
+
// ReadOnly = lookupoutput; a lookup selection must be able to fill these.
|
|
923
|
+
if (this.gS(output[i]) !== FieldState.Forced) {
|
|
924
|
+
this.sV(output[i], rsp.value[0][input[i]]);
|
|
925
|
+
// 'lookupCheck' is in the exclusion list of the lookup block inside
|
|
926
|
+
// fieldChanged, so this call is entirely synchronous (no HTTP fetch,
|
|
927
|
+
// no recursive lookupCheck). Do NOT await — there is nothing to await,
|
|
928
|
+
// and awaiting would schedule microtasks that interleave with the
|
|
929
|
+
// still-pending blur-triggered fieldChanged subscribers.
|
|
930
|
+
this.fieldChanged({
|
|
931
|
+
id: output[i],
|
|
932
|
+
value: rsp.value[0][input[i]],
|
|
933
|
+
function: 'lookupCheck',
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
// Run business logic for the primary output field so that computed
|
|
938
|
+
// fields (e.g. MMAG_RIG: imp/tot/rim calculations, Forced states on
|
|
939
|
+
// prz/codiva) are applied immediately after the lookup fills the fields.
|
|
940
|
+
// fieldBusiness calls updateState() internally, so we skip the separate call.
|
|
941
|
+
if (output.length > 0) {
|
|
942
|
+
this.fieldBusiness(output[0]);
|
|
943
|
+
} else {
|
|
944
|
+
// No output fields — still update navigation badge states.
|
|
945
|
+
this.updateState();
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// For masterDetail entities the grid must be refreshed so that the newly
|
|
950
|
+
// populated lookup values (and any computed members set by fieldBusiness)
|
|
951
|
+
// are visible in the grid immediately after lookup selection.
|
|
952
|
+
if (this.entity?.masterdetail !== 0) {
|
|
953
|
+
this.refreshData();
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (this.lookupField) {
|
|
957
|
+
this.setFocus(this.lookupField.id);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/** Opens the lookup popup for the currently focused field, computing position and filter. */
|
|
962
|
+
public commandsResult(rsp: unknown) {
|
|
963
|
+
const field: Member = this.entity.members.find(
|
|
964
|
+
item => item.id === this.currentFieldId
|
|
965
|
+
)!;
|
|
966
|
+
if (!field) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
this.titlelookup = field.lookup !== '' ? field.lookup.split('#')[6] : '';
|
|
970
|
+
this.textlookup = '';
|
|
971
|
+
this.lookupHeight = 500;
|
|
972
|
+
this.lookupWidth = 700;
|
|
973
|
+
|
|
974
|
+
const scrollTop =
|
|
975
|
+
this.container().nativeElement.parentElement.parentElement.parentElement
|
|
976
|
+
.parentElement.scrollTop;
|
|
977
|
+
const scrollLeft =
|
|
978
|
+
this.container().nativeElement.parentElement.parentElement.parentElement
|
|
979
|
+
.parentElement.scrollLeft;
|
|
980
|
+
|
|
981
|
+
if (
|
|
982
|
+
parseInt(field.y, 10) + parseInt(field.height, 10) + this.lookupHeight >
|
|
983
|
+
this.container().nativeElement.parentElement.parentElement.parentElement
|
|
984
|
+
.parentElement.clientHeight +
|
|
985
|
+
scrollTop &&
|
|
986
|
+
this.model.masterDetail == 0
|
|
987
|
+
) {
|
|
988
|
+
this.lookupTop =
|
|
989
|
+
parseInt(field.y, 10) + parseInt(field.height, 10) - this.lookupHeight;
|
|
990
|
+
} else {
|
|
991
|
+
this.lookupTop = parseInt(field.y, 10) + parseInt(field.height, 10);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
this.lookupField = field;
|
|
995
|
+
this.lookupfilter = field.lookup !== '' ? field.lookup.split('#')[5] : '';
|
|
996
|
+
if (this.lookupfilter !== '') {
|
|
997
|
+
const lkf: string[] = this.lookupfilter.split(',');
|
|
998
|
+
// tslint:disable-next-line:prefer-for-of
|
|
999
|
+
for (let i = 0; i < lkf.length; i++) {
|
|
1000
|
+
const lkfe: string[] = lkf[i].split(';');
|
|
1001
|
+
this.lookupfilter = this.lookupfilter.replace(
|
|
1002
|
+
lkfe[1],
|
|
1003
|
+
String(this.entity.members.find(item => item.id === lkfe[1].substring(1))?.value ?? '')
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
this.lookupLeft = parseInt(field.x, 10);
|
|
1009
|
+
|
|
1010
|
+
this.lookupTop -= scrollTop - 2;
|
|
1011
|
+
this.lookupLeft -= scrollLeft;
|
|
1012
|
+
|
|
1013
|
+
let isCentered = false;
|
|
1014
|
+
if (this.lookupTop < 0) {
|
|
1015
|
+
const _scale = this.gsv.getScale();
|
|
1016
|
+
this.lookupTop = Math.max(
|
|
1017
|
+
0,
|
|
1018
|
+
(window.innerHeight / _scale - this.lookupHeight) / 2
|
|
1019
|
+
);
|
|
1020
|
+
this.lookupLeft = Math.max(
|
|
1021
|
+
0,
|
|
1022
|
+
(window.innerWidth / _scale - this.lookupWidth) / 2
|
|
1023
|
+
);
|
|
1024
|
+
isCentered = true;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (!isCentered) {
|
|
1028
|
+
if (
|
|
1029
|
+
this.lookupLeft + this.lookupWidth >
|
|
1030
|
+
this.container().nativeElement.scrollWidth
|
|
1031
|
+
) {
|
|
1032
|
+
this.lookupLeft = this.lookupLeft - this.lookupWidth;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (this.lookupLeft < 0) {
|
|
1036
|
+
this.lookupLeft = 0;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
this.lookup = field.lookup;
|
|
1041
|
+
this.textcommands = '';
|
|
1042
|
+
this.texttitle = '';
|
|
1043
|
+
this.showcommands = false;
|
|
1044
|
+
this.bdetail = true;
|
|
1045
|
+
this.showlookup = true;
|
|
1046
|
+
this.sendLookup();
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/** Resets all member fields to blank/default values (called when entering insert mode). */
|
|
1050
|
+
public setupFields(data: Member[]) {
|
|
1051
|
+
for (const input of data) {
|
|
1052
|
+
if (input.id !== undefined) {
|
|
1053
|
+
input.value = '';
|
|
1054
|
+
input.tag = '';
|
|
1055
|
+
input.initialvalue = '';
|
|
1056
|
+
// Use sS() so: DOM attribute, schema member AND backing data-module state
|
|
1057
|
+
// are all reset synchronously. executeCommand runs immediately after and
|
|
1058
|
+
// re-applies correct states via awaited fieldChanged calls, avoiding the
|
|
1059
|
+
// race condition that was caused by the old non-awaited fieldChanged here.
|
|
1060
|
+
this.sS(input.id, FieldState.None);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Sets the visual state of a field (setState). Also updates the underlying MemberData
|
|
1067
|
+
* in the data cache so that business logic and JSON serialisation remain in sync.
|
|
1068
|
+
*/
|
|
1069
|
+
public sS(id: string, state: FieldState, message = '', dialog = false) {
|
|
1070
|
+
const field: Member = this.gF(id);
|
|
1071
|
+
if (field && !this.model.preview) {
|
|
1072
|
+
if (this.entity.members.length > 0) {
|
|
1073
|
+
if (
|
|
1074
|
+
this.gE(id) &&
|
|
1075
|
+
this.gE(id)!.getAttribute(FieldAttr.get('state')!) !== null
|
|
1076
|
+
) {
|
|
1077
|
+
field.state = state;
|
|
1078
|
+
if (state === FieldState.ReadOnly) {
|
|
1079
|
+
this._readonlySet.add(id);
|
|
1080
|
+
} else if (state === FieldState.None) {
|
|
1081
|
+
this._readonlySet.delete(id);
|
|
1082
|
+
}
|
|
1083
|
+
this.gE(id)!.setAttribute(FieldAttr.get('state')!, state.toString());
|
|
1084
|
+
field.title = message;
|
|
1085
|
+
if (dialog && message !== '') {
|
|
1086
|
+
this.showMessage(message);
|
|
1087
|
+
} else {
|
|
1088
|
+
this.gsv.setDialog({ target: '', state: false });
|
|
1089
|
+
}
|
|
1090
|
+
if (this.data) {
|
|
1091
|
+
const member = this.getModuleMemberData(
|
|
1092
|
+
field.id,
|
|
1093
|
+
this.model.name.toLocaleLowerCase()
|
|
1094
|
+
);
|
|
1095
|
+
if (member) {
|
|
1096
|
+
member.state = state;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/** Returns the FieldType of the given field (getType). Defaults to Text if not found. */
|
|
1105
|
+
public gT(id: string): FieldType {
|
|
1106
|
+
return this.gF(id)?.type ?? FieldType.Text;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/** Returns the current FieldState of the given field (getState). Defaults to None. */
|
|
1110
|
+
public gS(id: string): FieldState {
|
|
1111
|
+
return this.gF(id)?.state ?? FieldState.None;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Sets the display value of a field (setValue). Handles type-specific formatting
|
|
1116
|
+
* (dates, numbers, text) and keeps the backing MemberData and logic function in sync.
|
|
1117
|
+
*/
|
|
1118
|
+
public sV(id: string, value: any) {
|
|
1119
|
+
const field: Member = this.gF(id);
|
|
1120
|
+
if (field !== undefined) {
|
|
1121
|
+
switch (this.gT(id)) {
|
|
1122
|
+
case FieldType.Date:
|
|
1123
|
+
field.value = value;
|
|
1124
|
+
if (value === '' || value === null) {
|
|
1125
|
+
field.value = '';
|
|
1126
|
+
field.tag = '';
|
|
1127
|
+
} else {
|
|
1128
|
+
value = value.replaceAll(' ', '');
|
|
1129
|
+
// If the value is in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss),
|
|
1130
|
+
// convert it to locale display format before further processing.
|
|
1131
|
+
const isoMatch =
|
|
1132
|
+
typeof value === 'string' &&
|
|
1133
|
+
value.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
1134
|
+
if (isoMatch) {
|
|
1135
|
+
const fmt =
|
|
1136
|
+
this.gsv.getLng() === Languages.IT
|
|
1137
|
+
? 'DD/MM/YYYY'
|
|
1138
|
+
: 'MM/DD/YYYY';
|
|
1139
|
+
value = this.formatDateFromISO(value.substr(0, 10), fmt);
|
|
1140
|
+
} else if (value.indexOf('/') === -1 && value.length > 0) {
|
|
1141
|
+
// User typed DDMMYYYY without separators — insert slashes.
|
|
1142
|
+
value =
|
|
1143
|
+
value.toString().substr(0, 2) +
|
|
1144
|
+
'/' +
|
|
1145
|
+
value.toString().substr(2, 2) +
|
|
1146
|
+
'/' +
|
|
1147
|
+
value.toString().substr(4);
|
|
1148
|
+
}
|
|
1149
|
+
const fmt =
|
|
1150
|
+
this.gsv.getLng() === Languages.IT ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
|
|
1151
|
+
if (this.isValidDateString(value, fmt)) {
|
|
1152
|
+
field.value = value;
|
|
1153
|
+
} else {
|
|
1154
|
+
field.value = '';
|
|
1155
|
+
}
|
|
1156
|
+
field.tag = field.value;
|
|
1157
|
+
}
|
|
1158
|
+
break;
|
|
1159
|
+
case FieldType.Number:
|
|
1160
|
+
if (value === '' || value === '0') {
|
|
1161
|
+
field.value = '';
|
|
1162
|
+
field.tag = '';
|
|
1163
|
+
} else {
|
|
1164
|
+
const isIT = this.gsv.getLng() === Languages.IT;
|
|
1165
|
+
if (typeof value === 'string') {
|
|
1166
|
+
value = isIT
|
|
1167
|
+
? value.replace(/\./g, '').replace(',', '.')
|
|
1168
|
+
: value.replace(/,/g, '');
|
|
1169
|
+
}
|
|
1170
|
+
const numVal =
|
|
1171
|
+
typeof value === 'number'
|
|
1172
|
+
? value
|
|
1173
|
+
: parseFloat(String(value).replace(',', '.'));
|
|
1174
|
+
const fixed = numVal.toFixed(field.scale);
|
|
1175
|
+
field.tag = isIT ? fixed.replace('.', ',') : fixed;
|
|
1176
|
+
if (field.thousandseparator) {
|
|
1177
|
+
field.value = isIT
|
|
1178
|
+
? numVal.toLocaleString('it-it', {
|
|
1179
|
+
minimumFractionDigits: field.scale,
|
|
1180
|
+
})
|
|
1181
|
+
: numVal.toLocaleString('en-us', {
|
|
1182
|
+
minimumFractionDigits: field.scale,
|
|
1183
|
+
});
|
|
1184
|
+
} else {
|
|
1185
|
+
field.value = field.tag;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
break;
|
|
1189
|
+
default:
|
|
1190
|
+
if (value !== null) {
|
|
1191
|
+
field.value = value.toString().toUpperCase();
|
|
1192
|
+
field.tag = value.toString().toUpperCase();
|
|
1193
|
+
} else {
|
|
1194
|
+
field.value = '';
|
|
1195
|
+
field.tag = '';
|
|
1196
|
+
}
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
if (this.data) {
|
|
1200
|
+
const member = this.getModuleMemberData(field.id);
|
|
1201
|
+
if (member) {
|
|
1202
|
+
if (
|
|
1203
|
+
member.initialvalue !== null &&
|
|
1204
|
+
member.initialvalue === member.value &&
|
|
1205
|
+
member.type === FieldType.Date &&
|
|
1206
|
+
typeof member.initialvalue === 'string' &&
|
|
1207
|
+
/^\d{4}-\d{2}-\d{2}/.test(member.initialvalue)
|
|
1208
|
+
) {
|
|
1209
|
+
// Server returned an ISO date (with or without time); normalise
|
|
1210
|
+
// initialvalue to the display format so dirty-detection is correct.
|
|
1211
|
+
member.initialvalue = this.gV(id);
|
|
1212
|
+
}
|
|
1213
|
+
member.state = this.gS(id);
|
|
1214
|
+
member.value = this.gV(id);
|
|
1215
|
+
if (member.type === FieldType.Number && member.value === '') {
|
|
1216
|
+
member.value = 0;
|
|
1217
|
+
}
|
|
1218
|
+
this.logic.function(
|
|
1219
|
+
id + Functions.changed,
|
|
1220
|
+
this.lastKey,
|
|
1221
|
+
this.getKeyArray(),
|
|
1222
|
+
this.currentFieldId
|
|
1223
|
+
);
|
|
1224
|
+
if (id === FixedField.cfpiva || id === FixedField.modnum) {
|
|
1225
|
+
this.paddingFormattingRegEx(id);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/** Converts an ISO date string to DD/MM/YYYY or MM/DD/YYYY depending on locale. */
|
|
1233
|
+
private formatDateFromISO(
|
|
1234
|
+
isoDate: string,
|
|
1235
|
+
format: 'DD/MM/YYYY' | 'MM/DD/YYYY'
|
|
1236
|
+
): string {
|
|
1237
|
+
const d = new Date(isoDate);
|
|
1238
|
+
if (isNaN(d.getTime())) return '';
|
|
1239
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
1240
|
+
const day = pad(d.getUTCDate());
|
|
1241
|
+
const month = pad(d.getUTCMonth() + 1);
|
|
1242
|
+
const year = d.getUTCFullYear();
|
|
1243
|
+
return format === 'DD/MM/YYYY'
|
|
1244
|
+
? `${day}/${month}/${year}`
|
|
1245
|
+
: `${month}/${day}/${year}`;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Converts a locale display date string (DD/MM/YYYY or MM/DD/YYYY) to ISO YYYY-MM-DD.
|
|
1250
|
+
* Returns an empty string if the value is empty or not a valid display date.
|
|
1251
|
+
*/
|
|
1252
|
+
private displayDateToISO(value: string): string {
|
|
1253
|
+
if (!value || value.length !== 10) return value;
|
|
1254
|
+
const fmt =
|
|
1255
|
+
this.gsv.getLng() === Languages.IT ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
|
|
1256
|
+
if (!this.isValidDateString(value, fmt)) return value;
|
|
1257
|
+
const parts = value.split('/');
|
|
1258
|
+
const [p1, p2, p3] = parts.map(Number);
|
|
1259
|
+
const [day, month, year] =
|
|
1260
|
+
fmt === 'DD/MM/YYYY' ? [p1, p2, p3] : [p2, p1, p3];
|
|
1261
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
1262
|
+
return `${year}-${pad(month)}-${pad(day)}`;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/** Returns true if the given string is a valid date in the specified format. */
|
|
1266
|
+
private isValidDateString(
|
|
1267
|
+
value: string,
|
|
1268
|
+
format: 'DD/MM/YYYY' | 'MM/DD/YYYY'
|
|
1269
|
+
): boolean {
|
|
1270
|
+
if (!value || value.length !== 10) return false;
|
|
1271
|
+
const parts = value.split('/');
|
|
1272
|
+
if (parts.length !== 3) return false;
|
|
1273
|
+
const [p1, p2, p3] = parts.map(Number);
|
|
1274
|
+
const [day, month, year] =
|
|
1275
|
+
format === 'DD/MM/YYYY' ? [p1, p2, p3] : [p2, p1, p3];
|
|
1276
|
+
if (isNaN(day) || isNaN(month) || isNaN(year)) return false;
|
|
1277
|
+
const d = new Date(year, month - 1, day);
|
|
1278
|
+
return (
|
|
1279
|
+
d.getFullYear() === year &&
|
|
1280
|
+
d.getMonth() === month - 1 &&
|
|
1281
|
+
d.getDate() === day
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/** Returns the formatted string value (tag) of the given field (getValue). */
|
|
1286
|
+
public gV(id: string): any {
|
|
1287
|
+
const field: Member = this.gF(id);
|
|
1288
|
+
let retValue: any = '';
|
|
1289
|
+
if (field) {
|
|
1290
|
+
if (field.tag) {
|
|
1291
|
+
switch (this.gT(id)) {
|
|
1292
|
+
case FieldType.Number:
|
|
1293
|
+
retValue =
|
|
1294
|
+
String(field.value ?? '').trim().length === 0 ||
|
|
1295
|
+
String(field.value ?? '') === '0'
|
|
1296
|
+
? 0
|
|
1297
|
+
: parseFloat(field.tag.toString().replace(',', '.'));
|
|
1298
|
+
break;
|
|
1299
|
+
case FieldType.Date:
|
|
1300
|
+
retValue = field.value;
|
|
1301
|
+
break;
|
|
1302
|
+
default:
|
|
1303
|
+
retValue = field.value;
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return retValue === null ? '' : retValue;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/** Returns the native DOM input element for the given field id (getElement). */
|
|
1312
|
+
public gE(id: string): HTMLInputElement {
|
|
1313
|
+
return document.getElementById(id) as HTMLInputElement;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/** Returns the schema Member definition for the given field id (getField). */
|
|
1317
|
+
public gF(id: string): Member {
|
|
1318
|
+
if (this.entity !== null && this.entity.members !== undefined) {
|
|
1319
|
+
if (!this._memberMap) {
|
|
1320
|
+
this._memberMap = new Map(this.entity.members.map(m => [m.id, m]));
|
|
1321
|
+
}
|
|
1322
|
+
return this._memberMap.get(id)!;
|
|
1323
|
+
}
|
|
1324
|
+
return undefined!;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Validates whether the key field value is non-empty and unique within loaded modules.
|
|
1329
|
+
* On success marks the field ReadOnly and triggers commandData('create').
|
|
1330
|
+
*/
|
|
1331
|
+
public checkKey(id: string) {
|
|
1332
|
+
if (this.gV(id) === '' || this.gV(id) === 0) {
|
|
1333
|
+
this.sS(id, FieldState.Fatal, this.getMessage('app.requiredkey'));
|
|
1334
|
+
this.showMessage(this.getMessage('app.requiredkey'));
|
|
1335
|
+
this.resetCommands();
|
|
1336
|
+
} else {
|
|
1337
|
+
this.sS(id, FieldState.None);
|
|
1338
|
+
const entity = this.getDataEntity(
|
|
1339
|
+
this.managment.entId.toLocaleLowerCase()
|
|
1340
|
+
);
|
|
1341
|
+
const prgLength = Math.max(0, entity.modules[0].prg.length - 1);
|
|
1342
|
+
let modulefiltered: ModuleData[];
|
|
1343
|
+
if (prgLength > 0) {
|
|
1344
|
+
modulefiltered = entity.modules.filter(
|
|
1345
|
+
e => e.prg[0] === this.getKeyArray()[0] && e.state === 1
|
|
1346
|
+
);
|
|
1347
|
+
} else {
|
|
1348
|
+
modulefiltered = entity.modules.filter(e => e.state === 1);
|
|
1349
|
+
}
|
|
1350
|
+
let count = 0;
|
|
1351
|
+
for (let i = 0; i < modulefiltered.length; i++) {
|
|
1352
|
+
const v = modulefiltered[i].members.find(
|
|
1353
|
+
e => e.name === id && e.value === this.gV(id)
|
|
1354
|
+
);
|
|
1355
|
+
if (v) {
|
|
1356
|
+
count++;
|
|
1357
|
+
}
|
|
1358
|
+
if (count > 1) {
|
|
1359
|
+
this.sS(id, FieldState.Fatal, this.getMessage('app.existskey'));
|
|
1360
|
+
this.showMessage(this.getMessage('app.existskey'));
|
|
1361
|
+
break;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (this.gS(id) !== FieldState.Fatal) {
|
|
1366
|
+
this.sS(id, FieldState.ReadOnly);
|
|
1367
|
+
if (this.insertMode) {
|
|
1368
|
+
this.sendCommands();
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/** Performs a server-side constraint check before deleting a module record. */
|
|
1375
|
+
public async checkConstraint(): Promise<boolean> {
|
|
1376
|
+
let retData: boolean = true;
|
|
1377
|
+
this.managment.functionId = 'checkconstraint';
|
|
1378
|
+
await this.msv.putDataPromise(this.managment).then(
|
|
1379
|
+
(data: TransportInterface) => {
|
|
1380
|
+
retData = data.booleanResult;
|
|
1381
|
+
},
|
|
1382
|
+
error => {}
|
|
1383
|
+
);
|
|
1384
|
+
return retData;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/** Returns the localised message string for the given message key. */
|
|
1388
|
+
public getMessage(id: string): string {
|
|
1389
|
+
return this.msg.get(id);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/** Displays the snackbar/dialog message overlay; optionally shows a confirm button. */
|
|
1393
|
+
public showMessage(
|
|
1394
|
+
message: string,
|
|
1395
|
+
confirm?: boolean,
|
|
1396
|
+
dialogType?: DialogType
|
|
1397
|
+
) {
|
|
1398
|
+
const field: Member = this.entity.members.find(
|
|
1399
|
+
item => item.id === this.currentFieldId
|
|
1400
|
+
)!;
|
|
1401
|
+
this._dialogTarget = this.currentFieldId;
|
|
1402
|
+
this.gsv.setDialog({ target: this.currentFieldId, state: true });
|
|
1403
|
+
this.textdialog = message;
|
|
1404
|
+
this.dialogType = dialogType!;
|
|
1405
|
+
this.titledialog = this.msg.get('app.notify');
|
|
1406
|
+
this.dialogHeight = 200;
|
|
1407
|
+
this.dialogWidth = 400;
|
|
1408
|
+
const _scale = this.gsv.getScale();
|
|
1409
|
+
this.dialogTop = Math.max(0, (window.innerHeight / _scale - 200) / 2);
|
|
1410
|
+
this.dialogLeft = Math.max(0, (window.innerWidth / _scale - 400) / 2);
|
|
1411
|
+
this.showconfirm = confirm ?? false;
|
|
1412
|
+
this.showdialog = true;
|
|
1413
|
+
this.sendDialog();
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Executes a navigation command (create, plus, minus, first, previous, next, last,
|
|
1418
|
+
* table, readed, print, …) on the entity module list and then calls executeCommand.
|
|
1419
|
+
*/
|
|
1420
|
+
public async commandData(functionId: string): Promise<void> {
|
|
1421
|
+
const entity = this.getDataEntity(this.managment.entId.toLocaleLowerCase());
|
|
1422
|
+
const prgLength = Math.max(0, entity.modules[0].prg.length - 1);
|
|
1423
|
+
|
|
1424
|
+
if (functionId !== 'table') {
|
|
1425
|
+
const currentLinkItem: ItemLinkInterface = this.gsv.getCurrentLinkItem();
|
|
1426
|
+
if (
|
|
1427
|
+
currentLinkItem !== undefined &&
|
|
1428
|
+
currentLinkItem !== null &&
|
|
1429
|
+
this.entity.groupname !== '' &&
|
|
1430
|
+
this.entity.groupname === currentLinkItem.groupName
|
|
1431
|
+
) {
|
|
1432
|
+
for (let i = 0; i < this.entity.prgdim; i++) {
|
|
1433
|
+
if (this.managment.keys !== '') {
|
|
1434
|
+
this.managment.keys += ';';
|
|
1435
|
+
}
|
|
1436
|
+
if (i === 0) {
|
|
1437
|
+
this.managment.keys = 'prg=' + currentLinkItem.prg[i];
|
|
1438
|
+
} else {
|
|
1439
|
+
this.managment.keys += 'prg=1';
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
let modulefiltered = this.getFilteredModules(entity);
|
|
1446
|
+
|
|
1447
|
+
switch (functionId) {
|
|
1448
|
+
case 'create':
|
|
1449
|
+
this.insertMode = false;
|
|
1450
|
+
this.btable = this.viewList || modulefiltered.length <= 1;
|
|
1451
|
+
this.bplus = !this.entity.allowadd;
|
|
1452
|
+
this.bminus = this.entity.allowdelete === 'none';
|
|
1453
|
+
if (!this.bplus) {
|
|
1454
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1455
|
+
} else {
|
|
1456
|
+
this.updateNavButtons(0);
|
|
1457
|
+
}
|
|
1458
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1459
|
+
this.setLastKey(
|
|
1460
|
+
this.getLastKeyFromModule(modulefiltered, prgLength) +
|
|
1461
|
+
modulefiltered.filter(m => m.state === 0).length
|
|
1462
|
+
);
|
|
1463
|
+
modulefiltered[modulefiltered.length - 1].prg = this.getKeyArray();
|
|
1464
|
+
modulefiltered[modulefiltered.length - 1].insert = true;
|
|
1465
|
+
break;
|
|
1466
|
+
case 'readed':
|
|
1467
|
+
if (!this.viewList || this.model.loaded) {
|
|
1468
|
+
this.insertMode =
|
|
1469
|
+
modulefiltered.length === 0 ||
|
|
1470
|
+
modulefiltered[this.lastKey]!.prg.length === 0 ||
|
|
1471
|
+
modulefiltered[this.lastKey]!.members[0]!.state ===
|
|
1472
|
+
FieldState.Fatal;
|
|
1473
|
+
this.btable =
|
|
1474
|
+
this.viewList ||
|
|
1475
|
+
this.insertMode ||
|
|
1476
|
+
modulefiltered.length === 0 ||
|
|
1477
|
+
this.model.masterDetail! > 0;
|
|
1478
|
+
this.bprint = !this.entity.printable;
|
|
1479
|
+
this.bplus = !this.entity.allowadd || this.insertMode;
|
|
1480
|
+
this.bminus = this.entity.allowdelete === 'none' || this.insertMode;
|
|
1481
|
+
if (modulefiltered.length === 0) {
|
|
1482
|
+
entity.modules.push(this.getEmptyModule());
|
|
1483
|
+
modulefiltered.push(entity.modules[entity.modules.length - 1]);
|
|
1484
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1485
|
+
modulefiltered[modulefiltered.length - 1].prg = this.getKeyArray();
|
|
1486
|
+
} else {
|
|
1487
|
+
this.lastKey = Math.max(
|
|
1488
|
+
0,
|
|
1489
|
+
modulefiltered.indexOf(
|
|
1490
|
+
modulefiltered.find(
|
|
1491
|
+
e => e.prg[prgLength] === this.getLastKey()
|
|
1492
|
+
)!
|
|
1493
|
+
)
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1497
|
+
if (modulefiltered[modulefiltered.length - 1].prg.length === 0) {
|
|
1498
|
+
modulefiltered[modulefiltered.length - 1].prg = this.getKeyArray();
|
|
1499
|
+
}
|
|
1500
|
+
this.btable = this.model.loaded && this.model.viewList ? false : true;
|
|
1501
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1502
|
+
} else {
|
|
1503
|
+
this.bplus = !this.entity.allowadd || this.insertMode;
|
|
1504
|
+
this.bprint = false;
|
|
1505
|
+
// Even in list view, run executeCommand so that updateState() fires
|
|
1506
|
+
// (nav-badge states) and lookupChecks validate the loaded data.
|
|
1507
|
+
if (modulefiltered.length === 0) {
|
|
1508
|
+
entity.modules.push(this.getEmptyModule());
|
|
1509
|
+
modulefiltered.push(entity.modules[entity.modules.length - 1]);
|
|
1510
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1511
|
+
modulefiltered[modulefiltered.length - 1].prg = this.getKeyArray();
|
|
1512
|
+
} else {
|
|
1513
|
+
this.lastKey = 0;
|
|
1514
|
+
}
|
|
1515
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1516
|
+
}
|
|
1517
|
+
this.model.loaded = false;
|
|
1518
|
+
modulefiltered[modulefiltered.length - 1].insert = false;
|
|
1519
|
+
break;
|
|
1520
|
+
case 'plus':
|
|
1521
|
+
this.btable = true;
|
|
1522
|
+
this.updateNavButtons(0);
|
|
1523
|
+
this.bplus = true;
|
|
1524
|
+
this.bminus = true;
|
|
1525
|
+
this.insertMode = true;
|
|
1526
|
+
this.viewList = false;
|
|
1527
|
+
if (entity.modules[0].prg.length === 0) {
|
|
1528
|
+
entity.modules.splice(0, 1);
|
|
1529
|
+
}
|
|
1530
|
+
const newModule = this.getEmptyModule();
|
|
1531
|
+
if (prgLength > 0) {
|
|
1532
|
+
entity.modules.push(newModule);
|
|
1533
|
+
}
|
|
1534
|
+
modulefiltered.push(newModule);
|
|
1535
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1536
|
+
this.setLastKey(
|
|
1537
|
+
this.getLastKeyFromModule(modulefiltered, prgLength) +
|
|
1538
|
+
modulefiltered.filter(m => m.state === 0).length
|
|
1539
|
+
);
|
|
1540
|
+
modulefiltered[modulefiltered.length - 1].prg = this.getKeyArray();
|
|
1541
|
+
modulefiltered[modulefiltered.length - 1].insert = true;
|
|
1542
|
+
this.setupFields(this.entity.members);
|
|
1543
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1544
|
+
break;
|
|
1545
|
+
case 'minus':
|
|
1546
|
+
modulefiltered[this.lastKey].state = 0;
|
|
1547
|
+
// Delete linkeds
|
|
1548
|
+
this.model.linkedItems?.forEach(linkedItemValue => {
|
|
1549
|
+
if (linkedItemValue !== '') {
|
|
1550
|
+
const linkedItem = this.data.entities.filter(
|
|
1551
|
+
e => e.name === linkedItemValue.toLocaleLowerCase()
|
|
1552
|
+
)[0];
|
|
1553
|
+
const linkedElements = linkedItem.modules.filter(
|
|
1554
|
+
e => e.prg[0] === this.getKeyArray()[0]
|
|
1555
|
+
);
|
|
1556
|
+
linkedElements.forEach(linkedElement => {
|
|
1557
|
+
linkedElement.state = 0;
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
if (
|
|
1562
|
+
(this.model.linkedItems ?? []).indexOf(
|
|
1563
|
+
this.model.name.toUpperCase()
|
|
1564
|
+
) < 0
|
|
1565
|
+
) {
|
|
1566
|
+
modulefiltered[this.lastKey]!.state = 0;
|
|
1567
|
+
}
|
|
1568
|
+
this.insertMode = true;
|
|
1569
|
+
modulefiltered = modulefiltered.filter(m => m.state === 1);
|
|
1570
|
+
const deleted = entity.modules.filter(
|
|
1571
|
+
e => e.prg[0] === this.getKeyArray()[0] && e.state === 0
|
|
1572
|
+
).length;
|
|
1573
|
+
if (modulefiltered.length > 0) {
|
|
1574
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1575
|
+
} else {
|
|
1576
|
+
await this.commandData('plus');
|
|
1577
|
+
}
|
|
1578
|
+
this.insertMode =
|
|
1579
|
+
modulefiltered.length === 0 ||
|
|
1580
|
+
modulefiltered[this.lastKey].prg.length === 0 ||
|
|
1581
|
+
modulefiltered[this.lastKey].members[0].state === FieldState.Fatal;
|
|
1582
|
+
this.btable = modulefiltered.length <= 1;
|
|
1583
|
+
this.bprint = !this.entity.printable;
|
|
1584
|
+
this.bplus = !this.entity.allowadd || this.insertMode;
|
|
1585
|
+
this.bminus = this.entity.allowdelete === 'none' || this.insertMode;
|
|
1586
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1587
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1588
|
+
break;
|
|
1589
|
+
case 'table':
|
|
1590
|
+
this.lastKey = modulefiltered.indexOf(
|
|
1591
|
+
modulefiltered.find(e => e.prg[prgLength] === this.getLastKey())!
|
|
1592
|
+
);
|
|
1593
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1594
|
+
this.bplus = !this.entity.allowadd || this.insertMode;
|
|
1595
|
+
this.bminus = this.entity.allowdelete === 'none' || this.insertMode;
|
|
1596
|
+
this.btable = false;
|
|
1597
|
+
this.bprint = this.viewList;
|
|
1598
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1599
|
+
break;
|
|
1600
|
+
case 'first':
|
|
1601
|
+
this.lastKey = 0;
|
|
1602
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1603
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1604
|
+
break;
|
|
1605
|
+
case 'previous':
|
|
1606
|
+
this.lastKey--;
|
|
1607
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1608
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1609
|
+
break;
|
|
1610
|
+
case 'next':
|
|
1611
|
+
this.lastKey++;
|
|
1612
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1613
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1614
|
+
break;
|
|
1615
|
+
case 'last':
|
|
1616
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1617
|
+
this.updateNavButtons(modulefiltered.length);
|
|
1618
|
+
await this.executeCommand(modulefiltered, functionId);
|
|
1619
|
+
break;
|
|
1620
|
+
default:
|
|
1621
|
+
this.resetData = false;
|
|
1622
|
+
await this.close().then(_ => {
|
|
1623
|
+
this.msv.putDataPromise(this.managment).then(
|
|
1624
|
+
(data: TransportInterface) => {
|
|
1625
|
+
this.insertMode = false;
|
|
1626
|
+
if (functionId === 'print') {
|
|
1627
|
+
this.gsv.setLoaderState(true);
|
|
1628
|
+
this.managment.fields = [];
|
|
1629
|
+
this.managment.functionId = 'print';
|
|
1630
|
+
this.msv.downloadFile(this.managment).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
1631
|
+
next: data => {
|
|
1632
|
+
switch (data.type) {
|
|
1633
|
+
case HttpEventType.DownloadProgress:
|
|
1634
|
+
break;
|
|
1635
|
+
case HttpEventType.Response:
|
|
1636
|
+
const blob = new Blob([data.body!], {
|
|
1637
|
+
type: data.body!.type,
|
|
1638
|
+
});
|
|
1639
|
+
saveAs(blob, uuidv4() + '.pdf');
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
1643
|
+
error: error => {},
|
|
1644
|
+
});
|
|
1645
|
+
this.gsv.setLoaderState(false);
|
|
1646
|
+
}
|
|
1647
|
+
this.setFocus(
|
|
1648
|
+
this.insertMode
|
|
1649
|
+
? this.model.focusOnInsert
|
|
1650
|
+
: this.model.focusOnUpdate
|
|
1651
|
+
);
|
|
1652
|
+
this.managment.fields = [];
|
|
1653
|
+
this.managment.states = [];
|
|
1654
|
+
},
|
|
1655
|
+
error => {}
|
|
1656
|
+
);
|
|
1657
|
+
});
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
this.gsv.setCurrentLinkItem({
|
|
1662
|
+
id: this.entity.name,
|
|
1663
|
+
groupName: this.entity.groupname,
|
|
1664
|
+
prg: this.getKeyArray(),
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* Sets the four directional navigation buttons (first/prev/next/last) based on
|
|
1670
|
+
* the current `lastKey` position within the filtered module array.
|
|
1671
|
+
*/
|
|
1672
|
+
private updateNavButtons(totalItems: number) {
|
|
1673
|
+
this.bfirst = true;
|
|
1674
|
+
this.bprevious = true;
|
|
1675
|
+
this.bnext = true;
|
|
1676
|
+
this.blast = true;
|
|
1677
|
+
if (totalItems > 1) {
|
|
1678
|
+
if (this.lastKey > 0) {
|
|
1679
|
+
this.bfirst = false;
|
|
1680
|
+
this.bprevious = false;
|
|
1681
|
+
}
|
|
1682
|
+
if (this.lastKey < totalItems - 1) {
|
|
1683
|
+
this.bnext = false;
|
|
1684
|
+
this.blast = false;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/** Returns the filtered module list for the current entity, scoped to the active key. */
|
|
1690
|
+
private getFilteredModules(entity: EntityData): ModuleData[] {
|
|
1691
|
+
const prgLength = Math.max(0, entity.modules[0].prg.length - 1);
|
|
1692
|
+
if (prgLength > 0) {
|
|
1693
|
+
return entity.modules.filter(
|
|
1694
|
+
e => e.prg[0] === this.getKeyArray()[0] && e.state === 1
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
return entity.modules;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/** Pushes updated entity data to GlobalService and optionally triggers a grid refresh. */
|
|
1701
|
+
refreshData(force: boolean = false) {
|
|
1702
|
+
if (this.entity.masterdetail !== 0 || force) {
|
|
1703
|
+
this.gsv.setData(this.fieldsId, this.data);
|
|
1704
|
+
this.gsv.setData(
|
|
1705
|
+
this.managment.appId +
|
|
1706
|
+
'.' +
|
|
1707
|
+
this.managment.entId +
|
|
1708
|
+
'.' +
|
|
1709
|
+
this.managment.year,
|
|
1710
|
+
this.data
|
|
1711
|
+
);
|
|
1712
|
+
this.refreshgrid = !this.refreshgrid;
|
|
1713
|
+
this.gsv.emitGridRefresh();
|
|
1714
|
+
}
|
|
1715
|
+
this.sendCommands();
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/** Computes the next PRG key start index for a new module record. */
|
|
1719
|
+
getLastKeyFromModule(
|
|
1720
|
+
modulefiltered: ModuleData[],
|
|
1721
|
+
prgLength: number
|
|
1722
|
+
): number {
|
|
1723
|
+
let startKey = 0;
|
|
1724
|
+
this.lastKey = modulefiltered.length - 1;
|
|
1725
|
+
if (this.lastKey > 0) {
|
|
1726
|
+
startKey = modulefiltered[this.lastKey - 1].prg[prgLength] + 1;
|
|
1727
|
+
} else {
|
|
1728
|
+
startKey = modulefiltered.length;
|
|
1729
|
+
}
|
|
1730
|
+
return startKey;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/** Clones the first module record from the schema JSON to use as a blank template. */
|
|
1734
|
+
getEmptyModule(): ModuleData {
|
|
1735
|
+
const entity = this.gsv
|
|
1736
|
+
.getDataJson(this.getIdModel())
|
|
1737
|
+
.entities.filter(
|
|
1738
|
+
e => e.name === this.managment.entId.toLocaleLowerCase()
|
|
1739
|
+
)[0];
|
|
1740
|
+
return JSON.parse(JSON.stringify(entity.modules[0]));
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/** Returns the composite project key 'PROJECT.YEAR' used to look up schema/data caches. */
|
|
1744
|
+
getIdModel(): string {
|
|
1745
|
+
return this.gsv.getProject().project + '.' + this.gsv.getProject().year;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
/** Returns the composite entity cache key 'APP.ENT.YEAR'. */
|
|
1749
|
+
getEntIdModel(): string {
|
|
1750
|
+
return (
|
|
1751
|
+
this.managment.appId +
|
|
1752
|
+
'.' +
|
|
1753
|
+
this.managment.entId +
|
|
1754
|
+
'.' +
|
|
1755
|
+
this.managment.year
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* Populates entity member fields from the given module, fires fieldChanged for each,
|
|
1761
|
+
* syncs initialvalue/initialstate after insert to avoid false-dirty detection, and
|
|
1762
|
+
* applies any pending logic refresh flags before moving focus.
|
|
1763
|
+
*/
|
|
1764
|
+
async executeCommand(
|
|
1765
|
+
module: ModuleData[],
|
|
1766
|
+
functionId: string
|
|
1767
|
+
): Promise<void> {
|
|
1768
|
+
this._readonlySet.clear();
|
|
1769
|
+
let entity = module[this.lastKey];
|
|
1770
|
+
for (let fld of entity.members) {
|
|
1771
|
+
const field: Member = this.entity.members.find(
|
|
1772
|
+
item => item.name === fld.name
|
|
1773
|
+
)!;
|
|
1774
|
+
if (field) {
|
|
1775
|
+
if (!field.visible) {
|
|
1776
|
+
this.sS(field.id, FieldState.Hidden);
|
|
1777
|
+
}
|
|
1778
|
+
switch (field.type) {
|
|
1779
|
+
case FieldType.Date:
|
|
1780
|
+
if (fld.value !== null && fld.value !== '' && typeof fld.value === 'string') {
|
|
1781
|
+
field.value =
|
|
1782
|
+
fld.value.length > 10
|
|
1783
|
+
? this.formatDateFromISO(
|
|
1784
|
+
fld.value.substring(0, 10),
|
|
1785
|
+
this.gsv.getLng() === Languages.IT
|
|
1786
|
+
? 'DD/MM/YYYY'
|
|
1787
|
+
: 'MM/DD/YYYY'
|
|
1788
|
+
)
|
|
1789
|
+
: fld.value;
|
|
1790
|
+
field.tag = new Date(fld.value);
|
|
1791
|
+
} else {
|
|
1792
|
+
field.value = null;
|
|
1793
|
+
field.tag = null;
|
|
1794
|
+
}
|
|
1795
|
+
break;
|
|
1796
|
+
case FieldType.Number:
|
|
1797
|
+
if (fld.value === 0) {
|
|
1798
|
+
field.value = '';
|
|
1799
|
+
field.tag = '';
|
|
1800
|
+
} else {
|
|
1801
|
+
field.value = fld.value;
|
|
1802
|
+
field.tag = parseFloat(String(fld.value));
|
|
1803
|
+
}
|
|
1804
|
+
break;
|
|
1805
|
+
case FieldType.Text:
|
|
1806
|
+
case FieldType.TextualCheck:
|
|
1807
|
+
case FieldType.Combo:
|
|
1808
|
+
field.value = fld.value;
|
|
1809
|
+
field.tag = fld.value;
|
|
1810
|
+
break;
|
|
1811
|
+
default:
|
|
1812
|
+
field.value = fld.value;
|
|
1813
|
+
field.tag = fld.value;
|
|
1814
|
+
break;
|
|
1815
|
+
}
|
|
1816
|
+
await this.fieldChanged({
|
|
1817
|
+
id: fld.name,
|
|
1818
|
+
value: String(field.value ?? ''),
|
|
1819
|
+
function: 'executeCommand',
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
if (!this.insertMode) {
|
|
1824
|
+
this.setLastKey(
|
|
1825
|
+
module[this.lastKey].prg[module[this.lastKey].prg.length - 1]
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
let fieldFocus =
|
|
1830
|
+
this.insertMode || !this.entity.members.find(_ => _.value !== '')
|
|
1831
|
+
? this.entity.focusoninsert
|
|
1832
|
+
: this.entity.focusonupdate;
|
|
1833
|
+
|
|
1834
|
+
if (
|
|
1835
|
+
this.gsv.getCurrentFocus().id &&
|
|
1836
|
+
this.gsv.getCurrentFocus().id.length > 0
|
|
1837
|
+
) {
|
|
1838
|
+
fieldFocus = this.gsv.getCurrentFocus().id;
|
|
1839
|
+
this.gsv.setCurrentFocus({ id: '', value: '' });
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
this.logic.function(
|
|
1843
|
+
Functions.executedCommand + functionId,
|
|
1844
|
+
this.lastKey,
|
|
1845
|
+
this.getKeyArray(),
|
|
1846
|
+
this.currentFieldId
|
|
1847
|
+
);
|
|
1848
|
+
|
|
1849
|
+
// After all auto-fills/calculations on a new module, sync initialvalue so
|
|
1850
|
+
// that logic-auto-filled defaults are not treated as user changes. Without
|
|
1851
|
+
// this, close() → getJsonData() would detect value !== initialvalue and
|
|
1852
|
+
// send an empty record to the server even if the user never typed anything.
|
|
1853
|
+
// Exception: non-empty Date fields are intentionally excluded from the sync
|
|
1854
|
+
// so that mandatory today-filled dates are always serialised in the save
|
|
1855
|
+
// payload (member.initialvalue stays null → value !== initialvalue).
|
|
1856
|
+
if (module[this.lastKey]?.insert) {
|
|
1857
|
+
module[this.lastKey].members.forEach(m => {
|
|
1858
|
+
if (m.type === FieldType.Date && m.value !== null && m.value !== '') {
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
m.initialvalue = m.value;
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// Apply any state/value changes queued via refresh flags (e.g. dynamicView
|
|
1866
|
+
// triggered by logic.function above). Sync initialstate so getJsonData()
|
|
1867
|
+
// does not mistake structural visibility changes for user edits.
|
|
1868
|
+
try {
|
|
1869
|
+
const _eDyn = this.getDataEntity(this.model.name.toLocaleLowerCase());
|
|
1870
|
+
const _idxDyn = this.getIndex();
|
|
1871
|
+
const _dynModule = _eDyn?.modules.filter(e => e.state === 1)[_idxDyn];
|
|
1872
|
+
const _isInsert = !!_dynModule?.insert;
|
|
1873
|
+
const _toRefresh = _dynModule?.members.filter(m => m.refresh) ?? [];
|
|
1874
|
+
_toRefresh.forEach(m => {
|
|
1875
|
+
m.initialstate = m.state;
|
|
1876
|
+
// Do not sync initialvalue for Date fields whose value is non-empty but
|
|
1877
|
+
// whose initialvalue is still null. This means the date was set by the
|
|
1878
|
+
// mandatory-today logic over a null server value and must remain dirty
|
|
1879
|
+
// so that getJsonData() includes it in the save payload.
|
|
1880
|
+
if (
|
|
1881
|
+
!(
|
|
1882
|
+
m.type === FieldType.Date &&
|
|
1883
|
+
m.value !== null &&
|
|
1884
|
+
m.value !== '' &&
|
|
1885
|
+
m.initialvalue === null
|
|
1886
|
+
)
|
|
1887
|
+
) {
|
|
1888
|
+
m.initialvalue = m.value;
|
|
1889
|
+
}
|
|
1890
|
+
this.sS(m.name, m.state);
|
|
1891
|
+
this.sV(m.name, m.value);
|
|
1892
|
+
if (m.message) this.gF(m.name).title = m.message;
|
|
1893
|
+
m.refresh = false;
|
|
1894
|
+
});
|
|
1895
|
+
} catch (e) {
|
|
1896
|
+
console.error('executeCommand refresh-flags error:', e);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// General logic.calc() pass: run calc for every field in the loaded module so
|
|
1900
|
+
// that state changes expressed in calc() (ReadOnly, Error, Warning, Hidden…)
|
|
1901
|
+
// are applied on initial load for ALL models — not only those that use the
|
|
1902
|
+
// logic.function(executedCommand*) / refresh-flag mechanism.
|
|
1903
|
+
try {
|
|
1904
|
+
const _calcIdx = this.getIndex();
|
|
1905
|
+
for (const fld of entity.members) {
|
|
1906
|
+
this.logic.calc(fld.name, _calcIdx, this.getKeyArray());
|
|
1907
|
+
}
|
|
1908
|
+
// Apply refresh flags accumulated by the calc pass above.
|
|
1909
|
+
const _eCalc = this.getDataEntity(this.model.name.toLocaleLowerCase());
|
|
1910
|
+
const _calcModule = _eCalc?.modules.filter(e => e.state === 1)[_calcIdx];
|
|
1911
|
+
if (_calcModule) {
|
|
1912
|
+
_calcModule.members
|
|
1913
|
+
.filter(m => m.refresh)
|
|
1914
|
+
.forEach(m => {
|
|
1915
|
+
m.initialstate = m.state;
|
|
1916
|
+
// Do not sync initialvalue for Date fields whose value is non-empty but
|
|
1917
|
+
// whose initialvalue is still null. This means the date was set by the
|
|
1918
|
+
// mandatory-today logic over a null server value (either new insert or
|
|
1919
|
+
// existing record that never had a date) and must remain dirty so that
|
|
1920
|
+
// getJsonData() includes it in the save payload.
|
|
1921
|
+
if (
|
|
1922
|
+
!(
|
|
1923
|
+
m.type === FieldType.Date &&
|
|
1924
|
+
m.value !== null &&
|
|
1925
|
+
m.value !== '' &&
|
|
1926
|
+
m.initialvalue === null
|
|
1927
|
+
)
|
|
1928
|
+
) {
|
|
1929
|
+
m.initialvalue = m.value;
|
|
1930
|
+
}
|
|
1931
|
+
this.sS(m.name, m.state);
|
|
1932
|
+
this.sV(m.name, m.value);
|
|
1933
|
+
if (m.message) this.gF(m.name).title = m.message;
|
|
1934
|
+
m.refresh = false;
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
} catch (e) {
|
|
1938
|
+
console.error('executeCommand calc-pass error:', e);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// Re-apply business-logic states (Error/Warning) that were wiped by the
|
|
1942
|
+
// fieldChanged('executeCommand') loop above, which resets any Error state
|
|
1943
|
+
// to None before logic.calc has a chance to re-set it.
|
|
1944
|
+
if (functionId === 'create' && this.currentFieldId) {
|
|
1945
|
+
this.fieldBusiness(this.currentFieldId);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
this.save = true;
|
|
1949
|
+
this.updateState();
|
|
1950
|
+
this.refreshData(true);
|
|
1951
|
+
this.setFocus(fieldFocus);
|
|
1952
|
+
this.gsv.setLoaderState(false);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
/**
|
|
1956
|
+
* Serialises only changed (or deleted) members and modules to a JSON string
|
|
1957
|
+
* suitable for PUT /managment. Returns an empty string when there is nothing to save.
|
|
1958
|
+
*/
|
|
1959
|
+
getJsonData(): string {
|
|
1960
|
+
if (this.data) {
|
|
1961
|
+
let hasData = false;
|
|
1962
|
+
let json = '{"entities":[';
|
|
1963
|
+
this.data.entities.forEach(entity => {
|
|
1964
|
+
let modules = 0;
|
|
1965
|
+
let hasModule = false;
|
|
1966
|
+
entity.modules.forEach((module, mi) => {
|
|
1967
|
+
let members = 0;
|
|
1968
|
+
const fatalMember = module.members.find(
|
|
1969
|
+
i => i.state === FieldState.Fatal
|
|
1970
|
+
);
|
|
1971
|
+
if (fatalMember) {
|
|
1972
|
+
// Only mark as deleted if the module has actual server data
|
|
1973
|
+
// (at least one member whose value differs from initialvalue).
|
|
1974
|
+
// Template modules created locally but never committed should not
|
|
1975
|
+
// generate @deleted markers that trigger spurious save requests.
|
|
1976
|
+
const hasServerData = module.members.some(
|
|
1977
|
+
m =>
|
|
1978
|
+
m.value !== m.initialvalue ||
|
|
1979
|
+
(m.state !== m.initialstate && m.state !== FieldState.Fatal)
|
|
1980
|
+
);
|
|
1981
|
+
if (hasServerData) {
|
|
1982
|
+
module.state = 0;
|
|
1983
|
+
} else {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (module.state === 0) {
|
|
1988
|
+
module.members = [];
|
|
1989
|
+
}
|
|
1990
|
+
if (module.members.length === 0) {
|
|
1991
|
+
if (modules === 0 && members == 0) {
|
|
1992
|
+
if (json !== '{"entities":[') {
|
|
1993
|
+
json += ',';
|
|
1994
|
+
}
|
|
1995
|
+
json += '{"name":"' + entity.name + '","modules":[';
|
|
1996
|
+
}
|
|
1997
|
+
if (modules > 0 && members === 0) {
|
|
1998
|
+
json += ',';
|
|
1999
|
+
}
|
|
2000
|
+
if (members === 0) {
|
|
2001
|
+
json += '{ "members": [';
|
|
2002
|
+
}
|
|
2003
|
+
if (members > 0) {
|
|
2004
|
+
json += ',';
|
|
2005
|
+
}
|
|
2006
|
+
json += '{"name":"@deleted"}';
|
|
2007
|
+
hasData = true;
|
|
2008
|
+
members++;
|
|
2009
|
+
}
|
|
2010
|
+
module.members.forEach(member => {
|
|
2011
|
+
if (
|
|
2012
|
+
(member?.value !== member?.initialvalue ||
|
|
2013
|
+
member?.initialstate !== member?.state) &&
|
|
2014
|
+
member.type !== FieldType.Label &&
|
|
2015
|
+
member.type !== FieldType.Button &&
|
|
2016
|
+
member.type !== FieldType.Video &&
|
|
2017
|
+
member.type !== FieldType.Image &&
|
|
2018
|
+
member.type !== FieldType.Map &&
|
|
2019
|
+
member.type !== FieldType.Chart
|
|
2020
|
+
) {
|
|
2021
|
+
if (modules === 0 && members == 0) {
|
|
2022
|
+
if (json !== '{"entities":[') {
|
|
2023
|
+
json += ',';
|
|
2024
|
+
}
|
|
2025
|
+
json += '{"name":"' + entity.name + '","modules":[';
|
|
2026
|
+
}
|
|
2027
|
+
if (modules > 0 && members === 0) {
|
|
2028
|
+
json += ',';
|
|
2029
|
+
}
|
|
2030
|
+
if (members === 0) {
|
|
2031
|
+
json += '{ "members": [';
|
|
2032
|
+
}
|
|
2033
|
+
if (members > 0) {
|
|
2034
|
+
json += ',';
|
|
2035
|
+
}
|
|
2036
|
+
const _serializedValue =
|
|
2037
|
+
member.type === FieldType.Date && member.value
|
|
2038
|
+
? this.displayDateToISO(member.value.toString())
|
|
2039
|
+
: member.value
|
|
2040
|
+
? member.value.toString().toUpperCase()
|
|
2041
|
+
: '';
|
|
2042
|
+
json +=
|
|
2043
|
+
'{"name":"' +
|
|
2044
|
+
member.name +
|
|
2045
|
+
'", "value":' +
|
|
2046
|
+
'"' +
|
|
2047
|
+
_serializedValue +
|
|
2048
|
+
'"' +
|
|
2049
|
+
',"state":' +
|
|
2050
|
+
member.state +
|
|
2051
|
+
',"initialstate":' +
|
|
2052
|
+
member.initialstate +
|
|
2053
|
+
'}';
|
|
2054
|
+
member.initialvalue = member.value;
|
|
2055
|
+
member.initialstate = member.state;
|
|
2056
|
+
members++;
|
|
2057
|
+
hasData = true;
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
if (module.prg.length === 0) {
|
|
2061
|
+
module.prg = this.getKeyArray();
|
|
2062
|
+
}
|
|
2063
|
+
if (members > 0) {
|
|
2064
|
+
json += '],"prg":[' + module.prg + ']}';
|
|
2065
|
+
modules++;
|
|
2066
|
+
hasModule = true;
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
if (hasModule) {
|
|
2070
|
+
json += ']}';
|
|
2071
|
+
}
|
|
2072
|
+
});
|
|
2073
|
+
json += ']}';
|
|
2074
|
+
return hasData ? json : '';
|
|
2075
|
+
}
|
|
2076
|
+
return '';
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/** Parses the composite PRG key string into a numeric array (e.g. 'prg=1;prg=2' → [1,2]). */
|
|
2080
|
+
public getKeyArray(): number[] {
|
|
2081
|
+
if (
|
|
2082
|
+
this._cachedKeyArray === null ||
|
|
2083
|
+
this._cachedKeyString !== this.managment.keys
|
|
2084
|
+
) {
|
|
2085
|
+
this._cachedKeyString = this.managment.keys;
|
|
2086
|
+
this._cachedKeyArray = this.managment.keys
|
|
2087
|
+
.split(';')
|
|
2088
|
+
.map(e => parseInt(e.replace('prg=', ''), 10));
|
|
2089
|
+
}
|
|
2090
|
+
return this._cachedKeyArray;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
/** Returns the last (innermost) PRG key from the composite key string. */
|
|
2094
|
+
public getLastKey(): number {
|
|
2095
|
+
const keys = this.managment.keys.split(';');
|
|
2096
|
+
return parseInt(keys[keys.length - 1].replace('prg=', ''));
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/** Replaces the last PRG segment in the composite key string with the given value. */
|
|
2100
|
+
public setLastKey(lastKey: number) {
|
|
2101
|
+
const keyElements = this.managment.keys.split(';');
|
|
2102
|
+
keyElements[keyElements.length - 1] = 'prg=' + lastKey;
|
|
2103
|
+
this.managment.keys = keyElements.join(';');
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
/** Resets the dialog overlay state and restores focus after the user dismisses the dialog. */
|
|
2107
|
+
public dialogResult(rsp: DialogResultEvent) {
|
|
2108
|
+
this.textdialog = '';
|
|
2109
|
+
this.titledialog = '';
|
|
2110
|
+
this.showconfirm = false;
|
|
2111
|
+
this.showdialog = false;
|
|
2112
|
+
const target = this._dialogTarget;
|
|
2113
|
+
this._dialogTarget = '';
|
|
2114
|
+
if (target !== '') {
|
|
2115
|
+
if (this.gS(target) === FieldState.Fatal) {
|
|
2116
|
+
this.sV(target, '');
|
|
2117
|
+
this.sS(target, FieldState.None);
|
|
2118
|
+
}
|
|
2119
|
+
this.setFocus(target);
|
|
2120
|
+
} else {
|
|
2121
|
+
this.setFocus(
|
|
2122
|
+
this.insertMode ? this.entity.focusoninsert : this.entity.focusonupdate
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
this.gsv.setDialog({ target: '', state: false });
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
/**
|
|
2129
|
+
* Updates the active field ID and shows/hides the lookup trigger button
|
|
2130
|
+
* when the focused field has an associated lookup entity.
|
|
2131
|
+
*/
|
|
2132
|
+
private currentField(currentField: Member) {
|
|
2133
|
+
if (
|
|
2134
|
+
currentField &&
|
|
2135
|
+
this.entity &&
|
|
2136
|
+
this.entity.members &&
|
|
2137
|
+
currentField.id !== this.currentFieldId
|
|
2138
|
+
) {
|
|
2139
|
+
this.currentFieldId = currentField.id;
|
|
2140
|
+
const field: Member = this.entity.members.find(
|
|
2141
|
+
item => item.id === currentField.id
|
|
2142
|
+
)!;
|
|
2143
|
+
this.textcommands = '';
|
|
2144
|
+
this.texttitle = '';
|
|
2145
|
+
this.showcommands = false;
|
|
2146
|
+
this.bdetail = true;
|
|
2147
|
+
this.bintodetail = true;
|
|
2148
|
+
if (field && field.lookup !== '') {
|
|
2149
|
+
this.lookupField = currentField;
|
|
2150
|
+
this.commandsHeight = parseInt(field.height, 10);
|
|
2151
|
+
this.commandsWidth = 12;
|
|
2152
|
+
this.commandsTop = parseInt(field.y, 10);
|
|
2153
|
+
this.commandsLeft =
|
|
2154
|
+
parseInt(field.x, 10) + parseInt(field.width, 10) - 12;
|
|
2155
|
+
this.textcommands = '...';
|
|
2156
|
+
this.texttitle = field.lookup.split('#')[6];
|
|
2157
|
+
this.showcommands = true;
|
|
2158
|
+
this.bdetail = false;
|
|
2159
|
+
this.bintodetail = false;
|
|
2160
|
+
}
|
|
2161
|
+
field.value = field?.tag;
|
|
2162
|
+
if (field?.autoformatter) {
|
|
2163
|
+
const domEl = this.gE(field.id);
|
|
2164
|
+
if (domEl) domEl.value = String(field.tag ?? '');
|
|
2165
|
+
}
|
|
2166
|
+
this.sendCommands();
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Core field-change pipeline: normalises the input value (padding, regex, lookup),
|
|
2172
|
+
* persists it to MemberData, runs business logic, and schedules DOM updates.
|
|
2173
|
+
*/
|
|
2174
|
+
private async fieldChanged(currentFieldChanged: ItemFieldChangedInterface) {
|
|
2175
|
+
if (
|
|
2176
|
+
currentFieldChanged.function === 'asyncRefresh' ||
|
|
2177
|
+
(this.showdialog && currentFieldChanged.function === 'sendChanged') ||
|
|
2178
|
+
(currentFieldChanged.function === 'sendChanged' &&
|
|
2179
|
+
this.gF(currentFieldChanged.id)?.lookupoutput &&
|
|
2180
|
+
!this.gF(currentFieldChanged.id)?.lookup &&
|
|
2181
|
+
this.gS(currentFieldChanged.id) === FieldState.ReadOnly)
|
|
2182
|
+
) {
|
|
2183
|
+
if (currentFieldChanged.function === 'asyncRefresh') {
|
|
2184
|
+
this.cdr.detectChanges();
|
|
2185
|
+
}
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (this.entity) {
|
|
2189
|
+
const id = currentFieldChanged.id;
|
|
2190
|
+
const field: Member = this.entity?.members?.find(item => item.id === id)!;
|
|
2191
|
+
if (field) {
|
|
2192
|
+
if (
|
|
2193
|
+
(this.gS(currentFieldChanged.id) === FieldState.Error ||
|
|
2194
|
+
field.function !== 'none') &&
|
|
2195
|
+
this.gS(currentFieldChanged.id) !== FieldState.Forced &&
|
|
2196
|
+
this.gS(currentFieldChanged.id) !== FieldState.ReadOnly
|
|
2197
|
+
) {
|
|
2198
|
+
this.sS(currentFieldChanged.id, FieldState.None);
|
|
2199
|
+
}
|
|
2200
|
+
if (this.gE(field.id)) {
|
|
2201
|
+
this.sV(id, currentFieldChanged.value);
|
|
2202
|
+
switch (field.type) {
|
|
2203
|
+
case FieldType.Text:
|
|
2204
|
+
if (field.generateuid && field.value === '') {
|
|
2205
|
+
this.sV(field.id, uuidv4());
|
|
2206
|
+
}
|
|
2207
|
+
break;
|
|
2208
|
+
case FieldType.Number:
|
|
2209
|
+
if (field.generateuid && field.value === '') {
|
|
2210
|
+
this.sV(
|
|
2211
|
+
field.id,
|
|
2212
|
+
parseInt(this.managment.keys.replace('prg=', ''), 0)
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
break;
|
|
2216
|
+
}
|
|
2217
|
+
if (
|
|
2218
|
+
field.groupname !== '' &&
|
|
2219
|
+
field.value !== '' &&
|
|
2220
|
+
field.groupname !== undefined
|
|
2221
|
+
) {
|
|
2222
|
+
for (let i = 0; i < this.entity.members.length; i++) {
|
|
2223
|
+
if (
|
|
2224
|
+
this.entity.members[i].groupname === field.groupname &&
|
|
2225
|
+
this.entity.members[i].id !== field.id
|
|
2226
|
+
) {
|
|
2227
|
+
this.sV(this.entity.members[i].id, '');
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
// Controlli di livello generale
|
|
2232
|
+
switch (field.type) {
|
|
2233
|
+
case FieldType.Date:
|
|
2234
|
+
if (
|
|
2235
|
+
field.mandatory === true &&
|
|
2236
|
+
this.gV(id) === '' &&
|
|
2237
|
+
this.gS(id) !== FieldState.Forced
|
|
2238
|
+
) {
|
|
2239
|
+
if (this.gS(currentFieldChanged.id) === FieldState.Error) {
|
|
2240
|
+
this.sS(currentFieldChanged.id, FieldState.None);
|
|
2241
|
+
}
|
|
2242
|
+
if (field.today) {
|
|
2243
|
+
const d = new Date();
|
|
2244
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
2245
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
2246
|
+
const yyyy = d.getFullYear();
|
|
2247
|
+
const dataStr = this.gsv.getLng() === Languages.IT
|
|
2248
|
+
? `${dd}/${mm}/${yyyy}`
|
|
2249
|
+
: `${mm}/${dd}/${yyyy}`;
|
|
2250
|
+
this.sV(id, dataStr);
|
|
2251
|
+
} else {
|
|
2252
|
+
this.sS(id, FieldState.Error, this.msg.get('app.required'));
|
|
2253
|
+
}
|
|
2254
|
+
} else if (this.gV(id).length < 10 && this.gV(id).length > 0) {
|
|
2255
|
+
this.sS(id, FieldState.Error, this.msg.get('app.dateerror'));
|
|
2256
|
+
} else if (
|
|
2257
|
+
this.gV(id) &&
|
|
2258
|
+
!this.isValidDateString(
|
|
2259
|
+
this.gV(id),
|
|
2260
|
+
this.gsv.getLng() === Languages.IT
|
|
2261
|
+
? 'DD/MM/YYYY'
|
|
2262
|
+
: 'MM/DD/YYYY'
|
|
2263
|
+
)
|
|
2264
|
+
) {
|
|
2265
|
+
this.sS(id, FieldState.Error, this.msg.get('app.dateerror'));
|
|
2266
|
+
}
|
|
2267
|
+
break;
|
|
2268
|
+
case FieldType.Text:
|
|
2269
|
+
if (
|
|
2270
|
+
field.mandatory === true &&
|
|
2271
|
+
this.gV(id) === '' &&
|
|
2272
|
+
this.gS(id) !== FieldState.Forced
|
|
2273
|
+
) {
|
|
2274
|
+
this.sS(id, FieldState.Error, this.msg.get('app.required'));
|
|
2275
|
+
}
|
|
2276
|
+
if (
|
|
2277
|
+
field.function === 'cf' &&
|
|
2278
|
+
this.gS(id) !== FieldState.Forced
|
|
2279
|
+
) {
|
|
2280
|
+
if (field.value !== '') {
|
|
2281
|
+
// Strip autoformatter spaces before structural validation so
|
|
2282
|
+
// that a spaced display value (e.g. "R S S M ...") validates
|
|
2283
|
+
// correctly against the raw 16-char CF string.
|
|
2284
|
+
const cfRaw = String(field.value ?? '').replace(/\s/g, '');
|
|
2285
|
+
try {
|
|
2286
|
+
const cf = new CodiceFiscale(cfRaw);
|
|
2287
|
+
} catch (err) {
|
|
2288
|
+
// BUG-FIX: was `if (this.gS(id))` — FieldState.None === 0 is
|
|
2289
|
+
// falsy, so the error was never set after the reset-to-None
|
|
2290
|
+
// that always runs at the top of fieldChanged. Only skip if
|
|
2291
|
+
// the field is already in a Fatal state (key field).
|
|
2292
|
+
if (this.gS(id) !== FieldState.Fatal) {
|
|
2293
|
+
this.sS(id, FieldState.Error, this.msg.get('app.cferr'));
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (
|
|
2299
|
+
field.function === 'piva' &&
|
|
2300
|
+
this.gS(id) !== FieldState.Forced
|
|
2301
|
+
) {
|
|
2302
|
+
// Strip autoformatter spaces before validation.
|
|
2303
|
+
const pivaRaw = String(field.value ?? '').replace(/\s/g, '');
|
|
2304
|
+
const piva: PartitaIVA = new PartitaIVA(pivaRaw);
|
|
2305
|
+
if (piva.Check().length !== 0) {
|
|
2306
|
+
this.sS(id, FieldState.Error, this.msg.get('app.pivaerr'));
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
if (
|
|
2310
|
+
field.function === 'cfpiva' &&
|
|
2311
|
+
this.gS(id) !== FieldState.Forced
|
|
2312
|
+
) {
|
|
2313
|
+
if (field.value !== '') {
|
|
2314
|
+
// Strip autoformatter spaces so the raw value is validated.
|
|
2315
|
+
// A 16-char CF with spacing:1 becomes 31 chars in the DOM;
|
|
2316
|
+
// without stripping, it would incorrectly fall into the PIVA
|
|
2317
|
+
// branch and get flagged as an error.
|
|
2318
|
+
const cfpivaRaw = String(field.value ?? '').replace(/\s/g, '');
|
|
2319
|
+
let error = false;
|
|
2320
|
+
try {
|
|
2321
|
+
if (cfpivaRaw.length === 16) {
|
|
2322
|
+
const cf = new CodiceFiscale(cfpivaRaw);
|
|
2323
|
+
error = false;
|
|
2324
|
+
} else {
|
|
2325
|
+
const piva: PartitaIVA = new PartitaIVA(cfpivaRaw);
|
|
2326
|
+
const cfpiva = piva.Check().length !== 0;
|
|
2327
|
+
if (cfpiva) {
|
|
2328
|
+
error = true;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
} catch (err) {
|
|
2332
|
+
error = true;
|
|
2333
|
+
}
|
|
2334
|
+
if (error) {
|
|
2335
|
+
this.sS(
|
|
2336
|
+
id,
|
|
2337
|
+
FieldState.Error,
|
|
2338
|
+
this.msg.get('app.cfpivaerr')
|
|
2339
|
+
);
|
|
2340
|
+
} else {
|
|
2341
|
+
this.sS(id, FieldState.None, '');
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
if (
|
|
2346
|
+
field.function === 'mail' &&
|
|
2347
|
+
this.gS(id) !== FieldState.Forced
|
|
2348
|
+
) {
|
|
2349
|
+
if (field.value != '') {
|
|
2350
|
+
// Strip spaces (autoformatter artefacts) before regex check.
|
|
2351
|
+
const mailRaw = String(field.value ?? '').replace(/\s/g, '');
|
|
2352
|
+
if (!mailRaw.match('[^s@]+@[^s@]+.[^s@]+$')) {
|
|
2353
|
+
this.sS(id, FieldState.Error, this.msg.get('app.emailerr'));
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
break;
|
|
2358
|
+
case FieldType.Number:
|
|
2359
|
+
if (
|
|
2360
|
+
field.mandatory === true &&
|
|
2361
|
+
this.gV(id) === 0 &&
|
|
2362
|
+
this.gS(id) !== FieldState.Forced
|
|
2363
|
+
) {
|
|
2364
|
+
this.sS(id, FieldState.Error, this.msg.get('app.required'));
|
|
2365
|
+
}
|
|
2366
|
+
if (field.min !== '' && this.gS(id) !== FieldState.Forced) {
|
|
2367
|
+
const minV = parseFloat(String(field.min));
|
|
2368
|
+
if (minV !== 0) {
|
|
2369
|
+
if (this.gV(id) < minV) {
|
|
2370
|
+
this.sS(id, FieldState.Error, this.msg.get('app.rangemin'));
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (field.max !== '' && this.gS(id) !== FieldState.Forced) {
|
|
2375
|
+
const maxV = parseFloat(String(field.max));
|
|
2376
|
+
if (maxV !== 0) {
|
|
2377
|
+
if (this.gV(id) > maxV) {
|
|
2378
|
+
this.sS(id, FieldState.Error, this.msg.get('app.rangemax'));
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
break;
|
|
2383
|
+
}
|
|
2384
|
+
if (
|
|
2385
|
+
field.lookup !== '' &&
|
|
2386
|
+
!this.showlookup &&
|
|
2387
|
+
currentFieldChanged.function !== 'noBusiness' &&
|
|
2388
|
+
currentFieldChanged.function !== 'lookupCheck'
|
|
2389
|
+
) {
|
|
2390
|
+
const lkmanagment: ManagmentInterface =
|
|
2391
|
+
this.gsv.getManagmentInterface();
|
|
2392
|
+
const lkinfo: string[] = field.lookup.split('#');
|
|
2393
|
+
const input: string[] = lkinfo[3].split(',');
|
|
2394
|
+
const output: string[] = lkinfo[4].split(',');
|
|
2395
|
+
const extern: string[] = lkinfo[5].split(',');
|
|
2396
|
+
lkmanagment.fields = [];
|
|
2397
|
+
lkmanagment.functionId = 'read';
|
|
2398
|
+
lkmanagment.appId = lkinfo[0].toUpperCase();
|
|
2399
|
+
lkmanagment.year = parseInt(lkinfo[1], 10);
|
|
2400
|
+
lkmanagment.entId = lkinfo[2].toUpperCase();
|
|
2401
|
+
lkmanagment.keys = 'prg=0';
|
|
2402
|
+
const lkFieldsId =
|
|
2403
|
+
lkmanagment.appId +
|
|
2404
|
+
'.' +
|
|
2405
|
+
lkmanagment.entId +
|
|
2406
|
+
'.' +
|
|
2407
|
+
lkmanagment.year;
|
|
2408
|
+
const lkdata = this.gsv.getData(lkFieldsId);
|
|
2409
|
+
let resolvedData = lkdata;
|
|
2410
|
+
// During executeCommand (initial data load from server), skip the
|
|
2411
|
+
// expensive HTTP fetch — values are already valid. If lookup data is
|
|
2412
|
+
// cached we still run lookupCheck to set ReadOnly states; if not
|
|
2413
|
+
// cached we just apply ReadOnly to lookupoutput fields directly.
|
|
2414
|
+
if (currentFieldChanged.function === 'executeCommand' && !lkdata) {
|
|
2415
|
+
for (let i = 1; i < output.length; i++) {
|
|
2416
|
+
if (this.gF(output[i])?.lookupoutput) {
|
|
2417
|
+
this.sS(
|
|
2418
|
+
output[i],
|
|
2419
|
+
this.gS(output[i]) !== this.fieldState.Hidden
|
|
2420
|
+
? this.fieldState.ReadOnly
|
|
2421
|
+
: this.fieldState.Hidden
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
} else {
|
|
2426
|
+
if (
|
|
2427
|
+
!lkdata ||
|
|
2428
|
+
(this.gsv.getSchemaJson(
|
|
2429
|
+
lkmanagment.appId + '.' + lkmanagment.year
|
|
2430
|
+
) !== undefined &&
|
|
2431
|
+
!this.gsv
|
|
2432
|
+
.getSchemaJson(lkmanagment.appId + '.' + lkmanagment.year)
|
|
2433
|
+
.entities.find(
|
|
2434
|
+
e => e.name == lkmanagment.entId.toLocaleLowerCase()
|
|
2435
|
+
)!.cache)
|
|
2436
|
+
) {
|
|
2437
|
+
if (currentFieldChanged.function !== 'executeCommand') {
|
|
2438
|
+
const data = await firstValueFrom(
|
|
2439
|
+
this.msv.getData(lkmanagment)
|
|
2440
|
+
);
|
|
2441
|
+
const dataFile = JSON.parse(data.dataFile as string);
|
|
2442
|
+
this.gsv.setData(lkFieldsId, dataFile);
|
|
2443
|
+
resolvedData = dataFile;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
if (resolvedData) {
|
|
2447
|
+
const module = resolvedData!.entities.filter(
|
|
2448
|
+
(e: any) => e.name === lkmanagment.entId.toLocaleLowerCase()
|
|
2449
|
+
)[0];
|
|
2450
|
+
await this.lookupCheck(
|
|
2451
|
+
module.modules,
|
|
2452
|
+
field,
|
|
2453
|
+
input,
|
|
2454
|
+
output,
|
|
2455
|
+
extern,
|
|
2456
|
+
currentFieldChanged.function
|
|
2457
|
+
);
|
|
2458
|
+
} else if (currentFieldChanged.function === 'executeCommand') {
|
|
2459
|
+
// No cached data and fetch was skipped — still mark lookupoutput fields ReadOnly.
|
|
2460
|
+
for (let i = 1; i < output.length; i++) {
|
|
2461
|
+
if (this.gF(output[i])?.lookupoutput) {
|
|
2462
|
+
this.sS(
|
|
2463
|
+
output[i],
|
|
2464
|
+
this.gS(output[i]) !== this.fieldState.Hidden
|
|
2465
|
+
? this.fieldState.ReadOnly
|
|
2466
|
+
: this.fieldState.Hidden
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
this.cdr.detectChanges();
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
this.paddingFormattingRegEx(id);
|
|
2476
|
+
if (
|
|
2477
|
+
this.gF(id).tabindex == 0 &&
|
|
2478
|
+
this.model.rootModel &&
|
|
2479
|
+
currentFieldChanged.function === 'sendChanged' &&
|
|
2480
|
+
!this.viewList
|
|
2481
|
+
) {
|
|
2482
|
+
this.checkKey(id);
|
|
2483
|
+
}
|
|
2484
|
+
if (currentFieldChanged.function === 'sendChanged') {
|
|
2485
|
+
if (this.insertMode && this.gS(id) !== FieldState.Fatal) {
|
|
2486
|
+
if (!this.insertMode) {
|
|
2487
|
+
this.commandData('create');
|
|
2488
|
+
this.sendCommands();
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
const preBusinessState = this.gS(id);
|
|
2492
|
+
const preBusinessMsg = this.gF(id)?.title ?? '';
|
|
2493
|
+
this.fieldBusiness(id);
|
|
2494
|
+
// Preserve validation Error/Warning that dynamicView/calc may have
|
|
2495
|
+
// overridden back to None via refresh flags.
|
|
2496
|
+
if (
|
|
2497
|
+
(preBusinessState === FieldState.Error ||
|
|
2498
|
+
preBusinessState === FieldState.Warning) &&
|
|
2499
|
+
this.gS(id) === FieldState.None
|
|
2500
|
+
) {
|
|
2501
|
+
this.sS(id, preBusinessState, preBusinessMsg);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
/** Padding, formatting, regex */
|
|
2509
|
+
private paddingFormattingRegEx(id: string) {
|
|
2510
|
+
this.PadSpacing(id);
|
|
2511
|
+
const regExpRet = this.regExpression(id);
|
|
2512
|
+
if (!regExpRet) {
|
|
2513
|
+
this.sS(id, FieldState.Error, this.msg.get('app.regexpnotmatch'));
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
/** Computes the flat module index accounting for multi-dimensional PRG arrays. */
|
|
2518
|
+
private getIndex(): number {
|
|
2519
|
+
let index = this.lastKey;
|
|
2520
|
+
const entity = this.getDataEntity(this.model.name.toLocaleLowerCase());
|
|
2521
|
+
if (entity && this.model.prgDim > 1) {
|
|
2522
|
+
index = Math.max(
|
|
2523
|
+
0,
|
|
2524
|
+
entity.modules.indexOf(
|
|
2525
|
+
entity.modules.filter(e => e.prg[0] === this.getKeyArray()[0])[
|
|
2526
|
+
this.lastKey
|
|
2527
|
+
]
|
|
2528
|
+
)
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
return index;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
/**
|
|
2535
|
+
* Runs logic.calc for the current field, then applies any member refresh flags
|
|
2536
|
+
* (state/value/message) marked by the logic layer in the active module.
|
|
2537
|
+
*/
|
|
2538
|
+
private fieldBusiness(id: string, calc: boolean = true) {
|
|
2539
|
+
let index = this.getIndex();
|
|
2540
|
+
if (calc) {
|
|
2541
|
+
this.logic.calc(id, index, this.getKeyArray());
|
|
2542
|
+
}
|
|
2543
|
+
const entity = this.getDataEntity(this.model.name.toLocaleLowerCase());
|
|
2544
|
+
if (!entity?.modules?.length) return;
|
|
2545
|
+
const modulefiltered = this.getFilteredModules(entity);
|
|
2546
|
+
const currentModule = modulefiltered[this.lastKey];
|
|
2547
|
+
if (!currentModule) return;
|
|
2548
|
+
const membersToRefresh = currentModule.members.filter(m => m.refresh);
|
|
2549
|
+
if (membersToRefresh?.length > 0) {
|
|
2550
|
+
membersToRefresh.forEach(member => {
|
|
2551
|
+
this.sS(member.name, member.state);
|
|
2552
|
+
this.sV(member.name, member.value);
|
|
2553
|
+
if (member.message && member.message !== '') {
|
|
2554
|
+
this.gF(member.name).title = member.message;
|
|
2555
|
+
}
|
|
2556
|
+
member.refresh = false;
|
|
2557
|
+
this.video()?.nativeElement?.load();
|
|
2558
|
+
this.dataChanged(member);
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
this.dataChanged(currentModule.members.find(m => m.name === id)!);
|
|
2562
|
+
this.updateState();
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
/**
|
|
2566
|
+
* Returns true if the given member has unsaved changes (value or state differ from
|
|
2567
|
+
* initial values), updating `this.save` as a side-effect.
|
|
2568
|
+
*/
|
|
2569
|
+
private dataChanged(member: MemberData): boolean {
|
|
2570
|
+
if (this.loaded) {
|
|
2571
|
+
if (
|
|
2572
|
+
((member?.initialvalue !== null &&
|
|
2573
|
+
member?.value !== member?.initialvalue) ||
|
|
2574
|
+
member?.initialstate !== member?.state) &&
|
|
2575
|
+
member.type !== FieldType.Label &&
|
|
2576
|
+
member.type !== FieldType.Button &&
|
|
2577
|
+
member.type !== FieldType.Video &&
|
|
2578
|
+
member.type !== FieldType.Image &&
|
|
2579
|
+
member.type !== FieldType.Map &&
|
|
2580
|
+
member.type !== FieldType.Chart
|
|
2581
|
+
) {
|
|
2582
|
+
this.save = false;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
return !this.save;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
/**
|
|
2589
|
+
* Validates the current field value against the referenced lookup entity.
|
|
2590
|
+
* On match, propagates output field values; on mismatch, sets Error state.
|
|
2591
|
+
*/
|
|
2592
|
+
private async lookupCheck(
|
|
2593
|
+
lkdata: ModuleData[],
|
|
2594
|
+
field: Member,
|
|
2595
|
+
input: string[],
|
|
2596
|
+
output: string[],
|
|
2597
|
+
extern: string[],
|
|
2598
|
+
functionId: string
|
|
2599
|
+
) {
|
|
2600
|
+
for (let i = 1; i < input.length; i++) {
|
|
2601
|
+
if (
|
|
2602
|
+
this.gS(output[i]) !== FieldState.Forced ||
|
|
2603
|
+
this.gV(output[0]) === ''
|
|
2604
|
+
) {
|
|
2605
|
+
this.sV(output[i], '');
|
|
2606
|
+
} else {
|
|
2607
|
+
if (this.gV(output[i])) {
|
|
2608
|
+
output[i] = '';
|
|
2609
|
+
input[i] = '';
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
if (field.value !== '' && lkdata) {
|
|
2614
|
+
let elem = lkdata.find(item =>
|
|
2615
|
+
item.members?.find(
|
|
2616
|
+
e => e.name === input[0] && e.value === this.gV(field.id)
|
|
2617
|
+
)
|
|
2618
|
+
);
|
|
2619
|
+
if (!elem) {
|
|
2620
|
+
this.sS(field.id, FieldState.Error, this.msg.get('app.itemerror'));
|
|
2621
|
+
for (let i = 0; i < input.length; i++) {
|
|
2622
|
+
if (i > 0) {
|
|
2623
|
+
this.sS(
|
|
2624
|
+
output[i],
|
|
2625
|
+
this.gF(output[i])?.lookupoutput
|
|
2626
|
+
? FieldState.ReadOnly
|
|
2627
|
+
: this.gS(output[i]),
|
|
2628
|
+
''
|
|
2629
|
+
);
|
|
2630
|
+
const fld = this.entity?.members?.find(
|
|
2631
|
+
item => item.id === output[i]
|
|
2632
|
+
);
|
|
2633
|
+
if (fld?.lookup !== '') {
|
|
2634
|
+
this.fieldChanged({
|
|
2635
|
+
id: fld?.id ?? '',
|
|
2636
|
+
value: String(fld?.value ?? ''),
|
|
2637
|
+
function: 'lookupCheck',
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
} else {
|
|
2643
|
+
if (this.gS(field.id) === FieldState.Error) {
|
|
2644
|
+
this.sS(field.id, FieldState.None);
|
|
2645
|
+
}
|
|
2646
|
+
for (let i = 0; i < input.length; i++) {
|
|
2647
|
+
if (i === 0) {
|
|
2648
|
+
for (let x = 0; x < extern.length; x++) {
|
|
2649
|
+
const ext: string[] = extern[x].split(';');
|
|
2650
|
+
if (ext[0] !== '') {
|
|
2651
|
+
const extField = ext[1].substring(1).toString();
|
|
2652
|
+
if (!this.gF(extField).readonly) {
|
|
2653
|
+
this.sS(field.id, FieldState.None);
|
|
2654
|
+
}
|
|
2655
|
+
const fldValue = this.gV(extField);
|
|
2656
|
+
if (x < extern.length - 1) {
|
|
2657
|
+
lkdata = lkdata.filter(item =>
|
|
2658
|
+
item.members?.find(
|
|
2659
|
+
e => e.name === ext[0] && e.value === fldValue
|
|
2660
|
+
)
|
|
2661
|
+
);
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
elem = lkdata.find(item =>
|
|
2665
|
+
item.members?.find(
|
|
2666
|
+
e => e.name === ext[0] && e.value === fldValue
|
|
2667
|
+
)
|
|
2668
|
+
);
|
|
2669
|
+
if (
|
|
2670
|
+
!elem ||
|
|
2671
|
+
elem.members
|
|
2672
|
+
?.filter(e => e.name === input[0])[0]
|
|
2673
|
+
?.value?.toString() !== String(field.value ?? '')
|
|
2674
|
+
) {
|
|
2675
|
+
this.sS(
|
|
2676
|
+
field.id,
|
|
2677
|
+
FieldState.Error,
|
|
2678
|
+
this.msg.get('app.itemerror')
|
|
2679
|
+
);
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
} else {
|
|
2685
|
+
if (elem) {
|
|
2686
|
+
const member = elem.members?.find(e => e.name === input[i]);
|
|
2687
|
+
if (member?.value) {
|
|
2688
|
+
if (
|
|
2689
|
+
!this.gV(output[i]) ||
|
|
2690
|
+
(this.gV(output[i]) &&
|
|
2691
|
+
this.gS(output[i]) !== this.fieldState.Forced)
|
|
2692
|
+
) {
|
|
2693
|
+
this.sV(output[i], member.value);
|
|
2694
|
+
if (functionId !== 'noBusiness') {
|
|
2695
|
+
this.fieldChanged({
|
|
2696
|
+
id: output[i],
|
|
2697
|
+
value: String(member.value ?? ''),
|
|
2698
|
+
function: '',
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
this.sS(
|
|
2704
|
+
output[i],
|
|
2705
|
+
this.gF(output[i])?.lookupoutput
|
|
2706
|
+
? this.gS(output[i]) !== this.fieldState.Hidden
|
|
2707
|
+
? this.fieldState.ReadOnly
|
|
2708
|
+
: this.fieldState.Hidden
|
|
2709
|
+
: this.gS(output[i])
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
} else {
|
|
2716
|
+
for (let i = 0; i < input.length; i++) {
|
|
2717
|
+
if (i > 0) {
|
|
2718
|
+
// Output fields that serve as lookup outputs should always be ReadOnly:
|
|
2719
|
+
// the user cannot type them directly — they auto-fill from the lookup popup.
|
|
2720
|
+
if (this.gF(output[i])?.lookupoutput) {
|
|
2721
|
+
this.sS(
|
|
2722
|
+
output[i],
|
|
2723
|
+
this.gS(output[i]) !== this.fieldState.Hidden
|
|
2724
|
+
? this.fieldState.ReadOnly
|
|
2725
|
+
: this.fieldState.Hidden
|
|
2726
|
+
);
|
|
2727
|
+
}
|
|
2728
|
+
const fld = this.entity?.members?.find(item => item.id === output[i]);
|
|
2729
|
+
if (fld?.lookup !== '') {
|
|
2730
|
+
this.fieldChanged({
|
|
2731
|
+
id: fld?.id ?? '',
|
|
2732
|
+
value: String(fld?.value ?? ''),
|
|
2733
|
+
function: 'lookupCheck',
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
/** Schedules a select() call on the given field's DOM input element on the next tick. */
|
|
2742
|
+
private setFocus(id: string) {
|
|
2743
|
+
if (this.model && !this.model.cache) {
|
|
2744
|
+
if (id && id !== '') {
|
|
2745
|
+
const tm = setTimeout(() => {
|
|
2746
|
+
const el = this.gE(id);
|
|
2747
|
+
if (el) el.select();
|
|
2748
|
+
clearTimeout(tm);
|
|
2749
|
+
}, 100);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
/** Resolves cached entity data or fetches from the API; calls backData when ready. */
|
|
2755
|
+
private async getData(id: string) {
|
|
2756
|
+
await new Promise<void>(r =>
|
|
2757
|
+
requestAnimationFrame(() => requestAnimationFrame(() => r()))
|
|
2758
|
+
);
|
|
2759
|
+
if (!this.model.rootModel) {
|
|
2760
|
+
this.fieldsId =
|
|
2761
|
+
this.managment.appId +
|
|
2762
|
+
'.' +
|
|
2763
|
+
this.root.entities[0].name.toUpperCase() +
|
|
2764
|
+
'.' +
|
|
2765
|
+
this.managment.year;
|
|
2766
|
+
}
|
|
2767
|
+
const data = this.gsv.getData(this.fieldsId);
|
|
2768
|
+
if (data && !this.model.preview && this.model.masterDetail === 0) {
|
|
2769
|
+
this.insertMode = false;
|
|
2770
|
+
await this.backDataFromCache(data);
|
|
2771
|
+
this.loaded = true;
|
|
2772
|
+
this.model.loaded = true;
|
|
2773
|
+
} else {
|
|
2774
|
+
await this.performGetData();
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
/** Performs a GET /managment read request and populates the component via backData. */
|
|
2779
|
+
async performGetData(): Promise<void> {
|
|
2780
|
+
this.managment.fields = [];
|
|
2781
|
+
this.managment.functionId = 'read';
|
|
2782
|
+
this.msv.getData(this.managment).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
2783
|
+
next: async (data: TransportInterface) => {
|
|
2784
|
+
this.insertMode = false;
|
|
2785
|
+
await this.backData(data);
|
|
2786
|
+
this.loaded = true;
|
|
2787
|
+
this.model.loaded = true;
|
|
2788
|
+
},
|
|
2789
|
+
error: err => {
|
|
2790
|
+
console.error('getData HTTP error:', err);
|
|
2791
|
+
},
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
/** Broadcasts the current dialog state to the model shell (model.component). */
|
|
2796
|
+
private sendDialog() {
|
|
2797
|
+
this.gsv.setDialogField({
|
|
2798
|
+
showdialog: this.showdialog,
|
|
2799
|
+
titledialog: this.titledialog,
|
|
2800
|
+
textdialog: this.textdialog,
|
|
2801
|
+
showconfirm: this.showconfirm,
|
|
2802
|
+
dialogHeight: this.dialogHeight,
|
|
2803
|
+
dialogWidth: this.dialogWidth,
|
|
2804
|
+
dialogTop: this.dialogTop,
|
|
2805
|
+
dialogLeft: this.dialogLeft,
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
/** Broadcasts the current lookup panel state to the model shell (model.component). */
|
|
2810
|
+
private sendLookup() {
|
|
2811
|
+
this.gsv.setLookupField({
|
|
2812
|
+
showlookup: this.showlookup,
|
|
2813
|
+
titlelookup: this.titlelookup,
|
|
2814
|
+
textlookup: this.textlookup,
|
|
2815
|
+
lookupHeight: this.lookupHeight,
|
|
2816
|
+
lookupWidth: this.lookupWidth,
|
|
2817
|
+
lookupTop: this.lookupTop,
|
|
2818
|
+
lookupLeft: this.lookupLeft,
|
|
2819
|
+
lookup: this.lookup,
|
|
2820
|
+
lookupfilter: this.lookupfilter,
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
/** Broadcasts the current toolbar command states to ModelToolbarComponent. */
|
|
2825
|
+
private sendCommands() {
|
|
2826
|
+
if (this.entity?.viewlist) {
|
|
2827
|
+
// In list-view-only entities, disable all directional navigation.
|
|
2828
|
+
this.updateNavButtons(0);
|
|
2829
|
+
}
|
|
2830
|
+
this.gsv.setItemCommands([
|
|
2831
|
+
{ id: 'exit', state: false },
|
|
2832
|
+
{ id: 'insertmode', state: this.insertMode },
|
|
2833
|
+
{ id: 'plus', state: this.bplus },
|
|
2834
|
+
{ id: 'minus', state: this.bminus },
|
|
2835
|
+
{ id: 'first', state: this.bfirst },
|
|
2836
|
+
{ id: 'previous', state: this.bprevious },
|
|
2837
|
+
{ id: 'next', state: this.bnext },
|
|
2838
|
+
{ id: 'last', state: this.blast },
|
|
2839
|
+
{ id: 'detail', state: this.bdetail },
|
|
2840
|
+
{ id: 'intodetail', state: this.bintodetail },
|
|
2841
|
+
{ id: 'download', state: this.bdownload },
|
|
2842
|
+
{ id: 'upload', state: this.bupload },
|
|
2843
|
+
{ id: 'print', state: this.bprint },
|
|
2844
|
+
{ id: 'table', state: this.btable },
|
|
2845
|
+
{ id: 'save', state: this.save },
|
|
2846
|
+
{ id: 'refreshgrid', state: this.refreshgrid },
|
|
2847
|
+
]);
|
|
2848
|
+
const actionStates: [string, boolean][] = [
|
|
2849
|
+
['plus', this.bplus],
|
|
2850
|
+
['minus', this.bminus],
|
|
2851
|
+
['next', this.bnext],
|
|
2852
|
+
['previous', this.bprevious],
|
|
2853
|
+
['first', this.bfirst],
|
|
2854
|
+
['last', this.blast],
|
|
2855
|
+
['table', this.btable],
|
|
2856
|
+
['detail', this.bdetail],
|
|
2857
|
+
['download', this.bdownload],
|
|
2858
|
+
['upload', this.bupload],
|
|
2859
|
+
['save', this.save],
|
|
2860
|
+
];
|
|
2861
|
+
for (const [name, state] of actionStates) {
|
|
2862
|
+
if (this.gE('action_' + name)) {
|
|
2863
|
+
this.gF('action_' + name).enabled = !state;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
/** Resets all toolbar button states to their default (all disabled) values. */
|
|
2869
|
+
private resetCommands() {
|
|
2870
|
+
this.bplus = true;
|
|
2871
|
+
this.bminus = true;
|
|
2872
|
+
this.updateNavButtons(0);
|
|
2873
|
+
this.bdetail = true;
|
|
2874
|
+
this.bintodetail = true;
|
|
2875
|
+
this.bdownload = true;
|
|
2876
|
+
this.bupload = true;
|
|
2877
|
+
this.bprint = this.viewList;
|
|
2878
|
+
this.btable = true;
|
|
2879
|
+
this.save = true;
|
|
2880
|
+
this.refreshgrid = false;
|
|
2881
|
+
this.sendCommands();
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* Parses the server response (JSON + SVG), instantiates the BASELogic layer,
|
|
2886
|
+
* and triggers commandData('readed') to populate the first/last record.
|
|
2887
|
+
*/
|
|
2888
|
+
private async backDataFromCache(cached: EntitiesData) {
|
|
2889
|
+
this.data = cached;
|
|
2890
|
+
await this.initLogicAndReady();
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
private async backData(data: TransportInterface) {
|
|
2894
|
+
if (data.dataFile) {
|
|
2895
|
+
this.data = JSON.parse(data.dataFile as string);
|
|
2896
|
+
await this.initLogicAndReady();
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
private async initLogicAndReady() {
|
|
2901
|
+
this.logic = await this.config.logicFactory(
|
|
2902
|
+
this.gsv, this.msv, this.msg, this.managment.entId, this.data
|
|
2903
|
+
);
|
|
2904
|
+
this.lastKey = 0;
|
|
2905
|
+
await this.logic.init();
|
|
2906
|
+
await this.commandData('readed');
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
/** Left- or right-pads a string value with the given character up to the target size. */
|
|
2910
|
+
private padding(
|
|
2911
|
+
direction: PadType,
|
|
2912
|
+
value: string,
|
|
2913
|
+
char: string,
|
|
2914
|
+
size: number
|
|
2915
|
+
): string {
|
|
2916
|
+
let s = value.toString();
|
|
2917
|
+
if (char.charCodeAt(0) !== 0 && s !== '' && s !== '0') {
|
|
2918
|
+
while (s.length < size) {
|
|
2919
|
+
if (direction === PadType.Right) {
|
|
2920
|
+
s = s + char;
|
|
2921
|
+
} else {
|
|
2922
|
+
s = char + s;
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
return s;
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
/** Applies the field's match/replace regular expression to the DOM input and the Member value. */
|
|
2930
|
+
private regExpression(id: string): boolean {
|
|
2931
|
+
const element = this.gE(id);
|
|
2932
|
+
if (element !== null) {
|
|
2933
|
+
if (
|
|
2934
|
+
element.getAttribute(FieldAttr.get('match')!) !== null &&
|
|
2935
|
+
element.getAttribute(FieldAttr.get('replace')!) !== null
|
|
2936
|
+
) {
|
|
2937
|
+
const rem = element.getAttribute(FieldAttr.get('match')!)!;
|
|
2938
|
+
const rep = element.getAttribute(FieldAttr.get('replace')!)!;
|
|
2939
|
+
if (rem !== '' && rep !== '') {
|
|
2940
|
+
const regExp = new RegExp(rem);
|
|
2941
|
+
this.gE(id).value = this.gV(id)
|
|
2942
|
+
.toString()
|
|
2943
|
+
.toUpperCase()
|
|
2944
|
+
.replace(regExp, rep);
|
|
2945
|
+
this.gF(id).value = this.gE(id).value;
|
|
2946
|
+
const fld = this.gE(id).value;
|
|
2947
|
+
const ret = this.gE(id).value.match(rem.toUpperCase());
|
|
2948
|
+
return ret || fld.length === 0 ? true : false;
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
/** Pads or trims a field value based on padleftchar / padrightchar / autoformatter attributes. */
|
|
2956
|
+
private PadSpacing(id: string) {
|
|
2957
|
+
let value: string = this.gV(id);
|
|
2958
|
+
if (value?.toString().length > 0) {
|
|
2959
|
+
if (this.gE(id) && this.gF(id).padleftchar !== '') {
|
|
2960
|
+
this.gE(id).value = this.padding(
|
|
2961
|
+
PadType.Left,
|
|
2962
|
+
this.gV(id),
|
|
2963
|
+
this.gF(id).padleftchar,
|
|
2964
|
+
this.gF(id).length ?? this.gF(id).precision ?? 0
|
|
2965
|
+
);
|
|
2966
|
+
this.gF(id).value = this.gE(id)!.value;
|
|
2967
|
+
}
|
|
2968
|
+
if (this.gE(id) && this.gF(id).padrightchar !== '') {
|
|
2969
|
+
this.gE(id)!.value = this.padding(
|
|
2970
|
+
PadType.Right,
|
|
2971
|
+
this.gV(id),
|
|
2972
|
+
this.gF(id).padrightchar,
|
|
2973
|
+
this.gF(id).length ?? this.gF(id).precision ?? 0
|
|
2974
|
+
);
|
|
2975
|
+
this.gF(id).value = this.gE(id)!.value;
|
|
2976
|
+
}
|
|
2977
|
+
if (this.gF(id).autoformatter && this.gF(id).match === '') {
|
|
2978
|
+
const spacing = ' ';
|
|
2979
|
+
let ret = '';
|
|
2980
|
+
if (this.gT(id) === FieldType.Date) {
|
|
2981
|
+
this.gF(id).value = String(this.gF(id).value ?? '').replace(/\//g, ' ');
|
|
2982
|
+
}
|
|
2983
|
+
const strVal = String(this.gF(id).value ?? '');
|
|
2984
|
+
for (let i = 0; i < strVal.length; i++) {
|
|
2985
|
+
if (i > 0) {
|
|
2986
|
+
ret += spacing.repeat(this.gF(id).spacing ?? 1);
|
|
2987
|
+
}
|
|
2988
|
+
ret += strVal[i];
|
|
2989
|
+
}
|
|
2990
|
+
this.gE(id).value = ret;
|
|
2991
|
+
this.gF(id).value = this.gE(id).value;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
/** Initialises the component from the given ItemInterface (model config). */
|
|
2997
|
+
private initItem(model: ItemInterface) {
|
|
2998
|
+
this.model = model;
|
|
2999
|
+
if (model.svg !== null && model.svg !== '') {
|
|
3000
|
+
this.svg = this.gsv.sanitizeurl(
|
|
3001
|
+
'assets/svg/' +
|
|
3002
|
+
this.gsv.getProject().year +
|
|
3003
|
+
'/' +
|
|
3004
|
+
this.gsv.getProject().project +
|
|
3005
|
+
'/' +
|
|
3006
|
+
model.svg +
|
|
3007
|
+
'.svg'
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
this.managment.appId = this.gsv.getProject().project;
|
|
3011
|
+
this.managment.year = this.gsv.getYear();
|
|
3012
|
+
this.managment.entId = this.model.name;
|
|
3013
|
+
this.managment.keyId = this.gsv.getKeyId();
|
|
3014
|
+
this.managment.name = model.title;
|
|
3015
|
+
for (let i = 0; i < model.prgDim; i++) {
|
|
3016
|
+
if (this.managment.keys !== '') {
|
|
3017
|
+
this.managment.keys += ';';
|
|
3018
|
+
}
|
|
3019
|
+
if (i === 0 && this.gsv.getCurrentLinkItem().groupName !== '') {
|
|
3020
|
+
this.managment.keys += 'prg=' + this.gsv.getCurrentLinkItem().prg[0];
|
|
3021
|
+
} else {
|
|
3022
|
+
this.managment.keys += 'prg=1';
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
this.fieldsId =
|
|
3027
|
+
this.managment.appId +
|
|
3028
|
+
'.' +
|
|
3029
|
+
this.managment.entId +
|
|
3030
|
+
'.' +
|
|
3031
|
+
this.managment.year;
|
|
3032
|
+
|
|
3033
|
+
this.insertMode = true;
|
|
3034
|
+
|
|
3035
|
+
this.root = this.gsv.getSchemaJson(
|
|
3036
|
+
this.managment.appId + '.' + this.managment.year
|
|
3037
|
+
);
|
|
3038
|
+
|
|
3039
|
+
this.entity = this.root.entities.find(
|
|
3040
|
+
e => e.name == this.managment.entId.toLocaleLowerCase()
|
|
3041
|
+
)!;
|
|
3042
|
+
this._memberMap = null;
|
|
3043
|
+
|
|
3044
|
+
this.setViewList(this.entity.viewlist);
|
|
3045
|
+
|
|
3046
|
+
if (this.gsv.getCurrentKeys().length > 0) {
|
|
3047
|
+
this.managment.keys = this.gsv.getCurrentKeys();
|
|
3048
|
+
this.gsv.setCurrentKeys('');
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
this.closing = false;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
/**
|
|
3055
|
+
* Handles the main toolbar command dispatch (exit, save, plus, minus, table, print,
|
|
3056
|
+
* go:<keys>, etc.) and updates entity state after each operation.
|
|
3057
|
+
*/
|
|
3058
|
+
public async sendCommand(command: ItemCommandInterface) {
|
|
3059
|
+
if (command.id === 'exit') {
|
|
3060
|
+
// Check for a saved intodetail state BEFORE navigating, so we can
|
|
3061
|
+
// decide the correct destination and avoid a spurious intermediate navigation.
|
|
3062
|
+
let restoredManagment: ManagmentInterface | null = null;
|
|
3063
|
+
let managmentKey: string | null = null;
|
|
3064
|
+
|
|
3065
|
+
if (localStorage.length > 0) {
|
|
3066
|
+
const allKeys: string[] = [];
|
|
3067
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
3068
|
+
const key = localStorage.key(i);
|
|
3069
|
+
if (key && /^\d+\./.test(key)) {
|
|
3070
|
+
allKeys.push(key);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
// Sort descending by numeric prefix so [0] is the most-recently saved
|
|
3074
|
+
// intodetail entry (LIFO order: last pushed = first to pop on exit).
|
|
3075
|
+
allKeys.sort((a, b) => parseInt(b, 10) - parseInt(a, 10));
|
|
3076
|
+
managmentKey = allKeys[0] ?? null;
|
|
3077
|
+
|
|
3078
|
+
if (managmentKey && localStorage.getItem(managmentKey) !== null) {
|
|
3079
|
+
const storedValue = localStorage.getItem(managmentKey);
|
|
3080
|
+
restoredManagment = JSON.parse(storedValue!);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
await this.close();
|
|
3085
|
+
|
|
3086
|
+
if (restoredManagment !== null && restoredManagment.appId) {
|
|
3087
|
+
// --- Back navigation: return to the component that launched intodetail ---
|
|
3088
|
+
localStorage.removeItem(managmentKey!);
|
|
3089
|
+
|
|
3090
|
+
if (restoredManagment?.keyId?.length === 36) {
|
|
3091
|
+
localStorage.setItem('search', restoredManagment?.keyId);
|
|
3092
|
+
}
|
|
3093
|
+
const originKey =
|
|
3094
|
+
restoredManagment?.appId +
|
|
3095
|
+
'.' +
|
|
3096
|
+
restoredManagment?.year +
|
|
3097
|
+
'.' +
|
|
3098
|
+
restoredManagment?.entId +
|
|
3099
|
+
'.' +
|
|
3100
|
+
restoredManagment.currentFieldId +
|
|
3101
|
+
'.' +
|
|
3102
|
+
restoredManagment.keys;
|
|
3103
|
+
localStorage.setItem('origin', originKey);
|
|
3104
|
+
|
|
3105
|
+
localStorage.setItem(
|
|
3106
|
+
'targetModel',
|
|
3107
|
+
restoredManagment?.entId?.toUpperCase()
|
|
3108
|
+
);
|
|
3109
|
+
|
|
3110
|
+
// Await navigation so that NavigationComponent is fully mounted and
|
|
3111
|
+
// subscribed to projectChanged before we fire activateProject.
|
|
3112
|
+
// This mirrors the same pattern used in handleIntoDetail (forward direction).
|
|
3113
|
+
await this.gsv.navigate('/home');
|
|
3114
|
+
|
|
3115
|
+
const projectName = this.gsv
|
|
3116
|
+
.getProjects()
|
|
3117
|
+
[
|
|
3118
|
+
this.gsv.getYear()
|
|
3119
|
+
]?.find(e => e.project === restoredManagment?.appId?.toUpperCase());
|
|
3120
|
+
|
|
3121
|
+
if (projectName) {
|
|
3122
|
+
this.gsv.activateProject(projectName);
|
|
3123
|
+
} else {
|
|
3124
|
+
console.error(
|
|
3125
|
+
'Project not found for appId:',
|
|
3126
|
+
restoredManagment?.appId
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
} else {
|
|
3130
|
+
// --- Normal exit: no saved intodetail state ---
|
|
3131
|
+
if (
|
|
3132
|
+
this.gsv.getProject().type === 'D' ||
|
|
3133
|
+
this.gsv.getProject().type === 'G'
|
|
3134
|
+
) {
|
|
3135
|
+
this.gsv.setActiveLink('/home/modelsearch');
|
|
3136
|
+
this.gsv.navigate('/home/modelsearch');
|
|
3137
|
+
} else {
|
|
3138
|
+
this.gsv.setActiveLink('/home/dashboard');
|
|
3139
|
+
this.gsv.navigate('/home/dashboard');
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
return;
|
|
3143
|
+
} else if (command.id === 'print') {
|
|
3144
|
+
if (!this.viewList) {
|
|
3145
|
+
this.logic.function(
|
|
3146
|
+
'save',
|
|
3147
|
+
this.lastKey,
|
|
3148
|
+
this.getKeyArray(),
|
|
3149
|
+
this.currentFieldId
|
|
3150
|
+
);
|
|
3151
|
+
this.commandData('print');
|
|
3152
|
+
return;
|
|
3153
|
+
} else {
|
|
3154
|
+
this.sendCommand({ id: 'print', state: false });
|
|
3155
|
+
}
|
|
3156
|
+
} else if (command.id === 'table') {
|
|
3157
|
+
if (this.gS(this.currentFieldId) !== FieldState.Fatal) {
|
|
3158
|
+
const json = this.getJsonData();
|
|
3159
|
+
await this.throwSave(json).then(async e => {
|
|
3160
|
+
await this.commandData(command.id).then(e => {
|
|
3161
|
+
this.resetCommands();
|
|
3162
|
+
this.bplus = !this.model.allowAdd;
|
|
3163
|
+
this.bprint = false;
|
|
3164
|
+
this.refreshData(true);
|
|
3165
|
+
});
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
} else if (command.id === 'save') {
|
|
3169
|
+
this.logic.function(
|
|
3170
|
+
command.id,
|
|
3171
|
+
this.lastKey,
|
|
3172
|
+
this.getKeyArray(),
|
|
3173
|
+
this.currentFieldId
|
|
3174
|
+
);
|
|
3175
|
+
const json = this.getJsonData();
|
|
3176
|
+
await this.throwSave(json).then(async e => {
|
|
3177
|
+
await this.commandData('readed');
|
|
3178
|
+
});
|
|
3179
|
+
} else if (command.id === 'minus') {
|
|
3180
|
+
this.checkConstraint().then(ret => {
|
|
3181
|
+
if (ret) {
|
|
3182
|
+
this.logic.function(
|
|
3183
|
+
command.id,
|
|
3184
|
+
this.lastKey,
|
|
3185
|
+
this.getKeyArray(),
|
|
3186
|
+
this.currentFieldId
|
|
3187
|
+
);
|
|
3188
|
+
this.commandData(command.id);
|
|
3189
|
+
} else {
|
|
3190
|
+
this.showMessage(this.msg.get('app.unabletodelete'));
|
|
3191
|
+
this.setFocus(this.currentFieldId);
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
} else if (command.id === 'detail') {
|
|
3195
|
+
this.commandsResult({});
|
|
3196
|
+
} else if (command.id === 'intodetail') {
|
|
3197
|
+
// Defer heavy operations to avoid blocking main thread
|
|
3198
|
+
Promise.resolve()
|
|
3199
|
+
.then(() => this.handleIntoDetail())
|
|
3200
|
+
.catch(error => {
|
|
3201
|
+
console.error('Error in intodetail command:', error);
|
|
3202
|
+
this.showMessage("Errore durante l'accesso al dettaglio");
|
|
3203
|
+
});
|
|
3204
|
+
return;
|
|
3205
|
+
} else {
|
|
3206
|
+
if (command.id.startsWith('go:')) {
|
|
3207
|
+
this.managment.keys = '';
|
|
3208
|
+
const keys: string[] = command.id.substring(3).split(';');
|
|
3209
|
+
// tslint:disable-next-line:prefer-for-of
|
|
3210
|
+
for (let i = 0; i < keys.length; i++) {
|
|
3211
|
+
if (this.managment.keys !== '') {
|
|
3212
|
+
this.managment.keys += ';';
|
|
3213
|
+
}
|
|
3214
|
+
this.managment.keys += 'prg=' + keys[i];
|
|
3215
|
+
}
|
|
3216
|
+
command.id = 'table';
|
|
3217
|
+
this.setLastKey(this.getLastKey());
|
|
3218
|
+
}
|
|
3219
|
+
if (!this.insertMode) {
|
|
3220
|
+
if (command.id === 'table') {
|
|
3221
|
+
this.checkKey(this.model.focusOnInsert);
|
|
3222
|
+
}
|
|
3223
|
+
await this.commandData(command.id);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
if (command.id === 'plus') {
|
|
3227
|
+
this.gsv.setExtendedDescription('');
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
/** Toggles the viewList flag and updates related navigation button states. */
|
|
3232
|
+
private setViewList(state: boolean) {
|
|
3233
|
+
this.viewList = state;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
/**
|
|
3237
|
+
* Handles the "intodetail" toolbar action: persists the current managment context
|
|
3238
|
+
* to localStorage, navigates to the home route, and activates the target project.
|
|
3239
|
+
*/
|
|
3240
|
+
private async handleIntoDetail() {
|
|
3241
|
+
const progressState =
|
|
3242
|
+
this.managment.year +
|
|
3243
|
+
'.' +
|
|
3244
|
+
this.managment.appId +
|
|
3245
|
+
'.' +
|
|
3246
|
+
this.managment.entId +
|
|
3247
|
+
'.' +
|
|
3248
|
+
this.model.title;
|
|
3249
|
+
|
|
3250
|
+
if (localStorage.getItem(progressState) !== null) {
|
|
3251
|
+
localStorage.removeItem(progressState);
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
this.managment.currentFieldId = this.currentFieldId;
|
|
3255
|
+
const fieldLookup = this.gF(this.currentFieldId).lookup;
|
|
3256
|
+
|
|
3257
|
+
if (!fieldLookup || !fieldLookup.includes('#')) {
|
|
3258
|
+
console.error('Invalid fieldLookup format:', fieldLookup);
|
|
3259
|
+
this.showMessage('Errore: configurazione campo non valida');
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
const parts = fieldLookup.split('#');
|
|
3264
|
+
|
|
3265
|
+
localStorage.setItem(
|
|
3266
|
+
localStorage.length + '.' + progressState,
|
|
3267
|
+
JSON.stringify(this.managment)
|
|
3268
|
+
);
|
|
3269
|
+
|
|
3270
|
+
const originKey =
|
|
3271
|
+
parts[0].toUpperCase() +
|
|
3272
|
+
'.' +
|
|
3273
|
+
parts[1].toUpperCase() +
|
|
3274
|
+
'.' +
|
|
3275
|
+
parts[2].toUpperCase();
|
|
3276
|
+
|
|
3277
|
+
localStorage.setItem('origin', originKey);
|
|
3278
|
+
|
|
3279
|
+
// Save target model name for navigation component to load it after project activation
|
|
3280
|
+
const modelName = parts[2].toUpperCase();
|
|
3281
|
+
localStorage.setItem('targetModel', modelName);
|
|
3282
|
+
|
|
3283
|
+
await this.gsv.navigate('/home');
|
|
3284
|
+
|
|
3285
|
+
const projectYear = this.gsv.getYear();
|
|
3286
|
+
const projects = this.gsv.getProjects()[projectYear];
|
|
3287
|
+
const projectName = parts[0].toUpperCase();
|
|
3288
|
+
|
|
3289
|
+
const project = projects?.find(e => e.project === projectName);
|
|
3290
|
+
|
|
3291
|
+
if (!project) {
|
|
3292
|
+
console.error('Project not found:', projectName);
|
|
3293
|
+
console.error('Available projects:', projects);
|
|
3294
|
+
this.showMessage('Errore: progetto non trovato');
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
this.gsv.activateProject(project);
|
|
3299
|
+
this.resetCommands();
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
/** Iterates all project items and recomputes each entity's aggregated Error/Warning/Valid state. */
|
|
3303
|
+
private updateState() {
|
|
3304
|
+
if (this.data) {
|
|
3305
|
+
const projectId =
|
|
3306
|
+
this.gsv.getProject().project + '.' + this.gsv.getYear();
|
|
3307
|
+
this.gsv.getProjectItems(projectId).forEach(item => {
|
|
3308
|
+
// Only update state for entities owned by this component's data.
|
|
3309
|
+
// Other components manage their own entities; overwriting their states
|
|
3310
|
+
// here would reset Error/Warning badges set by a sibling component.
|
|
3311
|
+
const entityName = item.name.toLocaleLowerCase();
|
|
3312
|
+
if (this.data.entities.find(e => e.name === entityName)) {
|
|
3313
|
+
item.state = this.updateEntityState(entityName);
|
|
3314
|
+
}
|
|
3315
|
+
});
|
|
3316
|
+
this.gsv.emitItemState();
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
/** Returns the aggregated ItemState (Error / Warning / Valid / Empty) for the given entity name. */
|
|
3321
|
+
updateEntityState(name: string): ItemState {
|
|
3322
|
+
const p1 = this.data.entities.find(m => m.name === name);
|
|
3323
|
+
if (p1) {
|
|
3324
|
+
let state = p1.modules.filter(e =>
|
|
3325
|
+
e.members?.find(m => m.state === ItemState.Error)
|
|
3326
|
+
);
|
|
3327
|
+
if (state.length > 0) {
|
|
3328
|
+
return ItemState.Error;
|
|
3329
|
+
} else {
|
|
3330
|
+
state = p1.modules.filter(e =>
|
|
3331
|
+
e.members?.find(m => m.state === ItemState.Warning)
|
|
3332
|
+
);
|
|
3333
|
+
if (state.length > 0) {
|
|
3334
|
+
return ItemState.Warning;
|
|
3335
|
+
} else {
|
|
3336
|
+
return ItemState.Valid;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return ItemState.Empty;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
/** Handles a button-type field click: delegates to sendCommand or fires a logic click_ function. */
|
|
3344
|
+
public buttonClick(field: string, action: string) {
|
|
3345
|
+
this.setFocus(this.currentFieldId);
|
|
3346
|
+
if (action !== LabelAction.Field.toLocaleString()) {
|
|
3347
|
+
if (this.gE('action_' + action) && this.gF('action_' + action).enabled) {
|
|
3348
|
+
this.sendCommand({ id: action.toLocaleLowerCase(), state: true });
|
|
3349
|
+
}
|
|
3350
|
+
} else {
|
|
3351
|
+
this.logic.function(
|
|
3352
|
+
'click_' + field,
|
|
3353
|
+
this.lastKey,
|
|
3354
|
+
this.getKeyArray(),
|
|
3355
|
+
this.currentFieldId
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/** TrackBy function for the entity member field loop. Returns the field name or index. */
|
|
3361
|
+
public trackByField(index: number, field: Member): string {
|
|
3362
|
+
return field.name || index.toString();
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
/** Returns the ngClass map for standard input/select fields (Combo, Date, Number, Text, TextualCheck). */
|
|
3366
|
+
tooltipClass(field: Member): string {
|
|
3367
|
+
switch (field?.state) {
|
|
3368
|
+
case this.fieldState.Error:
|
|
3369
|
+
return 'tooltip-error';
|
|
3370
|
+
case this.fieldState.Fatal:
|
|
3371
|
+
return 'tooltip-fatal';
|
|
3372
|
+
case this.fieldState.Warning:
|
|
3373
|
+
return 'tooltip-warning';
|
|
3374
|
+
default:
|
|
3375
|
+
return '';
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
fieldClasses(field: Member): Record<string, boolean> {
|
|
3380
|
+
const hasSvg = this.entity.svg.length > 0;
|
|
3381
|
+
return {
|
|
3382
|
+
field: !hasSvg,
|
|
3383
|
+
'field-mod': hasSvg,
|
|
3384
|
+
'text-right': field.alignment === 'right',
|
|
3385
|
+
'text-left': field.alignment === 'left',
|
|
3386
|
+
'text-center': field.alignment === 'center',
|
|
3387
|
+
'has-error': field?.state === this.fieldState.Error,
|
|
3388
|
+
'has-fatal': field?.state === this.fieldState.Fatal,
|
|
3389
|
+
'has-warning': field?.state === this.fieldState.Warning,
|
|
3390
|
+
'has-readonly':
|
|
3391
|
+
((field?.state === this.fieldState.ReadOnly || !field.enabled) &&
|
|
3392
|
+
field?.state !== this.fieldState.Error &&
|
|
3393
|
+
field?.state !== this.fieldState.Warning) ||
|
|
3394
|
+
this._readonlySet.has(field.id),
|
|
3395
|
+
'has-detail': field?.state === this.fieldState.Detail,
|
|
3396
|
+
'has-forced': field?.state === this.fieldState.Forced,
|
|
3397
|
+
'has-hidden': field?.state === this.fieldState.Hidden,
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
/** Returns the ngClass map for Label fields. */
|
|
3402
|
+
labelClasses(field: Member): Record<string, boolean> {
|
|
3403
|
+
return {
|
|
3404
|
+
label: this.entity.svg.length > 0,
|
|
3405
|
+
'text-right': field.alignment === 'right',
|
|
3406
|
+
'text-left': field.alignment === 'left',
|
|
3407
|
+
'text-center': field.alignment === 'center',
|
|
3408
|
+
'has-error': field?.state === this.fieldState.Error,
|
|
3409
|
+
'has-fatal': field?.state === this.fieldState.Fatal,
|
|
3410
|
+
'has-warning': field?.state === this.fieldState.Warning,
|
|
3411
|
+
'has-readonly':
|
|
3412
|
+
((field?.state === this.fieldState.ReadOnly || !field.enabled) &&
|
|
3413
|
+
field?.state !== this.fieldState.Error &&
|
|
3414
|
+
field?.state !== this.fieldState.Warning) ||
|
|
3415
|
+
this._readonlySet.has(field.id),
|
|
3416
|
+
'has-detail': field?.state === this.fieldState.Detail,
|
|
3417
|
+
'has-forced': field?.state === this.fieldState.Forced,
|
|
3418
|
+
'has-hidden': field?.state === this.fieldState.Hidden,
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
/** Returns the ngClass map for Button fields. */
|
|
3423
|
+
buttonClasses(field: Member): Record<string, boolean> {
|
|
3424
|
+
return {
|
|
3425
|
+
label: this.entity.svg.length > 0,
|
|
3426
|
+
btn: true,
|
|
3427
|
+
'bg-primary': field.id === 'action_exit',
|
|
3428
|
+
'bg-secondary': field.id !== 'action_exit',
|
|
3429
|
+
'bg-success': field.id === 'action_save',
|
|
3430
|
+
'text-white': true,
|
|
3431
|
+
'text-right': field.alignment === 'right',
|
|
3432
|
+
'text-left': field.alignment === 'left',
|
|
3433
|
+
'text-center': field.alignment === 'center',
|
|
3434
|
+
'has-error': field?.state === this.fieldState.Error,
|
|
3435
|
+
'has-fatal': field?.state === this.fieldState.Fatal,
|
|
3436
|
+
'has-warning': field?.state === this.fieldState.Warning,
|
|
3437
|
+
'has-readonly':
|
|
3438
|
+
((field?.state === this.fieldState.ReadOnly || !field.enabled) &&
|
|
3439
|
+
field?.state !== this.fieldState.Error &&
|
|
3440
|
+
field?.state !== this.fieldState.Warning) ||
|
|
3441
|
+
this._readonlySet.has(field.id),
|
|
3442
|
+
'has-detail': field?.state === this.fieldState.Detail,
|
|
3443
|
+
'has-forced': field?.state === this.fieldState.Forced,
|
|
3444
|
+
'has-hidden': field?.state === this.fieldState.Hidden,
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
/** TrackBy function for combo option lists. Returns the item string or its index. */
|
|
3449
|
+
public trackByCombo(index: number, item: string): string {
|
|
3450
|
+
return item || index.toString();
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
/** Parses a CSS pixel width string and returns the numeric value. */
|
|
3454
|
+
public parseWidth(width: string): number {
|
|
3455
|
+
return parseInt(width.replace('px', ''), 10);
|
|
3456
|
+
}
|
|
3457
|
+
}
|