beca-ui 2.1.6 → 2.1.7

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.
Files changed (28) hide show
  1. package/dist/DynamicWorkflowScript.js +1968 -363
  2. package/dist/beca-ui.js +54541 -48694
  3. package/dist/components/DebounceSelect/DebounceSelect.d.ts +9 -0
  4. package/dist/components/DebounceSelect/index.d.ts +1 -0
  5. package/dist/components/DynamicForm/AttachIcon.d.ts +6 -0
  6. package/dist/components/DynamicForm/AvatarItem.d.ts +6 -0
  7. package/dist/components/DynamicForm/DynamicForm.d.ts +4 -0
  8. package/dist/components/DynamicForm/DynamicFormInfo.d.ts +13 -0
  9. package/dist/components/DynamicForm/DynamicFormSidebar.d.ts +21 -0
  10. package/dist/components/DynamicForm/DynamicWorkflowForm.d.ts +19 -0
  11. package/dist/components/DynamicForm/PropertyFileUpload.d.ts +27 -0
  12. package/dist/components/DynamicForm/PropertyImageList.d.ts +8 -0
  13. package/dist/components/DynamicForm/WorkflowForm.d.ts +30 -0
  14. package/dist/components/DynamicForm/context.d.ts +30 -0
  15. package/dist/components/DynamicForm/index.d.ts +13 -0
  16. package/dist/components/DynamicForm/layout.d.ts +9 -0
  17. package/dist/components/DynamicForm/types.d.ts +241 -0
  18. package/dist/components/DynamicForm/utils.d.ts +59 -0
  19. package/dist/components/FormEditor/FormEditor.d.ts +25 -0
  20. package/dist/components/FormEditor/index.d.ts +2 -0
  21. package/dist/components/QrBarCodeScanField/QrBarCodeScanField.d.ts +24 -0
  22. package/dist/components/QrBarCodeScanField/index.d.ts +1 -0
  23. package/dist/components/index.d.ts +4 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/main.css +1 -1
  26. package/package.json +13 -30
  27. package/README.md +0 -115
  28. package/dist/styles/components/layout/test.d.ts +0 -0
@@ -1,13 +1,25 @@
1
- const docTien = new DocTienBangChu();
2
- const DEFAULT_MONEY_UNIT = 'đồng';
1
+ let docTien = typeof DocTienBangChu !== "undefined" ? new DocTienBangChu() : null;
2
+ function getDocTienInstance() {
3
+ if (!docTien && typeof DocTienBangChu !== "undefined") {
4
+ docTien = new DocTienBangChu();
5
+ }
6
+ return docTien;
7
+ }
8
+
9
+ const DEFAULT_MONEY_UNIT = {
10
+ vi: "đồng",
11
+ en: "dong",
12
+ };
3
13
 
4
14
  var isDetail = false;
5
15
  var isMasterDetail = false;
6
16
  var colName = undefined;
7
17
  var mdCellId = undefined;
18
+ var userWorkflowId = undefined;
8
19
  var masterDetailId = undefined;
9
20
 
10
- function capitalize(word = '') {
21
+ let previousName = null;
22
+ function capitalize(word = "") {
11
23
  word = word.toLowerCase();
12
24
  return word.charAt(0).toUpperCase() + word.slice(1);
13
25
  }
@@ -16,204 +28,733 @@ function runScript(scriptString) {
16
28
  eval(scriptString);
17
29
  }
18
30
 
31
+ function unaccent(str) {
32
+ if (!str) return "";
33
+ return str
34
+ .normalize("NFD")
35
+ .replace(/[\u0300-\u036f]/g, "")
36
+ .replace(/[\u02C6\u0306\u031B]/g, "")
37
+ .replace(/đ/g, "d")
38
+ .replace(/Đ/g, "D")
39
+ .replace(
40
+ /!|@|%|\^|\*|\(|\)|\+|\=|\<|\>|\?|\/|,|\.|\:|\;|\'|\"|\&|\#|\[|\]|~|\$|_|`|-|{|}|\||\\/g,
41
+ " ",
42
+ )
43
+ .replace(/\s+/g, " ")
44
+ .trim();
45
+ }
46
+
47
+ function clearText(text) {
48
+ let clearedText;
49
+ if (text?.length > 0) {
50
+ clearedText = unaccent(text.trim().toLowerCase());
51
+ }
52
+ return clearedText;
53
+ }
54
+
19
55
  function isNumeric(n) {
20
56
  return !isNaN(parseFloat(n)) && isFinite(n);
21
57
  }
22
58
 
23
59
  function getMoneyUnitName(unit) {
24
60
  switch (unit) {
25
- case 'VND':
26
- return 'đồng';
27
- case 'USD':
28
- return 'đô la';
29
- case 'EUR':
30
- return 'ơ-rô';
31
- case 'JPY':
32
- return 'yên';
33
- case 'SGD':
34
- return 'đô la Singapore';
35
- case 'KRW':
36
- return 'won';
61
+ case "VND":
62
+ return "đồng";
63
+ case "USD":
64
+ return "đô la";
65
+ case "EUR":
66
+ return "ơ-rô";
67
+ case "JPY":
68
+ return "yên";
69
+ case "SGD":
70
+ return "đô la Singapore";
71
+ case "KRW":
72
+ return "won";
37
73
  default:
38
- return '';
74
+ return "";
39
75
  }
40
76
  }
41
77
 
42
- function docSoTien(number, unit = DEFAULT_MONEY_UNIT, locale = 'vi') {
43
- let result = '';
44
- const numbers = number?.toString().split('.');
78
+ function docSoTien(number, unit, locale = "vi") {
79
+ if (!unit) {
80
+ unit = DEFAULT_MONEY_UNIT[locale];
81
+ }
82
+ let result = "";
83
+ const numbers = number?.toString().split(".");
45
84
 
46
- if (locale === 'en') {
85
+ if (locale === "en") {
47
86
  const numberText1 = numberToTextEN(Number(numbers[0]));
48
87
  result = numberText1.trim();
49
88
  } else {
50
- const numberText1 = docTien.doc(numbers[0]);
89
+ const reader = getDocTienInstance();
90
+ const numberText1 = reader ? reader.doc(numbers[0]) : "";
51
91
  result = numberText1.trim();
52
92
  }
53
93
 
54
94
  if (numbers.length === 2) {
55
- if (locale === 'en') {
95
+ if (locale === "en") {
56
96
  const numberText2 = numberToTextEN(numbers[1]);
57
97
  result += ` point ${numberText2.toLowerCase()}`;
58
98
  } else {
59
- const numberText2 = docTien.doc(numbers[1]);
99
+ const reader = getDocTienInstance();
100
+ const numberText2 = reader ? reader.doc(numbers[1]) : "";
60
101
  result += ` phẩy ${numberText2.toLowerCase()}`;
61
102
  }
62
103
  }
63
104
  result = capitalize(result);
64
- if (unit?.trim() !== '') {
105
+ if (unit?.trim() !== "") {
65
106
  result += ` ${unit}`;
66
107
  }
67
108
 
68
109
  return result;
69
110
  }
111
+ function getShortName(
112
+ name,
113
+ content,
114
+ numberOfWords,
115
+ numberOfChars = 1,
116
+ reverse = false,
117
+ ) {
118
+ var rawValue = getValue(name);
119
+
120
+ var noBrackets = rawValue.replace(/\s*[\(\[\{].*?[\)\]\}]\s*/g, " ");
121
+
122
+ var cleanValue = noBrackets.replace(
123
+ /[^\w\sàáạảãâầấậẩẫăằắặẳẵèéẹẻẽêềếệểễìíịỉĩòóọỏõôồốộổỗơờớợởỡùúụủũưừứựửữỳýỵỷỹđ]/gi,
124
+ "",
125
+ );
126
+
127
+ var valueName = cleanValue.trim();
128
+ var words = valueName.split(/\s+/).filter(Boolean);
129
+ var selectedWords = [];
130
+ //get selected words
131
+ if (reverse) {
132
+ var start = Math.max(0, words.length - numberOfWords);
133
+ selectedWords = words.slice(start, words.length);
134
+ } else {
135
+ selectedWords = words.slice(0, numberOfWords);
136
+ }
137
+ //get selected chars
138
+ var result = selectedWords
139
+ .map((word) => word.slice(0, numberOfChars))
140
+ .join("")
141
+ .toUpperCase();
142
+
143
+ setValue(content, result);
144
+ }
70
145
 
71
146
  function fetchDataFromApi(api, from, property, to, fromName) {
72
- var htmlElement = document.getElementById('field_' + fromName);
73
- if (htmlElement && htmlElement.tagName === 'SPAN') {
147
+ var htmlElement = document.getElementById("field_" + fromName);
148
+ if (htmlElement && htmlElement.tagName === "SPAN") {
74
149
  return;
75
150
  }
76
151
  if (from && from[property]) {
77
152
  if (!isNumeric(from[property])) {
78
153
  return;
79
154
  }
80
- api = window.location.origin + api + from[property];
155
+ api = `${window.location.origin}${api}${from[property]}?userWorkflowId=${userWorkflowId}`;
81
156
  var myHeaders = new Headers();
82
- myHeaders.append('Accept', '*/*');
83
- myHeaders.append('Accept-Language', 'en');
84
- myHeaders.append('Accept-Encoding', 'gzip, deflate, br');
85
- myHeaders.append('Connection', 'keep-alive');
157
+ myHeaders.append("Accept", "*/*");
158
+ myHeaders.append("Accept-Language", "en");
159
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
160
+ myHeaders.append("Connection", "keep-alive");
86
161
  var requestOptions = {
87
- method: 'GET',
162
+ method: "GET",
88
163
  headers: myHeaders,
89
- redirect: 'follow',
164
+ redirect: "follow",
90
165
  };
91
166
 
92
167
  fetch(api, requestOptions)
93
- .then(response => response.text())
94
- .then(result => {
95
- result = result.replace(/"/g, '');
168
+ .then((response) => response.text())
169
+ .then((result) => {
170
+ result = result.replace(/"/g, "");
96
171
 
97
- const textValue = document.getElementById('field_' + to);
172
+ const textValue = document.getElementById("field_" + to);
98
173
 
99
174
  if (textValue) {
100
175
  if (result) {
101
176
  textValue.innerHTML = result;
102
177
  } else {
103
- textValue.innerHTML = '';
178
+ textValue.innerHTML = "";
104
179
  }
105
180
  }
106
181
  })
107
- .catch(error => console.log('error', error));
182
+ .catch((error) => console.log("error", error));
183
+ }
184
+ }
185
+ async function fetchDataFromApiWithoutNumber(api, to) {
186
+ try {
187
+ api = `${window.location.origin}${api}`;
188
+ api = api.replaceAll("@UserWorkflowId", userWorkflowId ?? 0);
189
+ const matches = [...api.matchAll(/@([a-zA-Z0-9_]+)/g)];
190
+
191
+ matches.forEach((match) => {
192
+ const variableName = match[1]; // ví dụ: MaPhong
193
+
194
+ const element =
195
+ document.getElementById(`field_${variableName}`) ||
196
+ document.querySelector(`.field_${variableName}`) || // Dùng querySelector ở đây để lấy element đầu tiên, thay vì getElementsByClassName
197
+ document.getElementById(variableName);
198
+
199
+ let value = "";
200
+
201
+ if (element) {
202
+ value =
203
+ element.getAttribute("value") ??
204
+ element.value ??
205
+ element.innerText ??
206
+ "";
207
+ }
208
+
209
+ api = api.replaceAll(`@${variableName}`, encodeURIComponent(value));
210
+ });
211
+ const myHeaders = new Headers();
212
+ myHeaders.append("Accept", "*/*");
213
+ myHeaders.append("Accept-Language", "en");
214
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
215
+ myHeaders.append("Connection", "keep-alive");
216
+
217
+ const requestOptions = {
218
+ method: "GET",
219
+ headers: myHeaders,
220
+ redirect: "follow",
221
+ };
222
+
223
+ const response = await fetch(api, requestOptions);
224
+ const resultValue = await response.json();
225
+ setValue(to, resultValue);
226
+ } catch (error) {
227
+ console.log("error", error);
228
+ }
229
+ }
230
+
231
+ //tienth1@vntt.com.vn
232
+ async function fetchAndMapDataFromApi(api, to, isMd = false) {
233
+ try {
234
+ api = `${window.location.origin}${api}`;
235
+ api = api.replaceAll("@UserWorkflowId", userWorkflowId ?? 0);
236
+ const matches = [...api.matchAll(/@([a-zA-Z0-9_]+)/g)];
237
+
238
+ matches.forEach((match) => {
239
+ const variableName = match[1]; // ví dụ: MaPhong
240
+
241
+ // ưu tiên lấy từ field_TenBien
242
+ const element =
243
+ document.getElementById(`field_${variableName}`) ||
244
+ document.getElementById(variableName);
245
+
246
+ let value = "";
247
+
248
+ if (element) {
249
+ const selectionItem = element.querySelector(
250
+ ".ant-select-selection-item",
251
+ );
252
+
253
+ if (selectionItem) {
254
+ value =
255
+ selectionItem.getAttribute("title") ??
256
+ selectionItem.textContent.trim();
257
+ } else {
258
+ value =
259
+ element.getAttribute("value") ??
260
+ element.value ??
261
+ element.innerText ??
262
+ "";
263
+ }
264
+ }
265
+
266
+ const safeValue = String(value).replace(/'/g, "''");
267
+ api = api.replaceAll(
268
+ `@${variableName}`,
269
+ encodeURIComponent(`'${safeValue}'`),
270
+ );
271
+ });
272
+
273
+ const myHeaders = new Headers();
274
+ myHeaders.append("Accept", "*/*");
275
+ myHeaders.append("Accept-Language", "en");
276
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
277
+ myHeaders.append("Connection", "keep-alive");
278
+
279
+ const requestOptions = {
280
+ method: "GET",
281
+ headers: myHeaders,
282
+ redirect: "follow",
283
+ };
284
+
285
+ const response = await fetch(api, requestOptions);
286
+ const resultValue = await response.json();
287
+ isMd ? mapMdValueIntoMD(to, resultValue) : setValue(to, resultValue, isMd);
288
+ } catch (error) {
289
+ console.log("error", error);
108
290
  }
109
291
  }
110
292
 
293
+ //tienth1@vntt.com.vn
294
+ function fetchAndMapDataIntoDropdownFromApi(api, to, isMd = false) {
295
+ const isAbsolute = /^https?:\/\//i.test(api);
296
+ api = `${window.location.origin}${api}`;
297
+ api = api.replaceAll("@UserWorkflowId", userWorkflowId ?? 0);
298
+ const matches = [...api.matchAll(/@([a-zA-Z0-9_]+)/g)];
299
+
300
+ matches.forEach((match) => {
301
+ const variableName = match[1]; // ví dụ: MaPhong
302
+
303
+ // ưu tiên lấy từ field_TenBien
304
+ const element =
305
+ document.getElementById(`field_${variableName}`) ||
306
+ document.getElementById(variableName);
307
+
308
+ let value = "";
309
+
310
+ if (element) {
311
+ const selectionItem = element.querySelector(".ant-select-selection-item");
312
+
313
+ if (selectionItem) {
314
+ value =
315
+ selectionItem.getAttribute("title") ??
316
+ selectionItem.textContent.trim();
317
+ } else {
318
+ value =
319
+ element.getAttribute("value") ??
320
+ element.value ??
321
+ element.innerText ??
322
+ "";
323
+ }
324
+ }
325
+
326
+ const safeValue = String(value).replace(/'/g, "''");
327
+ api = api.replaceAll(
328
+ `@${variableName}`,
329
+ encodeURIComponent(`'${safeValue}'`),
330
+ );
331
+ });
332
+
333
+ const myHeaders = new Headers();
334
+ myHeaders.append("Accept", "*/*");
335
+ myHeaders.append("Accept-Language", "en");
336
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
337
+ myHeaders.append("Connection", "keep-alive");
338
+
339
+ const requestOptions = {
340
+ method: "GET",
341
+ headers: myHeaders,
342
+ redirect: "follow",
343
+ };
344
+
345
+ fetch(api, requestOptions)
346
+ .then((response) => {
347
+ if (response.ok) {
348
+ return response.json();
349
+ }
350
+ console.log("Error: fetchAndMapDataIntoDropdownFromApi");
351
+ throw new Error();
352
+ })
353
+ .then((result) => {
354
+ isMd
355
+ ? mapDropDownValueIntoPropertyMD(to, result)
356
+ : mapDropDownValueIntoProperty(to, result);
357
+ });
358
+ }
359
+
111
360
  function getPathFromUrl(url) {
112
361
  return url.split(/[?#]/)[0];
113
362
  }
114
363
 
115
- function searchDataByProperyName(api, from, to) {
364
+ function searchDataByProperyNameWithMultipleMD(
365
+ api,
366
+ from,
367
+ to,
368
+ isMd = false,
369
+ masterDetailIndex = null,
370
+ ) {
116
371
  api = window.location.origin + api + encodeURIComponent(from.value);
117
372
  var myHeaders = new Headers();
118
- myHeaders.append('Accept', '*/*');
119
- myHeaders.append('Accept-Language', 'en');
120
- myHeaders.append('Accept-Encoding', 'gzip, deflate, br');
121
- myHeaders.append('Connection', 'keep-alive');
373
+ myHeaders.append("Accept", "*/*");
374
+ myHeaders.append("Accept-Language", "en");
375
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
376
+ myHeaders.append("Connection", "keep-alive");
122
377
  var requestOptions = {
123
- method: 'GET',
378
+ method: "GET",
124
379
  headers: myHeaders,
125
- redirect: 'follow',
380
+ redirect: "follow",
126
381
  };
127
382
 
128
383
  fetch(api, requestOptions)
129
- .then(response => response.json())
130
- .then(result => {
131
- mapArrayIntoChoiceProperty(to, result);
384
+ .then((response) => response.json())
385
+ .then((result) => {
386
+ isMd
387
+ ? mapArrayIntoChoicePropertyInMDMapMultiple[masterDetailIndex](
388
+ to,
389
+ result,
390
+ masterDetailIndex,
391
+ )
392
+ : mapArrayIntoChoiceProperty(to, result, masterDetailIndex);
132
393
  })
133
- .catch(error => console.log('error', error));
394
+ .catch((error) => console.log("error", error));
134
395
  }
135
396
 
136
- function _searchDataAndMapIntoDropdown(api, from, to) {
137
- searchDataAndMapIntoDropdown(api, from, to);
397
+ function searchDataByProperyName(
398
+ api,
399
+ from,
400
+ to,
401
+ isMd = false,
402
+ masterDetailIndex = null,
403
+ ) {
404
+ api = window.location.origin + api + encodeURIComponent(from.value);
405
+ var myHeaders = new Headers();
406
+ myHeaders.append("Accept", "*/*");
407
+ myHeaders.append("Accept-Language", "en");
408
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
409
+ myHeaders.append("Connection", "keep-alive");
410
+ var requestOptions = {
411
+ method: "GET",
412
+ headers: myHeaders,
413
+ redirect: "follow",
414
+ };
415
+
416
+ fetch(api, requestOptions)
417
+ .then((response) => response.json())
418
+ .then((result) => {
419
+ isMd
420
+ ? mapArrayIntoChoicePropertyInMD(to, result, masterDetailIndex)
421
+ : mapArrayIntoChoiceProperty(to, result, masterDetailIndex);
422
+ })
423
+ .catch((error) => console.log("error", error));
138
424
  }
139
425
 
140
- function searchDataAndMapIntoDropdown(api, from, to) {
426
+ function searchDataAndMapIntoDropdown(
427
+ api,
428
+ from,
429
+ to,
430
+ isMD = false,
431
+ isHavingDefaultValue = false,
432
+ ) {
141
433
  if (from && !isHtmlCollection(from)) {
142
- api = window.location.origin + api + encodeURIComponent(from.value);
434
+ api =
435
+ window.location.origin +
436
+ api +
437
+ (isHavingDefaultValue ? "" : encodeURIComponent(from.value));
143
438
  var myHeaders = new Headers();
144
- myHeaders.append('Accept', '*/*');
145
- myHeaders.append('Accept-Language', 'en');
146
- myHeaders.append('Accept-Encoding', 'gzip, deflate, br');
147
- myHeaders.append('Connection', 'keep-alive');
439
+ myHeaders.append("Accept", "*/*");
440
+ myHeaders.append("Accept-Language", "en");
441
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
442
+ myHeaders.append("Connection", "keep-alive");
148
443
  var requestOptions = {
149
- method: 'GET',
444
+ method: "GET",
150
445
  headers: myHeaders,
151
- redirect: 'follow',
446
+ redirect: "follow",
152
447
  };
153
448
 
154
449
  fetch(api, requestOptions)
155
- .then(response => {
450
+ .then((response) => {
156
451
  if (response.ok) {
157
452
  return response.json();
158
453
  }
159
- console.log('Error: searchDataAndMapIntoDropdown');
454
+ console.log("Error: searchDataAndMapIntoDropdown");
160
455
  throw new Error();
161
456
  })
162
- .then(result => {
163
- mapDropDownValueIntoProperty(to, result);
457
+ .then((result) => {
458
+ isMD
459
+ ? mapDropDownValueIntoPropertyMD(to, result)
460
+ : mapDropDownValueIntoProperty(to, result);
164
461
  });
165
462
  }
166
463
  }
167
464
 
168
- function _searchDataFromMDAndMapIntoMD(api, from, to, fromName) {
169
- searchDataFromMDAndMapIntoMD(api, from, to, fromName);
465
+ function searchDataAndMapIntoDropdown2(api, from, to, idx = 0, isMD = false) {
466
+ if (from && !isHtmlCollection(from)) {
467
+ const isAbsolute = /^https?:\/\//i.test(api);
468
+ api =
469
+ (isAbsolute ? api : window.location.origin + api) +
470
+ encodeURIComponent(from.value);
471
+ var myHeaders = new Headers();
472
+ myHeaders.append("Accept", "*/*");
473
+ myHeaders.append("Accept-Language", "en");
474
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
475
+ myHeaders.append("Connection", "keep-alive");
476
+ var requestOptions = {
477
+ method: "GET",
478
+ headers: myHeaders,
479
+ redirect: "follow",
480
+ };
481
+
482
+ fetch(api, requestOptions)
483
+ .then((response) => {
484
+ if (response.ok) {
485
+ return response.json();
486
+ }
487
+ console.log("Error: searchDataAndMapIntoDropdown");
488
+ throw new Error();
489
+ })
490
+ .then((result) => {
491
+ isMD
492
+ ? mapDropDownValueIntoPropertyMD(to, result)
493
+ : mapDropDownValueIntoProperty(to, result);
494
+ });
495
+ }
170
496
  }
171
497
 
172
498
  function mapMDFromMD({
173
499
  from,
174
- fromName = 'value',
500
+ fromName = "value",
175
501
  masterDetailId,
176
502
  fromMDName,
177
- filterName = 'UserWorkflowId',
503
+ filterName = "UserWorkflowId",
178
504
  toMDName,
179
505
  }) {
180
506
  searchDataFromMDAndMapIntoMD(
181
507
  `/api/v2/workflowReport/SearchDataFromMDAndMapIntoMD/${masterDetailId}?filterName=${filterName}&mdName=${fromMDName}&filterValue=`,
182
508
  from,
183
509
  `${toMDName}`,
184
- fromName
510
+ fromName,
185
511
  );
186
512
  }
187
513
 
514
+ function mapMDFromMD2({
515
+ from,
516
+ fromName = "value",
517
+ masterDetailId,
518
+ fromMDName,
519
+ filterName = "UserWorkflowId",
520
+ toMDName,
521
+ }) {
522
+ const fromData = window[from];
523
+ searchDataFromMDAndMapIntoMD(
524
+ `/api/v2/workflowReport/SearchDataFromMDAndMapIntoMD/${masterDetailId}?filterName=${filterName}&mdName=${fromMDName}&filterValue=`,
525
+ fromData,
526
+ `${toMDName}`,
527
+ fromName,
528
+ );
529
+ }
530
+
531
+ function mapMDFromMDForWareHouse({
532
+ from,
533
+ link,
534
+ dynamic,
535
+ value,
536
+ listHeader,
537
+ fromName = "value",
538
+ workflowId,
539
+ mdName,
540
+ filterName,
541
+ tableNames,
542
+ selectNames,
543
+ searchNames,
544
+ dicNames,
545
+ toMDName,
546
+ }) {
547
+ searchDataFromMDAndMapIntoMD(
548
+ `/api/v2/workflowReport/MapDataFastToMasterDeatail?link=${link}&dynamic=${dynamic}&value=${value}&listHeader=${listHeader}&workflowId=${workflowId}&mdName=${mdName}&filterName=${filterName}&tableNames=${tableNames}&selectNames=${selectNames}&searchNames=${searchNames}&dicNames=${dicNames}&filterValue=`,
549
+ from,
550
+ `${toMDName}`,
551
+ fromName,
552
+ );
553
+ }
554
+
555
+ async function mapMDFromValue(api, items, table, extraSql, where, value, to) {
556
+ try {
557
+ // Split where and value parameters
558
+ const whereFields = where.split("-");
559
+
560
+ const valueFields = value.split("-");
561
+ // Create array of objects based on where and value parameters, filtering out null values
562
+ // const transformedFormValues = whereFields
563
+ // .map((field, index) => ({
564
+
565
+ // name: field,
566
+ // value: getFieldValue(valueFields[index]),
567
+ // }))
568
+ // .filter(item => item.value !== null);
569
+ const transformedFormValues = whereFields
570
+ .map((field, index) => {
571
+ const rawValue = valueFields[index];
572
+ const resolvedValue = getFieldValue(rawValue);
573
+
574
+ return {
575
+ name: field,
576
+ value: resolvedValue ?? rawValue,
577
+ };
578
+ })
579
+ .filter((item) => item.value !== null);
580
+
581
+ // Ensure api is a valid URL by adding origin if it's a relative path
582
+ let baseUrl = api;
583
+
584
+ if (!api.startsWith("http://") && !api.startsWith("https://")) {
585
+ baseUrl = window.location.origin + api;
586
+ }
587
+ const params = new URLSearchParams();
588
+ params.set("items", items); // Let URLSearchParams handle initial structure
589
+ params.set("table", table);
590
+ params.set("extraSql", extraSql);
591
+
592
+ // Rebuild the query string ensuring spaces become %20
593
+ const queryString = Array.from(params.entries())
594
+ .map(
595
+ ([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
596
+ )
597
+ .join("&");
598
+
599
+ // Construct final URL
600
+ const finalUrl = `${baseUrl}?${queryString}`;
601
+
602
+ // Prepare request options for POST with transformed formValues as body
603
+ const requestOptions = {
604
+ method: "POST",
605
+ headers: {
606
+ "Content-Type": "application/json",
607
+ Accept: "*/*",
608
+ "Accept-Language": "en",
609
+ "Accept-Encoding": "gzip, deflate, br",
610
+ Connection: "keep-alive",
611
+ },
612
+ body: JSON.stringify(transformedFormValues),
613
+ };
614
+
615
+ // Fetch using the manually constructed URL string
616
+ //const response = await fetch(finalUrl, requestOptions); // Use finalUrl string
617
+
618
+ fetch(finalUrl, requestOptions)
619
+ .then((response) => response.json())
620
+ .then((result) => {
621
+ mapMdValueIntoMD(to, result);
622
+ })
623
+ .catch((error) => console.log("error", error));
624
+ } catch (e) {
625
+ console.log("mapMDFromValueError:", e);
626
+ }
627
+ }
188
628
  function searchDataFromMDAndMapIntoMD(api, from, to, fromName) {
189
629
  if (from) {
190
630
  api =
191
631
  window.location.origin +
192
632
  api +
193
- encodeURIComponent(from[fromName ?? 'value']);
633
+ encodeURIComponent(from[fromName ?? "value"]);
194
634
 
195
635
  var myHeaders = new Headers();
196
636
 
197
- myHeaders.append('Accept', '*/*');
198
- myHeaders.append('Accept-Language', 'en');
199
- myHeaders.append('Accept-Encoding', 'gzip, deflate, br');
200
- myHeaders.append('Connection', 'keep-alive');
637
+ myHeaders.append("Accept", "*/*");
638
+ myHeaders.append("Accept-Language", "en");
639
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
640
+ myHeaders.append("Connection", "keep-alive");
201
641
 
202
642
  var requestOptions = {
203
- method: 'GET',
643
+ method: "GET",
204
644
  headers: myHeaders,
205
- redirect: 'follow',
645
+ redirect: "follow",
206
646
  };
207
647
 
208
648
  fetch(api, requestOptions)
209
- .then(response => response.json())
210
- .then(result => {
649
+ .then((response) => response.json())
650
+ .then((result) => {
211
651
  mapMdValueIntoMD(to, result);
212
652
  })
213
- .catch(error => console.log('error', error));
653
+ .catch((error) => console.log("error", error));
214
654
  }
215
655
  }
656
+ function searchDataFromMDAndMapIntoMDAndAddAnotherInfo(
657
+ api,
658
+ to,
659
+ anotherLabel,
660
+ anotherValue,
661
+ ) {
662
+ var myHeaders = new Headers();
663
+
664
+ myHeaders.append("Accept", "*/*");
665
+ myHeaders.append("Accept-Language", "en");
666
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
667
+ myHeaders.append("Connection", "keep-alive");
216
668
 
669
+ var requestOptions = {
670
+ method: "GET",
671
+ headers: myHeaders,
672
+ redirect: "follow",
673
+ };
674
+
675
+ fetch(api, requestOptions)
676
+ .then((response) => response.json())
677
+ .then((result) => {
678
+ let data = [];
679
+
680
+ try {
681
+ data = JSON.parse(result); // Parse chuỗi thành mảng
682
+ } catch (e) {
683
+ console.error("Parse JSON lỗi:", e);
684
+ return;
685
+ }
686
+ if (
687
+ anotherLabel &&
688
+ anotherLabel.trim() !== "" &&
689
+ anotherValue &&
690
+ anotherValue.trim() !== ""
691
+ ) {
692
+ const labels = anotherLabel.split("-"); // ["a", "b", "c"]
693
+ const values = anotherValue
694
+ .split("-") // ["1", "2", "3"]
695
+ .map((v) => getValue(v));
696
+
697
+ // Thêm Ghichu vào từng phần tử
698
+ data = data.map((item) => {
699
+ const extraFields = {};
700
+
701
+ labels.forEach((label, index) => {
702
+ extraFields[label] = values[index];
703
+ });
704
+
705
+ return {
706
+ ...item,
707
+ ...extraFields,
708
+ };
709
+ });
710
+ }
711
+ const newResult = JSON.stringify(data);
712
+ mapMdValueIntoMD(to, newResult);
713
+ })
714
+ .catch((error) => console.log("error", error));
715
+ }
716
+ function mapMDFromMDForWareHouseAndAddAnotherInfo({
717
+ from,
718
+ link,
719
+ dynamic,
720
+ value,
721
+ listHeader,
722
+ workflowId,
723
+ mdName,
724
+ filterName,
725
+ tableNames,
726
+ selectNames,
727
+ searchNames,
728
+ dicNames,
729
+ toMDName,
730
+ anotherLabel,
731
+ anotherValue,
732
+ }) {
733
+ searchDataFromMDAndMapIntoMDAndAddAnotherInfo(
734
+ `/api/v2/workflowReport/MapDataFastToMasterDeatail?link=${link}&dynamic=${dynamic}&value=${value}&listHeader=${listHeader}&workflowId=${workflowId}&mdName=${mdName}&filterName=${filterName}&tableNames=${tableNames}&selectNames=${selectNames}&searchNames=${searchNames}&dicNames=${dicNames}&filterValue=${getValue(
735
+ from,
736
+ )}`,
737
+ `${toMDName}`,
738
+ anotherLabel,
739
+ anotherValue,
740
+ );
741
+ }
742
+ function clearDataInMDList(from, toMDName, fromName = "value") {
743
+ searchDataFromMDAndMapIntoMD(
744
+ `/api/v2/workflowReport/MapDataFastToMasterDeatail?filterValue=`,
745
+ from,
746
+ `${toMDName}`,
747
+ fromName,
748
+ );
749
+ setTimeout(() => {
750
+ searchDataFromMDAndMapIntoMD(
751
+ `/api/v2/workflowReport/MapDataFastToMasterDeatail?link=clearDataInMDList&filterValue=`,
752
+ from,
753
+ `${toMDName}`,
754
+ fromName,
755
+ );
756
+ }, 1);
757
+ }
217
758
  // function reSearchDataByProperyName(api, from, fromProperty, to) {
218
759
  // console.log('searchDataByProperyName');
219
760
  // api = window.location.origin + api + from[fromProperty];
@@ -241,33 +782,32 @@ function fromSelectToText(dataFrom, dataTo) {
241
782
  const selectElement = document.getElementById(dataFrom);
242
783
  if (selectElement) {
243
784
  const selectItems = selectElement.getElementsByClassName(
244
- 'ant-select-selection-item'
785
+ "ant-select-selection-item",
245
786
  );
787
+
246
788
  if (selectItems && selectItems.length > 0) {
247
789
  const title = selectItems[0].title;
248
- const textValue = document.getElementById('field_' + dataTo);
790
+ const textValue = document.getElementById("field_" + dataTo);
249
791
  if (textValue) {
250
- const value = title.split('=')[0].trim();
792
+ const value = title.split("=")[0].trim();
251
793
  textValue.innerHTML = value;
252
794
  }
253
795
  }
254
796
  }
255
797
  }
256
798
 
257
- function _numberToText(dataFrom, dataTo, unitId, locale) {
258
- numberToText(dataFrom, dataTo, unitId, locale);
259
- }
260
-
261
- function numberToText(dataFrom, dataTo, unitId, locale) {
799
+ function numberToText(dataFrom, dataTo, unitId, locale = "vi") {
262
800
  if (isDetail === undefined || isDetail === null || isDetail === false) {
263
801
  let numberValue = getValue(dataFrom);
802
+
264
803
  if (numberValue !== undefined && numberValue !== null) {
804
+ numberValue = Number(numberValue);
265
805
  // numberValue = formatNumberString(numberValue, locale);
266
806
 
267
807
  const textValueElement = getElementById(dataTo);
268
808
  if (textValueElement) {
269
809
  if (numberValue !== undefined && numberValue !== null) {
270
- let unit = DEFAULT_MONEY_UNIT;
810
+ let unit = DEFAULT_MONEY_UNIT[locale];
271
811
 
272
812
  if (unitId) {
273
813
  const unitElement = getElementById(unitId);
@@ -275,7 +815,7 @@ function numberToText(dataFrom, dataTo, unitId, locale) {
275
815
  if (unitElement) {
276
816
  const itemValue = getValue(unitId);
277
817
  if (itemValue !== undefined) {
278
- const unitName = getMoneyUnitName(itemValue);
818
+ const unitName = getMoneyUnitName(itemValue, locale);
279
819
  unit = unitName;
280
820
  }
281
821
  } else {
@@ -283,10 +823,12 @@ function numberToText(dataFrom, dataTo, unitId, locale) {
283
823
  }
284
824
  }
285
825
  let text = docSoTien(numberValue.toString(), unit, locale);
286
- text = text?.replaceAll(' ', ' ');
287
- textValueElement.innerHTML = text;
826
+ text = text?.replaceAll(" ", " ");
827
+ setValue(dataTo, text);
828
+ // textValueElement.innerHTML = text;
288
829
  } else {
289
- textValueElement.innerHTML = '';
830
+ // textValueElement.innerHTML = '';
831
+ setValue(dataTo, "");
290
832
  }
291
833
  }
292
834
  }
@@ -300,69 +842,69 @@ function numberToTextEN(n) {
300
842
 
301
843
  // Arrays to hold words for single-digit, double-digit, and below-hundred numbers
302
844
  single_digit = [
303
- '',
304
- 'One',
305
- 'Two',
306
- 'Three',
307
- 'Four',
308
- 'Five',
309
- 'Six',
310
- 'Seven',
311
- 'Eight',
312
- 'Nine',
845
+ "",
846
+ "One",
847
+ "Two",
848
+ "Three",
849
+ "Four",
850
+ "Five",
851
+ "Six",
852
+ "Seven",
853
+ "Eight",
854
+ "Nine",
313
855
  ];
314
856
  double_digit = [
315
- 'Ten',
316
- 'Eleven',
317
- 'Twelve',
318
- 'Thirteen',
319
- 'Fourteen',
320
- 'Fifteen',
321
- 'Sixteen',
322
- 'Seventeen',
323
- 'Eighteen',
324
- 'Nineteen',
857
+ "Ten",
858
+ "Eleven",
859
+ "Twelve",
860
+ "Thirteen",
861
+ "Fourteen",
862
+ "Fifteen",
863
+ "Sixteen",
864
+ "Seventeen",
865
+ "Eighteen",
866
+ "Nineteen",
325
867
  ];
326
868
  below_hundred = [
327
- 'Twenty',
328
- 'Thirty',
329
- 'Forty',
330
- 'Fifty',
331
- 'Sixty',
332
- 'Seventy',
333
- 'Eighty',
334
- 'Ninety',
869
+ "Twenty",
870
+ "Thirty",
871
+ "Forty",
872
+ "Fifty",
873
+ "Sixty",
874
+ "Seventy",
875
+ "Eighty",
876
+ "Ninety",
335
877
  ];
336
878
 
337
- if (n === 0) return 'Zero';
879
+ if (n === 0) return "Zero";
338
880
 
339
881
  // Recursive function to translate the number into words
340
882
  function translate(n) {
341
- let word = '';
883
+ let word = "";
342
884
  if (n < 10) {
343
- word = single_digit[n] + ' ';
885
+ word = single_digit[n] + " ";
344
886
  } else if (n < 20) {
345
- word = double_digit[n - 10] + ' ';
887
+ word = double_digit[n - 10] + " ";
346
888
  } else if (n < 100) {
347
889
  let rem = translate(n % 10);
348
- word = below_hundred[(n - (n % 10)) / 10 - 2] + ' ' + rem;
890
+ word = below_hundred[(n - (n % 10)) / 10 - 2] + " " + rem;
349
891
  } else if (n < 1000) {
350
892
  word =
351
- single_digit[Math.trunc(n / 100)] + ' Hundred ' + translate(n % 100);
893
+ single_digit[Math.trunc(n / 100)] + " Hundred " + translate(n % 100);
352
894
  } else if (n < 1000000) {
353
895
  word =
354
896
  translate(parseInt(n / 1000)).trim() +
355
- ' Thousand ' +
897
+ " Thousand " +
356
898
  translate(n % 1000);
357
899
  } else if (n < 1000000000) {
358
900
  word =
359
901
  translate(parseInt(n / 1000000)).trim() +
360
- ' Million ' +
902
+ " Million " +
361
903
  translate(n % 1000000);
362
904
  } else {
363
905
  word =
364
906
  translate(parseInt(n / 1000000000)).trim() +
365
- ' Billion ' +
907
+ " Billion " +
366
908
  translate(n % 1000000000);
367
909
  }
368
910
  return word;
@@ -373,7 +915,7 @@ function numberToTextEN(n) {
373
915
  return result.trim();
374
916
  }
375
917
 
376
- function readNumber(dataFrom, unitId, locale, dataTo = colName) {
918
+ function readNumber(dataFrom, unitId, locale = "vi", dataTo = colName) {
377
919
  if (isDetail === undefined || isDetail === null || isDetail === false) {
378
920
  let numberValue = getFieldValue(dataFrom);
379
921
  if (numberValue !== undefined && numberValue !== null) {
@@ -381,7 +923,7 @@ function readNumber(dataFrom, unitId, locale, dataTo = colName) {
381
923
  const textValueElement = getElementById(dataTo);
382
924
  if (textValueElement) {
383
925
  if (numberValue !== undefined && numberValue !== null) {
384
- let unit = DEFAULT_MONEY_UNIT;
926
+ let unit = DEFAULT_MONEY_UNIT[locale];
385
927
  if (unitId) {
386
928
  const unitElement = getElementById(unitId);
387
929
  if (unitElement) {
@@ -395,12 +937,12 @@ function readNumber(dataFrom, unitId, locale, dataTo = colName) {
395
937
  }
396
938
  }
397
939
  let text = docSoTien(numberValue.toString(), unit, locale);
398
- text = text?.replaceAll(' ', ' ');
940
+ text = text?.replaceAll(" ", " ");
399
941
  // textValueElement.innerHTML = text;
400
942
  setValue(dataTo, text);
401
943
  } else {
402
944
  // textValueElement.innerHTML = '';
403
- setValue(dataTo, '');
945
+ setValue(dataTo, "");
404
946
  }
405
947
  }
406
948
  }
@@ -409,13 +951,13 @@ function readNumber(dataFrom, unitId, locale, dataTo = colName) {
409
951
  }
410
952
  }
411
953
 
412
- function numberToTextDetail(dataFrom, dataTo, unitId, locale) {
413
- let unit = DEFAULT_MONEY_UNIT;
954
+ function numberToTextDetail(dataFrom, dataTo, unitId, locale = "vi") {
955
+ let unit = DEFAULT_MONEY_UNIT[locale];
414
956
  if (unitId) {
415
957
  const unitElement = document.querySelector(`#${unitId}`);
416
958
  if (unitElement) {
417
959
  const itemElement = unitElement.querySelector(
418
- '.ant-select-selection-item'
960
+ ".ant-select-selection-item",
419
961
  );
420
962
  if (itemElement && itemElement.title) {
421
963
  const unitName = getMoneyUnitName(itemElement.title);
@@ -433,43 +975,43 @@ function numberToTextDetail(dataFrom, dataTo, unitId, locale) {
433
975
  if (textValue) {
434
976
  if (value !== undefined && value !== null) {
435
977
  let text = docSoTien(value, unit, locale);
436
- text = text?.replaceAll(' ', ' ');
978
+ text = text?.replaceAll(" ", " ");
437
979
  textValue.innerHTML = text;
438
980
  } else {
439
- textValue.innerHTML = '';
981
+ textValue.innerHTML = "";
440
982
  }
441
983
  }
442
984
  }
443
985
  }
444
986
 
445
- function textNumberToText(dataFrom, dataTo, unitId) {
446
- const fromElement = document.getElementById('field_' + dataFrom);
987
+ function textNumberToText(dataFrom, dataTo, unitId, locale = "vi") {
988
+ const fromElement = document.getElementById("field_" + dataFrom);
447
989
  if (fromElement) {
448
990
  const fromValue = fromElement.innerHTML;
449
991
  const numberValue = formatNumberString(fromValue);
450
- const toElement = document.getElementById('field_' + dataTo);
992
+ const toElement = document.getElementById("field_" + dataTo);
451
993
  if (toElement) {
452
994
  if (numberValue) {
453
- let unit = DEFAULT_MONEY_UNIT;
995
+ let unit = DEFAULT_MONEY_UNIT[locale];
454
996
  if (unitId) {
455
997
  const unitElement = document.querySelector(`#${unitId}`);
456
998
  if (unitElement) {
457
999
  const itemElement = unitElement.querySelector(
458
- '.ant-select-selection-item'
1000
+ ".ant-select-selection-item",
459
1001
  );
460
1002
  if (itemElement && itemElement.title) {
461
1003
  const unitName = getMoneyUnitName(itemElement.title);
462
- unit = unitName;
1004
+ unit = "unitName";
463
1005
  }
464
1006
  } else {
465
- unit = unitId;
1007
+ unit = "unitId";
466
1008
  }
467
1009
  }
468
1010
  let text = docSoTien(numberValue.toString(), unit);
469
- text = text?.replaceAll(' ', ' ');
1011
+ text = text?.replaceAll(" ", " ");
470
1012
  toElement.innerHTML = text;
471
1013
  } else {
472
- toElement.innerHTML = '';
1014
+ toElement.innerHTML = "";
473
1015
  }
474
1016
  }
475
1017
  }
@@ -480,13 +1022,13 @@ function _textNumberToText(dataFrom, dataTo, unitId) {
480
1022
  }
481
1023
 
482
1024
  // Locale: "vi" | "en"
483
- function formatNumberString(number, locale = 'vi') {
484
- let result = '0';
485
- if (locale === 'vi') {
1025
+ function formatNumberString(number, locale = "vi") {
1026
+ let result = "0";
1027
+ if (locale === "vi") {
486
1028
  result = number
487
- ? number?.toString().replaceAll('.', '').replaceAll('= ', '')
488
- : '0';
489
- result = result.replaceAll(',', '.');
1029
+ ? number?.toString().replaceAll(".", "").replaceAll("= ", "")
1030
+ : "0";
1031
+ result = result.replaceAll(",", ".");
490
1032
  } else {
491
1033
  result = number;
492
1034
  }
@@ -496,7 +1038,9 @@ function formatNumberString(number, locale = 'vi') {
496
1038
 
497
1039
  function setValue(toId, value, isMD = false) {
498
1040
  const name = toId ?? getCurrentId();
499
- window.setFieldValue(name, value, isMD ? masterDetailId : undefined);
1041
+ const mdId = isMD ? masterDetailId : undefined;
1042
+
1043
+ window.setFieldValue(name, value, mdId);
500
1044
  }
501
1045
 
502
1046
  function setValueMD(name, value, mdId) {
@@ -530,32 +1074,178 @@ function calc(name, string) {
530
1074
  }
531
1075
 
532
1076
  const getFieldValue = (name, isMD) => {
533
- return window.getFieldValue(name, isMD);
1077
+ return window.getFieldValue(name, isMD ? masterDetailId : undefined);
534
1078
  };
535
1079
 
536
- const handleFormular = (formularString, isMD) => {
537
- if (formularString?.length > 0) {
538
- formularString = formularString.replace(/\s/g, '');
539
- let name = '';
540
- let adding = false;
541
- const ids = [];
542
-
543
- for (let i = 0; i < formularString.length; i++) {
544
- if (formularString[i] === '[') {
545
- adding = true;
546
- } else if (formularString[i] === ']') {
547
- ids.push(name);
548
- adding = false;
549
- name = '';
550
- } else if (adding === true) {
551
- name += formularString[i];
1080
+ const getFieldContent = (name, isMD) => {
1081
+ return window.getFieldContent(name, isMD ? masterDetailId : false);
1082
+ };
1083
+
1084
+ const getFieldContentMD = (name) => {
1085
+ let value = "";
1086
+
1087
+ const element = document.getElementById(`${masterDetailId}_${name}`);
1088
+ if (element) {
1089
+ const mdField = element.querySelector(".md-field");
1090
+ if (mdField) {
1091
+ const fieldType = mdField.getAttribute("type");
1092
+ const editable = mdField.getAttribute("editable");
1093
+ if (editable === "true") {
1094
+ if (fieldType === "lookup") {
1095
+ const valueFields = mdField.querySelectorAll(
1096
+ ".ant-select-selection-item",
1097
+ );
1098
+ if (valueFields.length > 0) {
1099
+ const values = Array.from(valueFields).map(
1100
+ (field) => field.innerText ?? field.textContent ?? "",
1101
+ );
1102
+ value = values.join("; ");
1103
+ }
1104
+ } else if (fieldType === "input") {
1105
+ const inputValue = mdField.querySelector(".ant-input")?.value;
1106
+ if (inputValue) {
1107
+ value = inputValue;
1108
+ }
1109
+ }
1110
+ } else {
1111
+ // View only mode
1112
+ }
1113
+ }
1114
+ }
1115
+ return value;
1116
+ };
1117
+
1118
+ const getInnerTextValue = (name, isMD) => {
1119
+ if (!isMD) {
1120
+ // If element is not found, it is editable field, otherwise it is a readonly field
1121
+ let element = document.getElementById(`field_${name}`);
1122
+ if (element) {
1123
+ // Check if element contains an Ant Design checkbox group as a child
1124
+ const checkboxGroup = element.querySelector(".ant-checkbox-group");
1125
+ if (checkboxGroup) {
1126
+ // Get all checked checkboxes within the checkbox group
1127
+ const checkedInputs = checkboxGroup.querySelectorAll(
1128
+ 'input[type="checkbox"]:checked',
1129
+ );
1130
+ const checkedLabels = [];
1131
+
1132
+ checkedInputs.forEach((input) => {
1133
+ // Get the label text next to the checkbox
1134
+ const label = input.closest("label");
1135
+ if (label) {
1136
+ // Get the span text (excludes the input)
1137
+ const spans = label.querySelectorAll("span");
1138
+ if (spans.length > 0) {
1139
+ // Usually the text is in the last span
1140
+ const textSpan = spans[spans.length - 1];
1141
+ if (textSpan && textSpan.textContent) {
1142
+ checkedLabels.push(textSpan.textContent.trim());
1143
+ }
1144
+ }
1145
+ }
1146
+ });
1147
+
1148
+ return checkedLabels.join(", ");
1149
+ }
1150
+ return element.innerText ?? element.textContent ?? "";
1151
+ } else {
1152
+ element = document.querySelector(`.field_${name}`);
1153
+ if (element) {
1154
+ // Kiểm tra xem element có phải là select hay lookup không
1155
+ // nếu có thì lấy giá trị của element
1156
+ const type = element.getAttribute("type");
1157
+ if (type === "lookup" || type === "select") {
1158
+ let contentElement = element.querySelector(
1159
+ ".ant-select-selection-item-content",
1160
+ );
1161
+ if (!contentElement) {
1162
+ contentElement = element.querySelector(
1163
+ ".ant-select-selection-item",
1164
+ );
1165
+ }
1166
+ if (contentElement) {
1167
+ return contentElement.innerText ?? contentElement.textContent ?? "";
1168
+ }
1169
+ } else if (type === "dateTime") {
1170
+ const inputElement = element.querySelector(
1171
+ `#dynamicWorkflowForm_${name}`,
1172
+ );
1173
+ if (inputElement) {
1174
+ return inputElement.value;
1175
+ }
1176
+ }
552
1177
  }
553
1178
  }
1179
+ } else {
1180
+ const fieldContent = getFieldContentMD(name);
1181
+ return fieldContent;
1182
+ }
1183
+ const fieldValue = window.getFieldValue(name, isMD);
1184
+ return fieldValue;
1185
+ };
1186
+
1187
+ const getFieldValueNew = (name, isMasterDetail, IsShowTime) => {
1188
+ return formatDate(getInnerTextValue(name, isMasterDetail), IsShowTime);
1189
+ };
1190
+
1191
+ const formatDate = (value, IsShowTime) => {
1192
+ // 1. Kiểm tra nhanh: Nếu là số, null hoặc không phải chuỗi/đối tượng thì trả về luôn
1193
+ if (!value || typeof parseInt(value, 10) === "number") {
1194
+ return value;
1195
+ }
1196
+
1197
+ const date = new Date(value);
1198
+
1199
+ // 2. Kiểm tra tính hợp lệ của Date
1200
+ // isValidDate kiểm tra xem giá trị có chuyển đổi thành mốc thời gian thực hay không
1201
+ const isValidDate = !isNaN(date.getTime());
1202
+ // 3. Kiểm tra bổ sung bằng Regex (Tùy chọn nhưng nên có)
1203
+ // Chỉ xử lý nếu chuỗi có định dạng giống ngày tháng (VD: 2025-12-16 hoặc 12/16/2025)
1204
+ // Điều này ngăn việc một từ ngẫu nhiên bị Date() hiểu nhầm
1205
+ const isDateString = typeof value === "string" && /[\d-:/T]/.test(value);
1206
+ if (isValidDate && isDateString) {
1207
+ const day = String(date.getDate()).padStart(2, "0");
1208
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1209
+ const year = date.getFullYear();
1210
+ const hours = String(date.getHours()).padStart(2, "0");
1211
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1212
+ // Nếu IsShowTime là undefined hoặc false thì chỉ trả về ngày
1213
+ if (!IsShowTime) {
1214
+ return `${day}/${month}/${year}`;
1215
+ } else {
1216
+ return `${day}/${month}/${year} ${hours}:${minutes}`;
1217
+ }
1218
+ }
554
1219
 
1220
+ // Nếu không lọt vào các điều kiện trên, trả về giá trị gốc
1221
+ return value;
1222
+ };
1223
+
1224
+ const handleFormularNew = (formularString, isMD, IsShowTime) => {
1225
+ if (formularString?.length > 0) {
1226
+ const matches = [...formularString.matchAll(/\[([^\]]+)\]/g)];
1227
+ const ids = matches.map((m) => m[1]);
555
1228
  let formular = formularString;
1229
+ if (ids.length > 0) {
1230
+ ids.forEach((id) => {
1231
+ const value = getFieldValueNew(id, isMD, IsShowTime) ?? 0;
1232
+ formular = formular.replaceAll(`[${id}]`, value);
1233
+ });
1234
+ }
556
1235
 
1236
+ return { math: formular, elementIds: ids };
1237
+ } else {
1238
+ return { math: formularString, elementIds: [] };
1239
+ }
1240
+ };
1241
+
1242
+ const handleFormular = (formularString, isMD) => {
1243
+ if (formularString?.length > 0) {
1244
+ const matches = [...formularString.matchAll(/\[([^\]]+)\]/g)];
1245
+ const ids = matches.map((m) => m[1]);
1246
+ let formular = formularString;
557
1247
  if (ids.length > 0) {
558
- ids.forEach(id => {
1248
+ ids.forEach((id) => {
559
1249
  const value = getFieldValue(id, isMD) ?? 0;
560
1250
  formular = formular.replaceAll(`[${id}]`, value);
561
1251
  });
@@ -567,15 +1257,104 @@ const handleFormular = (formularString, isMD) => {
567
1257
  }
568
1258
  };
569
1259
 
570
- function calculate(calcText, to = colName) {
1260
+ function handleCondition(conditionString, isMD) {
1261
+ if (!conditionString) return { math: "false", elementIds: [] };
1262
+
1263
+ let mathResult = conditionString;
1264
+ const ids = [];
1265
+ const matches = conditionString.match(/\[(.*?)\]/g) || [];
1266
+
1267
+ // 1. Thay thế các [ID]
1268
+ matches.forEach((match) => {
1269
+ const id = match.replace(/[\[\]]/g, "");
1270
+ ids.push(id);
1271
+ let value = getFieldValue(id, isMD);
1272
+ // Chỉ bọc nháy nếu giá trị là string và CHƯA bị bọc nháy
1273
+ const safeValue =
1274
+ typeof value === "string"
1275
+ ? `'${value.toString().replace(/'/g, "")}'` // Xóa hết nháy cũ đi rồi bọc lại cho chắc
1276
+ : (value ?? "");
1277
+
1278
+ mathResult = mathResult.replaceAll(match, safeValue);
1279
+ });
1280
+
1281
+ // 2. Chuyển = thành ==
1282
+ mathResult = mathResult.replace(/(?<![<>!])=(?!=)/g, "==");
1283
+
1284
+ // 3. Xử lý vế phải (giá trị so sánh cố định)
1285
+ // Xóa bỏ các dấu nháy thừa nếu người dùng lỡ tay nhập: 'Nghỉ phép' -> Nghỉ phép
1286
+ // Sau đó bọc lại một cặp nháy duy nhất cho toàn bộ cụm chữ sau dấu ==
1287
+ mathResult = mathResult.replace(
1288
+ /(==|!=)\s*['"]?(.*?)['"]?$/g,
1289
+ (match, op, content) => {
1290
+ const trimmedContent = content.trim();
1291
+ // Nếu là từ khóa đặc biệt thì không bọc nháy
1292
+ if (["true", "false", "null", "undefined"].includes(trimmedContent))
1293
+ return `${op} ${trimmedContent}`;
1294
+ return `${op} '${trimmedContent.replace(/'/g, "")}'`;
1295
+ },
1296
+ );
1297
+
1298
+ return { math: mathResult, elementIds: ids };
1299
+ }
1300
+
1301
+ function calculateMMD(
1302
+ calcTextTrue,
1303
+ to = colName,
1304
+ conditionText = null,
1305
+ calcTextFalse = null,
1306
+ ) {
1307
+ calculate(calcTextTrue, to, conditionText, calcTextFalse, true);
1308
+ }
1309
+
1310
+ function calculate(
1311
+ calcTextTrue,
1312
+ to = colName,
1313
+ conditionText = null,
1314
+ calcTextFalse = null,
1315
+ isMasterDetailT = null,
1316
+ ) {
1317
+ const isMD = isMasterDetailT !== null ? isMasterDetailT : isMasterDetail;
571
1318
  try {
572
- if (calcText && calcText.length > 0) {
573
- const formular = handleFormular(calcText, isMasterDetail);
1319
+ let isAllowed = true; // Mặc định là true nếu không truyền điều kiện
1320
+
1321
+ // Bước 1: Kiểm tra điều kiện (Nếu có)
1322
+ if (conditionText && conditionText.trim() !== "") {
1323
+ const conditionObj = handleFormular(conditionText, isMD);
1324
+ try {
1325
+ isAllowed = eval(conditionObj.math);
1326
+ } catch (evalError) {
1327
+ console.error("Lỗi cú pháp trong điều kiện:", conditionObj.math);
1328
+ return; // Dừng nếu điều kiện viết sai
1329
+ }
1330
+ }
1331
+ // Bước 2: Chọn công thức để chạy dựa vào kết quả của isAllowed
1332
+ let finalCalcText = "";
1333
+
1334
+ if (isAllowed) {
1335
+ // Nếu điều kiện ĐÚNG -> Dùng công thức chính
1336
+ finalCalcText = calcTextTrue;
1337
+ } else {
1338
+ // Nếu điều kiện SAI
1339
+ if (calcTextFalse && calcTextFalse.trim() !== "") {
1340
+ // Nếu có truyền công thức dự phòng -> Dùng công thức dự phòng
1341
+ finalCalcText = calcTextFalse;
1342
+ } else {
1343
+ // Nếu sai mà không dặn làm gì tiếp theo -> Bỏ qua, thoát hàm
1344
+ // console.log(
1345
+ // 'Điều kiện sai và không có công thức Else, dừng tính toán.'
1346
+ // );
1347
+ return;
1348
+ }
1349
+ }
1350
+ // Bước 3: Đưa công thức đã chọn đi tính toán
1351
+ if (finalCalcText && finalCalcText.length > 0) {
1352
+ const formular = handleFormular(finalCalcText, isMD);
574
1353
  const result = eval(formular.math);
575
- setValue(to, result, masterDetailId);
1354
+ setValue(to, result, isMD);
576
1355
  }
577
1356
  } catch (e) {
578
- console.log(e);
1357
+ console.error("Lỗi tổng quát trong calculate:", e);
579
1358
  }
580
1359
  }
581
1360
 
@@ -598,11 +1377,11 @@ function calcSub(name, string) {
598
1377
  `'${name}'`,
599
1378
  `
600
1379
  ${num}
601
- `
1380
+ `,
602
1381
  );
603
1382
  }
604
1383
  } catch (e) {
605
- console.log('calcSub', e);
1384
+ console.log("calcSub", e);
606
1385
  }
607
1386
  return string;
608
1387
  }
@@ -611,63 +1390,297 @@ function isHtmlCollection(element) {
611
1390
  return HTMLCollection.prototype.isPrototypeOf(element);
612
1391
  }
613
1392
 
614
- function mapData(name, from, to, type) {
615
- if (isHtmlCollection(from)) return;
616
-
617
- let value = from ? from[name] : undefined;
1393
+ function mapData(name, from, to, isMD, isDistinct = false) {
1394
+ try {
1395
+ if (isHtmlCollection(from)) return;
1396
+ let isMasterDetailT =
1397
+ isMD !== undefined ? isMD : masterDetailId?.length > 0;
1398
+ if (isMD === false) {
1399
+ isMasterDetailT = false;
1400
+ }
1401
+ if (isMasterDetailT) {
1402
+ // Map data for master detail
1403
+ mapDataMasterDetail(from, name, to, masterDetailId);
1404
+ } else {
1405
+ // Map data for form value
1406
+ //let value = from ? from[name] : undefined;
1407
+
1408
+ //tienth1@vntt.com.vn
1409
+ let value;
1410
+ if (Array.isArray(from)) {
1411
+ // Multiple items selected: join each item's value with ';'
1412
+ const values = from
1413
+ .map((item) => (item ? item[name] : undefined))
1414
+ .filter((v) => v !== undefined && v !== null && v !== "");
1415
+ value = (isDistinct ? [...new Set(values)] : values).join(";");
1416
+ } else {
1417
+ value = from ? from[name] : undefined;
1418
+ }
618
1419
 
619
- if (value && type === 'number') {
620
- value = convertNumberToText(value);
1420
+ // if (value && type === 'number') {
1421
+ // value = convertNumberToText(value);
1422
+ // }
1423
+ setValue(to, value);
1424
+ }
1425
+ } catch (e) {
1426
+ console.log("mapData", e);
621
1427
  }
622
- setValue(to, value);
623
- // const toElement = getElementById(to);
624
- // if (toElement) {
625
- // toElement.innerHTML = value ?? '';
626
- // }
627
1428
  }
628
1429
 
629
1430
  function _mapData(name, from, to, type) {
630
1431
  mapData(name, from, to, type);
631
1432
  }
632
1433
 
633
- function mapDataMasterDetail(
1434
+ /**
1435
+ * Chuyển đổi ngày UTC sang giờ Local tuỳ chỉnh và format
1436
+ * @param {string} dateStr - Chuỗi ngày (ví dụ: "2/9/2025 5:00:00 PM")
1437
+ * @param {number} offsetHour - Múi giờ muốn chuyển (ví dụ: 7 cho VN, 8 cho TQ)
1438
+ * @param {string} formatStr - Định dạng output (ví dụ: 'DD/MM/YYYY HH:mm:ss')
1439
+ */
1440
+
1441
+ function mapDateDataMasterDetail(
1442
+ from,
1443
+ fromName,
1444
+ to,
1445
+ id,
1446
+ offsetHour = 7,
1447
+ formatStr = "DD/MM/YYYY",
1448
+ ) {
1449
+ try {
1450
+ if (fromName && to && id !== undefined) {
1451
+ let dateStr = "";
1452
+ if (from) {
1453
+ dateStr = from[fromName];
1454
+ // 1. Tạo đối tượng Date, ép kiểu hiểu là UTC bằng cách thêm chuỗi 'UTC' nếu chưa có
1455
+ // Lưu ý: new Date("... UTC") sẽ trả về thời gian chính xác tại mốc UTC đó
1456
+ const isExplicitUTC =
1457
+ dateStr.toUpperCase().includes("Z") ||
1458
+ dateStr.toUpperCase().includes("UTC");
1459
+ const utcDate = new Date(isExplicitUTC ? dateStr : `${dateStr} UTC`);
1460
+
1461
+ if (isNaN(utcDate.getTime())) return "Invalid Date";
1462
+
1463
+ // 2. Cộng thêm giờ offset (đổi ra milliseconds)
1464
+ // Logic: Lấy thời gian UTC gốc + (số giờ * 60 phút * 60 giây * 1000 ms)
1465
+ const targetTime = new Date(
1466
+ utcDate.getTime() + offsetHour * 60 * 60 * 1000,
1467
+ );
1468
+
1469
+ // 3. Chuẩn bị các thành phần để map vào format
1470
+ // Lưu ý: Ta dùng getUTC...() vì ta đã cộng thẳng giờ vào timestamp rồi
1471
+ const map = {
1472
+ DD: String(targetTime.getUTCDate()).padStart(2, "0"),
1473
+ MM: String(targetTime.getUTCMonth() + 1).padStart(2, "0"),
1474
+ YYYY: targetTime.getUTCFullYear(),
1475
+ HH: String(targetTime.getUTCHours()).padStart(2, "0"), // 24h format
1476
+ mm: String(targetTime.getUTCMinutes()).padStart(2, "0"),
1477
+ ss: String(targetTime.getUTCSeconds()).padStart(2, "0"),
1478
+ // Thêm xử lý AM/PM nếu cần thiết (cho định dạng hh)
1479
+ };
1480
+
1481
+ // 4. Replace format
1482
+ dateStr = formatStr.replace(
1483
+ /DD|MM|YYYY|HH|mm|ss/g,
1484
+ (match) => map[match],
1485
+ );
1486
+ }
1487
+
1488
+ // toElement.innerHTML = value ?? '';
1489
+ setValueMD(to, dateStr ?? "", id);
1490
+ from = undefined;
1491
+ }
1492
+ } catch (e) {
1493
+ console.log("mapDateDataMasterDetailError:", e);
1494
+ }
1495
+ }
1496
+
1497
+ function mapDataMasterDetail(from, fromName, to, id) {
1498
+ try {
1499
+ if (fromName && to && id !== undefined) {
1500
+ let value = "";
1501
+ // if (from) {
1502
+
1503
+ //tienth1@vntt.com.vn
1504
+ if (Array.isArray(from)) {
1505
+ // Multiple items selected: join each item's value with ';'
1506
+ value = from
1507
+ .map((item) => (item ? item[fromName] : undefined))
1508
+ .filter((v) => v !== undefined && v !== null && v !== "")
1509
+ .join(";");
1510
+ } else if (from) {
1511
+ value = from[fromName];
1512
+ }
1513
+
1514
+ setValueMD(to, value ?? "", id);
1515
+ from = undefined;
1516
+ }
1517
+ } catch (e) {
1518
+ console.log("mapDataMasterDetailError:", e);
1519
+ }
1520
+ }
1521
+
1522
+ function mapDataMasterDetailWhenDataChange(
634
1523
  from,
635
- fromProperty,
1524
+ fromName,
636
1525
  to,
637
1526
  id,
638
1527
  type,
639
- isEditMasterDetail
1528
+ isEditMasterDetail,
640
1529
  ) {
641
1530
  try {
642
- if (!isEditMasterDetail) {
643
- if (fromProperty && to && id !== undefined) {
644
- let value = '';
645
- if (from) {
646
- value = from[fromProperty];
647
- // if (value && type === 'number') {
648
- // value = '= ' + convertNumberToText(value);
649
- // }
1531
+ if (from[fromName] !== previousName) {
1532
+ // Update the previousName to the current name
1533
+ previousName = from[fromName];
1534
+ if (!isEditMasterDetail) {
1535
+ if (fromName && to && id !== undefined) {
1536
+ let value = "";
1537
+ if (from) {
1538
+ value = from[fromName];
1539
+ // if (value && type === 'number') {
1540
+ // value = '= ' + convertNumberToText(value);
1541
+ // }
1542
+ }
1543
+ // toElement.innerHTML = value ?? '';
1544
+ setValueMD(to, value ?? "", id);
650
1545
  }
651
- // toElement.innerHTML = value ?? '';
652
- setValueMD(to, value ?? '', id);
653
1546
  }
1547
+ } else {
1548
+ // console.log('du lieu ko thay đổi', from[fromName], previousName);
654
1549
  }
655
1550
  } catch (e) {
656
- console.log('mapDataMasterDetailError:', e);
1551
+ console.log("mapDataMasterDetailWhenDataChangeError:", e);
657
1552
  }
658
1553
  }
659
1554
 
660
- function _mapDataMasterDetail(from, fromProperty, to, id, isEditMasterDetail) {
661
- mapDataMasterDetail(from, fromProperty, to, id, isEditMasterDetail);
1555
+ async function mapDataMasterDetailWithFormValue(
1556
+ api,
1557
+ items,
1558
+ table,
1559
+ where,
1560
+ value,
1561
+ target,
1562
+ id,
1563
+ formValues,
1564
+ ) {
1565
+ try {
1566
+ // Split where and value parameters
1567
+ const whereFields = where.split("-");
1568
+
1569
+ const valueFields = value.split("-");
1570
+
1571
+ // Create array of objects based on where and value parameters, filtering out null values
1572
+ const transformedFormValues = whereFields
1573
+ .map((field, index) => ({
1574
+ name: field,
1575
+ value: valueFields[index].includes(".")
1576
+ ? formValues[valueFields[index].split(".")[0]][
1577
+ valueFields[index].split(".")[1]
1578
+ ]
1579
+ : formValues[valueFields[index]],
1580
+ }))
1581
+ .filter((item) => item.value !== null);
1582
+
1583
+ // Ensure api is a valid URL by adding origin if it's a relative path
1584
+ let baseUrl = api;
1585
+
1586
+ if (!api.startsWith("http://") && !api.startsWith("https://")) {
1587
+ baseUrl = window.location.origin + api;
1588
+ }
1589
+
1590
+ const params = new URLSearchParams();
1591
+ params.set("items", items); // Let URLSearchParams handle initial structure
1592
+ params.set("table", table);
1593
+
1594
+ // Rebuild the query string ensuring spaces become %20
1595
+ const queryString = Array.from(params.entries())
1596
+ .map(
1597
+ ([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
1598
+ )
1599
+ .join("&");
1600
+
1601
+ // Construct final URL
1602
+ const finalUrl = `${baseUrl}?${queryString}`;
1603
+
1604
+ // Prepare request options for POST with transformed formValues as body
1605
+ const requestOptions = {
1606
+ method: "POST",
1607
+ headers: {
1608
+ "Content-Type": "application/json",
1609
+ Accept: "*/*",
1610
+ "Accept-Language": "en",
1611
+ "Accept-Encoding": "gzip, deflate, br",
1612
+ Connection: "keep-alive",
1613
+ },
1614
+ body: JSON.stringify(transformedFormValues),
1615
+ };
1616
+
1617
+ // Fetch using the manually constructed URL string
1618
+ const response = await fetch(finalUrl, requestOptions); // Use finalUrl string
1619
+
1620
+ if (!response.ok) {
1621
+ throw new Error("Network response was not ok");
1622
+ }
1623
+ const data = await response.json();
1624
+ if (data) {
1625
+ setValueMD(target, data.value, id);
1626
+ }
1627
+ } catch (e) {
1628
+ console.log("mapDataMasterDetailWithFormValueError:", e);
1629
+ }
1630
+ }
1631
+ async function mapDataMasterDetailForBIDV(api, target, id, formValues) {
1632
+ try {
1633
+ // Ensure api is a valid URL by adding origin if it's a relative path
1634
+ const matches = api.match(/@[a-zA-Z0-9_]+/g);
1635
+ if (matches) {
1636
+ api = api.replace(/@[a-zA-Z0-9_]+/g, (match) => {
1637
+ const key = match.slice(1);
1638
+ return formValues[key] ?? "0";
1639
+ });
1640
+ }
1641
+ let baseUrl = api;
1642
+
1643
+ if (!api.startsWith("http://") && !api.startsWith("https://")) {
1644
+ baseUrl = window.location.origin + api;
1645
+ }
1646
+
1647
+ // Construct final URL
1648
+ const finalUrl = `${baseUrl}`;
1649
+
1650
+ // Prepare request options for POST with transformed formValues as body
1651
+ var myHeaders = new Headers();
1652
+ myHeaders.append("Accept", "*/*");
1653
+ myHeaders.append("Accept-Language", "en");
1654
+ myHeaders.append("Accept-Encoding", "gzip, deflate, br");
1655
+ myHeaders.append("Connection", "keep-alive");
1656
+ var requestOptions = {
1657
+ method: "GET",
1658
+ headers: myHeaders,
1659
+ redirect: "follow",
1660
+ };
1661
+
1662
+ // Fetch using the manually constructed URL string
1663
+ const response = await fetch(finalUrl, requestOptions); // Use finalUrl string
1664
+
1665
+ if (!response.ok) {
1666
+ throw new Error("Network response was not ok");
1667
+ }
1668
+ const data = await response.json();
1669
+ if (data) {
1670
+ setValueMD(target, data.value, id);
1671
+ }
1672
+ } catch (e) {
1673
+ console.log("mapDataMasterDetailForBIDVError:", e);
1674
+ }
662
1675
  }
663
1676
 
664
1677
  function sendLinkToWebView(path, fileName) {
665
1678
  try {
666
1679
  let pathSendToWebView =
667
1680
  window.location.protocol +
668
- '//' +
1681
+ "//" +
669
1682
  window.location.host +
670
- path.replace('downloadFile', 'downloadFileAnonymous');
1683
+ path.replace("downloadFile", "downloadFileAnonymous");
671
1684
  let data = {
672
1685
  path: pathSendToWebView,
673
1686
  fileName: fileName,
@@ -680,15 +1693,18 @@ function sendLinkToWebView(path, fileName) {
680
1693
  }
681
1694
  }
682
1695
 
1696
+ // Dùng riêng cho VNTT để làm Đơn xin nghỉ phép
1697
+ // Tách ký hiệu để map. Ví dụ 4X4P, 4X4K
683
1698
  function fromSelectToTextMasterDetail(from, fromProperty, to, id) {
684
1699
  if (from) {
685
1700
  let value = from[fromProperty];
686
- const toId = id.toString() + '_' + to;
1701
+ const toId = id.toString() + "_" + to;
687
1702
  const toElement = document.getElementById(toId);
688
1703
  if (toElement) {
689
1704
  if (value) {
690
- const textValue = value.split('=')[0].trim();
691
- toElement.innerHTML = textValue;
1705
+ const textValue = value.split("=")[0].trim();
1706
+ setValueMD(to, textValue, id);
1707
+ // toElement.innerHTML = textValue;
692
1708
  }
693
1709
  }
694
1710
  }
@@ -699,43 +1715,43 @@ function isStringValid(string) {
699
1715
  return !format.test(string);
700
1716
  }
701
1717
 
702
- function _mapNumberMD2F(fromId, toNumberId, toTextId) {
703
- const num = _mapFromMasterDetail(fromId, toNumberId);
1718
+ // function _mapNumberMD2F(fromId, toNumberId, toTextId) {
1719
+ // const num = _mapFromMasterDetail(fromId, toNumberId);
704
1720
 
705
- _numberToTextFromMD(num, toTextId);
706
- }
1721
+ // _numberToTextFromMD(num, toTextId);
1722
+ // }
707
1723
 
708
- function _mapFromMasterDetail(fromId, toId) {
709
- let result;
710
- const fromElement = getElementById(fromId, true);
711
- if (fromElement) {
712
- const fromValue = getValueMD(fromId) ?? 0;
713
- const toElement = document.querySelector(`#field_${toId}`);
714
- if (toElement) {
715
- toElement.innerHTML = fromValue;
716
- result = fromValue;
717
- }
718
- }
719
- return result;
720
- }
1724
+ // function _mapFromMasterDetail(fromId, toId) {
1725
+ // let result;
1726
+ // const fromElement = getElementById(fromId, true);
1727
+ // if (fromElement) {
1728
+ // const fromValue = getValueMD(fromId) ?? 0;
1729
+ // const toElement = document.querySelector(`#field_${toId}`);
1730
+ // if (toElement) {
1731
+ // toElement.innerHTML = fromValue;
1732
+ // result = fromValue;
1733
+ // }
1734
+ // }
1735
+ // return result;
1736
+ // }
721
1737
 
722
- function _numberToTextFromMD(num, dataTo) {
723
- const toElement = document.querySelector(`#field_${dataTo}`);
724
- if (toElement) {
725
- if (num) {
726
- let number = formatNumberString(num);
727
- if (toElement) {
728
- if (number) {
729
- toElement.innerHTML = docTien.doc(number);
730
- } else {
731
- toElement.innerHTML = '';
732
- }
733
- }
734
- } else {
735
- toElement.innerHTML = '';
736
- }
737
- }
738
- }
1738
+ // function _numberToTextFromMD(num, dataTo) {
1739
+ // const toElement = document.querySelector(`#field_${dataTo}`);
1740
+ // if (toElement) {
1741
+ // if (num) {
1742
+ // let number = formatNumberString(num);
1743
+ // if (toElement) {
1744
+ // if (number) {
1745
+ // toElement.innerHTML = docTien.doc(number);
1746
+ // } else {
1747
+ // toElement.innerHTML = '';
1748
+ // }
1749
+ // }
1750
+ // } else {
1751
+ // toElement.innerHTML = '';
1752
+ // }
1753
+ // }
1754
+ // }
739
1755
 
740
1756
  function removeDuplicates(arr) {
741
1757
  return arr.filter((item, index) => arr.indexOf(item) === index);
@@ -745,24 +1761,24 @@ function calcMD(calcText, to, id) {
745
1761
  try {
746
1762
  // const toElement = document.getElementById(`${id}_${to}`);
747
1763
  const toElement = to ? getElementById(`${to}`, true) : getCurrentElement();
748
- let text = calcText.replace(/[^a-zA-Z0-9 ]/g, ' ');
1764
+ let text = calcText.replace(/[^a-zA-Z0-9 ]/g, " ");
749
1765
 
750
- text = text.trim().replace(/\s+/g, ' ');
751
- let variables = text.split(' ');
1766
+ text = text.trim().replace(/\s+/g, " ");
1767
+ let variables = text.split(" ");
752
1768
 
753
1769
  if (toElement && variables.length > 0) {
754
1770
  variables = removeDuplicates(variables);
755
1771
 
756
1772
  const names = [];
757
1773
 
758
- variables.forEach(variable => {
1774
+ variables.forEach((variable) => {
759
1775
  if (isNaN(variable)) {
760
1776
  names.push(variable);
761
1777
  }
762
1778
  });
763
1779
 
764
1780
  let calc = calcText;
765
- names.forEach(name => {
1781
+ names.forEach((name) => {
766
1782
  try {
767
1783
  const numberValue = getValueMD(name) ?? 0;
768
1784
  // const num = numberElement
@@ -775,7 +1791,7 @@ function calcMD(calcText, to, id) {
775
1791
 
776
1792
  calc = calc.replaceAll(`'${name}'`, numberValue);
777
1793
  } catch (e) {
778
- throw 'Not found element: ' + name;
1794
+ throw "Not found element: " + name;
779
1795
  }
780
1796
  });
781
1797
  const result = eval(calc);
@@ -804,51 +1820,49 @@ function calcMMD(calcText, to) {
804
1820
 
805
1821
  // const calcText = `('num1_mdsum' - 'num2_mdsum')`;
806
1822
  let calc = calcText;
807
- let text = calcText.replaceAll('+', ' ');
808
- text = text.replaceAll('-', ' ');
809
- text = text.replaceAll('*', ' ');
810
- text = text.replaceAll('/', ' ');
811
- text = text.replaceAll('(', ' ');
812
- text = text.replaceAll(')', ' ');
813
- text = text.replaceAll("'", ' ');
814
- text = text.replaceAll('"', ' ');
1823
+ let text = calcText.replaceAll("+", " ");
1824
+ text = text.replaceAll("-", " ");
1825
+ text = text.replaceAll("*", " ");
1826
+ text = text.replaceAll("/", " ");
1827
+ text = text.replaceAll("(", " ");
1828
+ text = text.replaceAll(")", " ");
1829
+ text = text.replaceAll("'", " ");
1830
+ text = text.replaceAll('"', " ");
815
1831
 
816
- text = text.trim().replace(/\s+/g, ' ');
1832
+ text = text.trim().replace(/\s+/g, " ");
817
1833
 
818
- let variables = text.split(' ');
1834
+ let variables = text.split(" ");
819
1835
 
820
1836
  if (toElement && variables.length > 0) {
821
1837
  variables = removeDuplicates(variables);
822
1838
 
823
1839
  const names = [];
824
1840
 
825
- variables.forEach(variable => {
1841
+ variables.forEach((variable) => {
826
1842
  if (isNaN(variable)) {
827
1843
  names.push(variable);
828
1844
  }
829
1845
  });
830
-
831
- names.forEach(name => {
1846
+ names.forEach((name) => {
832
1847
  let val = 0;
833
- const nameSplit = name.split('_');
1848
+ const nameSplit = name.split("_");
834
1849
  if (nameSplit.length === 2) {
835
1850
  const tableElement = document.getElementById(`${nameSplit[0]}`);
836
1851
  if (tableElement) {
837
- const valueElement = tableElement.querySelector(
838
- `.sum-${nameSplit[1]}`
839
- );
840
-
1852
+ const sumClass = `.sum-${nameSplit[1]}`;
1853
+ const valueElement = tableElement.querySelector(sumClass);
841
1854
  if (valueElement) {
842
1855
  val = valueElement.innerHTML;
843
1856
  }
844
1857
  }
845
1858
  }
846
1859
  if (val !== 0) {
847
- val = formatNumberString(val);
1860
+ val = formatNumberString(val.replace(" %", ""));
848
1861
  }
849
1862
  calc = calc.replaceAll(`'${name}'`, val ?? 0);
850
1863
  });
851
- const result = eval(calc !== '()' ? calc : '');
1864
+ const result = eval(calc !== "()" ? calc : "");
1865
+
852
1866
  setValue(to, result);
853
1867
  // toElement.innerHTML = Intl.NumberFormat('vi-VN', {}).format(result);
854
1868
  }
@@ -861,14 +1875,14 @@ const getElementById = (name, isMD) => {
861
1875
  if (name) {
862
1876
  let isMasterDetailT = isMD;
863
1877
  if (!isMasterDetailT) {
864
- const splitName = name.split('-');
865
- if (splitName.length === 2 && splitName[0] === 'md') {
1878
+ const splitName = name.split("-");
1879
+ if (splitName.length === 2 && splitName[0] === "md") {
866
1880
  isMasterDetailT = true;
867
1881
  }
868
1882
  }
869
1883
  // check if master detail
870
1884
  if (isMasterDetailT && mdCellId) {
871
- const ids = mdCellId.split('_');
1885
+ const ids = mdCellId.split("_");
872
1886
  if (ids.length === 3) {
873
1887
  const id = `${ids[0]}_${ids[1]}_${name}`;
874
1888
  const ele = document.getElementById(id);
@@ -882,7 +1896,7 @@ const getElementById = (name, isMD) => {
882
1896
  }
883
1897
  }
884
1898
  } else {
885
- const ele = document.getElementById('field_' + name);
1899
+ const ele = document.getElementById("field_" + name);
886
1900
 
887
1901
  if (ele) {
888
1902
  return ele;
@@ -891,7 +1905,7 @@ const getElementById = (name, isMD) => {
891
1905
  if (classEle) {
892
1906
  return classEle;
893
1907
  }
894
- return document.getElementById('dynamicWorkflowForm_' + name);
1908
+ return document.getElementById("dynamicWorkflowForm_" + name);
895
1909
  }
896
1910
  }
897
1911
  }
@@ -925,7 +1939,7 @@ const getValue = (name, mdId) => {
925
1939
  // }
926
1940
  };
927
1941
 
928
- const getCurrentElement = isMD => {
1942
+ const getCurrentElement = (isMD) => {
929
1943
  let isMasterDetailT = isMasterDetail;
930
1944
  if (isMD !== undefined) {
931
1945
  isMasterDetailT = isMD;
@@ -933,24 +1947,53 @@ const getCurrentElement = isMD => {
933
1947
  if (isMasterDetailT) {
934
1948
  return document.getElementById(mdCellId);
935
1949
  } else {
936
- return document.getElementById('field_' + colName);
1950
+ return document.getElementById("field_" + colName);
937
1951
  }
938
1952
  };
939
1953
 
940
- const getCurrentId = isMD => {
1954
+ const getCurrentId = (isMD) => {
941
1955
  let isMasterDetailT = isMasterDetail;
942
1956
  if (isMD !== undefined) {
943
1957
  isMasterDetailT = isMD;
944
1958
  }
945
1959
  if (isMasterDetailT) {
946
- return mdCellId;
1960
+ if (mdCellId) {
1961
+ const mdCells = mdCellId.split("_");
1962
+ if (mdCells.length === 3) {
1963
+ return mdCells[2];
1964
+ }
1965
+ return mdCellId;
1966
+ }
947
1967
  } else {
948
1968
  return colName;
949
1969
  }
950
1970
  };
951
1971
 
952
1972
  function isValidDate(str) {
953
- return dayjs(str, 'YYYY-MM-DD', true).isValid();
1973
+ return dayjs(str, "YYYY-MM-DD", true).isValid();
1974
+ }
1975
+
1976
+ function connectTextMD(name, formularString, IsShowTime = undefined) {
1977
+ try {
1978
+ if (formularString && formularString.length > 0) {
1979
+ var connect = handleFormularNew(formularString, true, IsShowTime);
1980
+ setValue(name, connect.math, true);
1981
+ }
1982
+ } catch (e) {
1983
+ console.log(e);
1984
+ }
1985
+ }
1986
+
1987
+ function connectText(name, formularString, IsShowTime = undefined) {
1988
+ try {
1989
+ if (formularString && formularString.length > 0) {
1990
+ var connect = handleFormularNew(formularString, false, IsShowTime);
1991
+
1992
+ setValue(name, connect.math, false);
1993
+ }
1994
+ } catch (e) {
1995
+ console.log(e);
1996
+ }
954
1997
  }
955
1998
 
956
1999
  const datetime = {
@@ -961,119 +2004,166 @@ const datetime = {
961
2004
  }
962
2005
  let dateTime;
963
2006
  if (date.length === 10) {
964
- let dateSplit = date.split('/');
2007
+ let dateSplit = date.split("/");
965
2008
 
966
2009
  if (dateSplit[2].length === 4) {
967
2010
  dateSplit = dateSplit.reverse();
968
2011
  }
969
- dateTime = dateSplit.join('/') + ' 00:00';
2012
+ dateTime = dateSplit.join("/") + " 00:00";
970
2013
  } else if (date.length === 16) {
971
- const dateTimes = date.split(' ');
972
- const dateSplit = dateTimes[0].split('/');
973
- dateTime = dateSplit.reverse().join('/') + ` ${dateTimes[1]}`;
2014
+ const dateTimes = date.split(" ");
2015
+ const dateSplit = dateTimes[0].split("/");
2016
+ dateTime = dateSplit.reverse().join("/") + ` ${dateTimes[1]}`;
974
2017
  } else if (date.length === 19) {
975
- return dayjs(date, 'YYYY-MM-DDTHH:mm:ss');
2018
+ return dayjs(date, "YYYY-MM-DDTHH:mm:ss");
976
2019
  }
977
2020
  if (dateTime) {
978
- return dayjs(dateTime, 'YYYY/MM/DD HH:mm');
2021
+ return dayjs(dateTime, "YYYY/MM/DD HH:mm");
979
2022
  } else if (isValidDate(date)) {
980
2023
  return dayjs(date);
981
2024
  }
982
2025
  }
983
2026
  },
984
2027
 
985
- count(type, fromDateName, toDateName, toId = colName) {
986
- let result = '';
2028
+ count(type, fromDateName, toDateName, isMD) {
2029
+ const isMDValue = isMD !== undefined ? isMD : isMasterDetail;
2030
+ let result = "";
987
2031
 
988
- const fromDateValue = getFieldValue(fromDateName, isMasterDetail);
989
- const toDateValue = getFieldValue(toDateName, isMasterDetail);
2032
+ let fromDateValue = getValue(fromDateName, isMDValue);
2033
+ let toDateValue = getValue(toDateName, isMDValue);
2034
+
2035
+ if (!fromDateValue && fromDateName.toLowerCase() === "today") {
2036
+ fromDateValue = new Date();
2037
+ }
2038
+ if (!toDateValue && toDateName.toLowerCase() === "today") {
2039
+ toDateValue = new Date();
2040
+ }
990
2041
 
991
2042
  if (
992
2043
  fromDateValue &&
993
- fromDateValue !== 'Invalid Date' &&
2044
+ fromDateValue !== "Invalid Date" &&
994
2045
  toDateValue &&
995
- toDateValue !== 'Invalid Date'
2046
+ toDateValue !== "Invalid Date"
996
2047
  ) {
997
2048
  let fromDate = this.formatDayjs(fromDateValue);
998
2049
  let toDate = this.formatDayjs(toDateValue);
999
2050
 
1000
- let diff = '';
2051
+ let diff = "";
1001
2052
  switch (type) {
1002
- case 'minutes':
1003
- diff = toDate.diff(fromDate, 'minutes');
2053
+ case "minutes":
2054
+ diff = toDate.diff(fromDate, "minutes");
1004
2055
  break;
1005
- case 'hours':
1006
- diff = toDate.diff(fromDate, 'hours');
2056
+ case "hours":
2057
+ diff = toDate.diff(fromDate, "hours");
1007
2058
  break;
1008
- case 'days':
1009
- diff = toDate.diff(fromDate, 'days') + 1;
2059
+ case "days":
2060
+ diff = toDate.diff(fromDate, "days") + 1;
1010
2061
  break;
1011
- case 'months':
1012
- diff = toDate.diff(fromDate, 'months');
2062
+ case "months":
2063
+ diff = toDate.diff(fromDate, "months");
1013
2064
  break;
1014
- case 'years':
1015
- diff = toDate.diff(fromDate, 'years');
2065
+ case "years":
2066
+ diff = toDate.diff(fromDate, "years");
1016
2067
  break;
1017
2068
 
1018
2069
  default:
1019
2070
  break;
1020
2071
  }
2072
+
1021
2073
  result = diff.toString();
1022
2074
  }
1023
-
1024
- setValue(toId, result, isMasterDetail);
2075
+ return result;
1025
2076
  // toElement.innerHTML = result;
1026
2077
  },
1027
2078
  };
1028
2079
 
1029
- const countYears = (fromDateName, toDateName, toId) => {
1030
- datetime.count('years', fromDateName, toDateName, toId);
2080
+ const countYears = (fromDateName, toDateName, toId, isMD) => {
2081
+ const result = datetime.count("years", fromDateName, toDateName, isMD);
2082
+ setValue(toId, result, isMasterDetail);
1031
2083
  };
1032
2084
 
1033
- const countMonths = (fromDateName, toDateName, toId) => {
1034
- datetime.count('months', fromDateName, toDateName, toId);
2085
+ const countMonths = (fromDateName, toDateName, toId, isMD) => {
2086
+ const result = datetime.count("months", fromDateName, toDateName, isMD);
2087
+ setValue(toId, result, isMasterDetail);
1035
2088
  };
1036
2089
 
1037
- const countDays = (fromDateName, toDateName, toId) => {
1038
- datetime.count('days', fromDateName, toDateName, toId);
2090
+ const countDays = (fromDateName, toDateName, toId, isMD) => {
2091
+ const result = datetime.count("days", fromDateName, toDateName, isMD);
2092
+ setValue(toId, result, isMasterDetail);
1039
2093
  };
1040
2094
 
1041
- const countHours = (fromDateName, toDateName, toId) => {
1042
- datetime.count('hours', fromDateName, toDateName, toId);
2095
+ const countHours = (fromDateName, toDateName, toId, isMD) => {
2096
+ const result = datetime.count("hours", fromDateName, toDateName, isMD);
2097
+ setValue(toId, result, isMasterDetail);
1043
2098
  };
1044
2099
 
1045
- const countMinutes = (fromDateName, toDateName, toId) => {
1046
- datetime.count('minutes', fromDateName, toDateName, toId);
2100
+ const countMinutes = (fromDateName, toDateName, toId, isMD) => {
2101
+ const result = datetime.count("minutes", fromDateName, toDateName, isMD);
2102
+ setValue(toId, result, isMasterDetail);
1047
2103
  };
1048
2104
 
1049
2105
  const validateDatesRange = (startDate, toDate, dateType, count) => {
1050
2106
  window.validateDatesRange(startDate, toDate, dateType, count);
1051
2107
  };
1052
2108
 
1053
- const validatePhoneNumber = name => {
2109
+ const validatePhoneNumber = (name) => {
1054
2110
  window.validatePhoneNumber(name);
1055
2111
  };
1056
2112
 
1057
- const validateEmail = name => {
2113
+ const validateEmail = (name) => {
1058
2114
  window.validateEmail(name);
1059
2115
  };
1060
2116
 
1061
- const convertNumberToText = value => {
1062
- return new Intl.NumberFormat('vi-VN').format(Number(value));
2117
+ const validateField = (fieldName, scriptString, message, isMasterDetailT) => {
2118
+ let mdId = masterDetailId;
2119
+ if (isMasterDetailT === false) {
2120
+ mdId = undefined;
2121
+ }
2122
+ const value = getValue(fieldName, mdId);
2123
+ if (value !== "null") {
2124
+ const formular = handleFormularString(scriptString, mdId);
2125
+
2126
+ const isValid = eval(formular.math);
2127
+
2128
+ window.handleActionButtons(isValid, formular.elementIds, mdId, message);
2129
+ } else {
2130
+ window.handleActionButtons(true, [fieldName], mdId);
2131
+ }
1063
2132
  };
1064
2133
 
1065
- const displayNumber = elementId => {
2134
+ const validateNumber = (fieldName, formularText, message) => {
2135
+ const value = getValue(fieldName);
2136
+ if (value !== "null" && value !== undefined && value !== null) {
2137
+ const formular = handleFormularString(formularText, isMasterDetail);
2138
+ const isValid = eval(formular.math);
2139
+
2140
+ window.handleActionButtons(
2141
+ isValid,
2142
+ formular.elementIds,
2143
+ isMasterDetail,
2144
+ message,
2145
+ );
2146
+ } else {
2147
+ window.handleActionButtons(true, [fieldName], isMasterDetail);
2148
+ }
2149
+ };
2150
+
2151
+ const convertNumberToText = (value) => {
2152
+ return new Intl.NumberFormat("vi-VN").format(Number(value));
2153
+ };
2154
+
2155
+ const displayNumber = (elementId) => {
1066
2156
  const id = elementId ?? colName;
1067
2157
  const element = getElementById(id);
1068
2158
  if (element) {
1069
2159
  const value = getValue(id) ?? 0;
1070
- if (!value.includes('.')) {
1071
- element.innerHTML = new Intl.NumberFormat('vi-VN').format(Number(value));
2160
+ if (!value.includes(".")) {
2161
+ element.innerHTML = new Intl.NumberFormat("vi-VN").format(Number(value));
1072
2162
  }
1073
2163
  }
1074
2164
  };
1075
2165
 
1076
- const _displayNumber = elementId => {
2166
+ const _displayNumber = (elementId) => {
1077
2167
  displayNumber(elementId);
1078
2168
  };
1079
2169
 
@@ -1081,61 +2171,576 @@ const validateForm = (isValid, elementIds, isMD) => {
1081
2171
  window.handleActionButtons(isValid, elementIds, isMD);
1082
2172
  };
1083
2173
 
1084
- const compareNumbers = (compareString, isMD) => {
2174
+ const compareNumbers = (compareString, isMD, error) => {
1085
2175
  const formular = handleFormularString(compareString, isMD);
1086
-
1087
- window.handleActionButtons(eval(formular.math), formular.elementIds, isMD);
2176
+ window.handleActionButtons(
2177
+ eval(formular.math),
2178
+ formular.elementIds,
2179
+ isMD ? masterDetailId : false,
2180
+ error,
2181
+ );
1088
2182
  };
1089
2183
 
1090
- const compareNumbersMD = compareString => {
2184
+ const compareNumbersMD = (compareString) => {
1091
2185
  compareNumbers(compareString, true);
1092
2186
  };
1093
2187
 
1094
2188
  const handleFormularString = (formularString, isMD) => {
1095
- if (formularString?.length > 0) {
1096
- formularString = formularString.replace(/\s/g, '');
1097
- let name = '';
1098
- let adding = false;
1099
- const ids = [];
1100
-
1101
- for (let i = 0; i < formularString.length; i++) {
1102
- if (formularString[i] === '[') {
1103
- adding = true;
1104
- } else if (formularString[i] === ']') {
1105
- ids.push(name);
1106
- adding = false;
1107
- name = '';
1108
- } else if (adding === true) {
1109
- name += formularString[i];
2189
+ try {
2190
+ if (formularString?.length > 0) {
2191
+ const cleanFormular = formularString.replace(/\s/g, "");
2192
+ const matches = [...cleanFormular.matchAll(/\[([^\]]+)\]/g)];
2193
+ const ids = matches.map((m) => m[1]);
2194
+ let formular = cleanFormular;
2195
+ if (ids.length > 0) {
2196
+ ids.forEach((id) => {
2197
+ const value = isMD ? getValueMD(id) : getValue(id);
2198
+ formular = formular.replaceAll(`[${id}]`, value);
2199
+ });
1110
2200
  }
2201
+ return { math: formular, elementIds: ids };
1111
2202
  }
1112
-
1113
- let formular = formularString;
1114
- if (ids.length > 0) {
1115
- ids.forEach(id => {
1116
- const value = formatNumberString(
1117
- isMD ? getValueMD(id) : getValue(id) ?? '0'
1118
- );
1119
- formular = formular.replaceAll(`[${id}]`, value);
1120
- });
1121
- }
1122
- return { math: formular, elementIds: ids };
1123
- } else {
2203
+ return { math: formularString, elementIds: [] };
2204
+ } catch (e) {
2205
+ console.error("handleFormularString error:", e);
1124
2206
  return { math: formularString, elementIds: [] };
1125
2207
  }
1126
2208
  };
1127
2209
 
1128
2210
  const valid = () => {
1129
- window.handleActionButtons(true);
2211
+ if (typeof window !== "undefined" && typeof window.handleActionButtons === "function") {
2212
+ window.handleActionButtons(true);
2213
+ }
1130
2214
  };
1131
2215
 
1132
2216
  const invalid = () => {
1133
- window.handleActionButtons(false);
2217
+ if (typeof window !== "undefined" && typeof window.handleActionButtons === "function") {
2218
+ window.handleActionButtons(false);
2219
+ }
1134
2220
  };
1135
2221
 
1136
- const convertVIToENNumber = num => {
1137
- num = num.replaceAll(',', '-');
1138
- num = num.replaceAll('.', ',');
1139
- num = num.replaceAll('-', '.');
1140
- return num;
2222
+ const convertVIToENNumber = (num) => {
2223
+ if (typeof num !== "string") return num;
2224
+ return num.replace(/[.,]/g, (m) => (m === "," ? "." : ","));
1141
2225
  };
2226
+
2227
+ function mapDayOfWeek(date, target, isMD) {
2228
+ const value = getValueMD(date, isMD);
2229
+ let val;
2230
+ if (value !== undefined) {
2231
+ val = dayjs(value).day()?.toString();
2232
+ }
2233
+ setValue(target, val, isMD);
2234
+ }
2235
+
2236
+ function validateDate(startDate, endDate, condition, isMD, isDateOnly = false) {
2237
+ const startDateValue = getValue(startDate, isMD);
2238
+ const endDateValue = getValue(endDate, isMD);
2239
+
2240
+ let isValid = false;
2241
+ if (startDateValue && endDateValue) {
2242
+ let startDatejs = dayjs(startDateValue);
2243
+ let endDatejs = dayjs(endDateValue);
2244
+
2245
+ if (isDateOnly) {
2246
+ startDatejs = startDatejs.startOf("day");
2247
+ endDatejs = endDatejs.startOf("day");
2248
+ }
2249
+
2250
+ switch (condition) {
2251
+ case ">=":
2252
+ case "=>":
2253
+ isValid =
2254
+ startDatejs.isAfter(endDatejs) || startDatejs.isSame(endDatejs);
2255
+ break;
2256
+
2257
+ case "<=":
2258
+ case "=<":
2259
+ isValid =
2260
+ startDatejs.isBefore(endDatejs) || startDatejs.isSame(endDatejs);
2261
+ break;
2262
+ case ">":
2263
+ isValid = startDatejs.isAfter(endDatejs);
2264
+ break;
2265
+ case "<":
2266
+ isValid = startDatejs.isBefore(endDatejs);
2267
+ break;
2268
+
2269
+ case "=":
2270
+ isValid = startDatejs.isSame(endDatejs);
2271
+ break;
2272
+
2273
+ default:
2274
+ isValid = false;
2275
+ }
2276
+ }
2277
+ window.handleActionButtons(isValid, [endDate], isMD, "Date is not valid");
2278
+ }
2279
+
2280
+ function getYearFromDateTime(from, to, isShortYear = false, isMD = false) {
2281
+ const value = getValue(from, isMD);
2282
+ const year = dayjs(value).year();
2283
+ let yearString = year.toString();
2284
+
2285
+ if (isShortYear) {
2286
+ yearString = yearString.slice(2);
2287
+ }
2288
+ setValue(to, yearString, isMD);
2289
+ }
2290
+
2291
+ function getNumberInSingleProperty(from, to, isMD = false) {
2292
+ const value = getValue(from, isMD);
2293
+ if (value) {
2294
+ const number = value.replace(/[^0-9]/g, "");
2295
+ const numberValue = Number(number);
2296
+ setValue(to, numberValue, isMD);
2297
+ }
2298
+ }
2299
+
2300
+ /**
2301
+ * Kiểu selector cho DynamicFieldResolver:
2302
+ * có thể truyền tên field (string) hoặc 1 callback function nhận (item, lang)
2303
+ *
2304
+ * @param {Record<string, any>} item
2305
+ * @param {string | ((item: Record<string, any>, lang: string) => any)} resolver
2306
+ * @param {string} lang
2307
+ * @returns {any}
2308
+ */
2309
+ function resolveFieldValue(item, resolver, lang) {
2310
+ if (!item) return undefined;
2311
+
2312
+ if (typeof resolver === "function") {
2313
+ return resolver(item, lang);
2314
+ }
2315
+
2316
+ if (typeof resolver !== "string") {
2317
+ return undefined;
2318
+ }
2319
+
2320
+ // Convention tự động: nếu lang === 'en', ưu tiên tìm `${fieldName}EN`, không có thì fallback về fieldName gốc
2321
+ if (lang === "en") {
2322
+ const enKey = `${resolver}EN`;
2323
+ if (item[enKey] !== undefined && item[enKey] !== null) {
2324
+ return item[enKey];
2325
+ }
2326
+ }
2327
+
2328
+ return item[resolver];
2329
+ }
2330
+
2331
+ /**
2332
+ * Trích xuất data từ Master Detail và map thành options cho dropdown/choice field trên DynamicForm
2333
+ *
2334
+ * @param {string} fromMasterDetailName - Tên của Master Detail nguồn
2335
+ * @param {string | ((item: Record<string, any>, lang: string) => any)} valueName - Tên field hoặc function resolver lấy value
2336
+ * @param {string | ((item: Record<string, any>, lang: string) => any)} labelName - Tên field hoặc function resolver lấy label
2337
+ * @param {string} [toName] - Tên trường dropdown đích trên DynamicForm hoặc MasterDetail (mặc định lấy trường đang chạy script)
2338
+ * @param {boolean} [isMD] - Chỉ định rõ ràng đích đến là Master Detail (nếu không truyền sẽ tự động nhận diện)
2339
+ */
2340
+ /**
2341
+ * Kho đăng ký toàn cục cho các mapping options từ Master Detail
2342
+ * key: `${fromMasterDetailName}::${toName}`
2343
+ * value: { fromMasterDetailName, valueName, labelName, toName, isMD }
2344
+ */
2345
+ if (typeof window !== "undefined") {
2346
+ if (!window.masterDetailOptionMappings) {
2347
+ window.masterDetailOptionMappings = new Map();
2348
+ }
2349
+ }
2350
+
2351
+ /**
2352
+ * Đăng ký một quy tắc mapping options từ Master Detail vào Registry
2353
+ */
2354
+ function registerMasterDetailOptionMapping(
2355
+ fromMasterDetailName,
2356
+ valueName,
2357
+ labelName,
2358
+ toName,
2359
+ isMD,
2360
+ ) {
2361
+ if (typeof window === "undefined") return;
2362
+ if (!window.masterDetailOptionMappings) {
2363
+ window.masterDetailOptionMappings = new Map();
2364
+ }
2365
+ const key = `${fromMasterDetailName}::${toName}`;
2366
+ const mapping = {
2367
+ fromMasterDetailName,
2368
+ valueName,
2369
+ labelName,
2370
+ toName,
2371
+ isMD,
2372
+ };
2373
+ window.masterDetailOptionMappings.set(key, mapping);
2374
+ return mapping;
2375
+ }
2376
+
2377
+ /**
2378
+ * Thực thi mapping options từ Master Detail vào trường đích
2379
+ */
2380
+ function applyMasterDetailOptionMapping(mapping) {
2381
+ if (!mapping || !mapping.fromMasterDetailName || !mapping.toName) return;
2382
+ const { fromMasterDetailName, valueName, labelName, toName, isMD } = mapping;
2383
+
2384
+ // 1. Lấy data của Master Detail nguồn
2385
+ let mdData = [];
2386
+ if (typeof window.getMasterDetailData === "function") {
2387
+ mdData = window.getMasterDetailData(fromMasterDetailName);
2388
+ }
2389
+ if ((!mdData || mdData.length === 0) && window.masterDetailData) {
2390
+ mdData = window.masterDetailData[fromMasterDetailName];
2391
+ }
2392
+ if ((!mdData || mdData.length === 0) && window.masterDetailValues) {
2393
+ mdData = window.masterDetailValues[fromMasterDetailName];
2394
+ }
2395
+
2396
+ // Fallback: Tìm records từ DOM container nếu có
2397
+ if (!mdData || mdData.length === 0) {
2398
+ const mdElement =
2399
+ typeof document !== "undefined"
2400
+ ? document.querySelector(`[data-name="${fromMasterDetailName}"]`) ||
2401
+ document.getElementById(`master-detail-${fromMasterDetailName}`)
2402
+ : null;
2403
+ if (mdElement) {
2404
+ const cellElements = mdElement.querySelectorAll("td[record], [record]");
2405
+ const recordsMap = new Map();
2406
+ cellElements.forEach((cell) => {
2407
+ try {
2408
+ const recordStr = cell.getAttribute("record");
2409
+ if (recordStr) {
2410
+ const rec = JSON.parse(recordStr);
2411
+ if (rec && rec.id !== undefined && !recordsMap.has(rec.id)) {
2412
+ recordsMap.set(rec.id, rec);
2413
+ }
2414
+ }
2415
+ } catch (e) {
2416
+ // ignore parse error
2417
+ }
2418
+ });
2419
+ if (recordsMap.size > 0) {
2420
+ mdData = Array.from(recordsMap.values());
2421
+ }
2422
+ }
2423
+ }
2424
+
2425
+ // 2. Xác định ngôn ngữ hiện tại
2426
+ const lang = (
2427
+ (typeof localStorage !== "undefined" &&
2428
+ (localStorage.getItem("locale") || localStorage.getItem("i18nextLng"))) ||
2429
+ "vi"
2430
+ )
2431
+ .toLowerCase()
2432
+ .startsWith("en")
2433
+ ? "en"
2434
+ : "vi";
2435
+
2436
+ // 3. Map các items thành mảng options, deduplicate theo value
2437
+ const seen = new Set();
2438
+ const options = [];
2439
+
2440
+ if (Array.isArray(mdData)) {
2441
+ mdData.forEach((item) => {
2442
+ if (!item) return;
2443
+
2444
+ const val = resolveFieldValue(item, valueName, lang);
2445
+ const lbl = resolveFieldValue(item, labelName, lang);
2446
+
2447
+ if (val !== undefined && val !== null && val !== "") {
2448
+ if (!seen.has(val)) {
2449
+ seen.add(val);
2450
+ options.push({
2451
+ ...item,
2452
+ value: val,
2453
+ label: lbl !== undefined && lbl !== null && lbl !== "" ? lbl : val,
2454
+ userStatus: 1,
2455
+ item,
2456
+ });
2457
+ }
2458
+ }
2459
+ });
2460
+ }
2461
+
2462
+ // 4. Map options vào field dropdown (tự động nhận diện Master Detail hoặc DynamicForm)
2463
+ const fnMD =
2464
+ (typeof window !== "undefined" && window.mapDropDownValueIntoPropertyMD) ||
2465
+ (typeof mapDropDownValueIntoPropertyMD === "function"
2466
+ ? mapDropDownValueIntoPropertyMD
2467
+ : null);
2468
+
2469
+ const fnForm =
2470
+ (typeof window !== "undefined" && window.mapDropDownValueIntoProperty) ||
2471
+ (typeof mapDropDownValueIntoProperty === "function"
2472
+ ? mapDropDownValueIntoProperty
2473
+ : null);
2474
+
2475
+ const isTargetMD =
2476
+ Boolean(isMD) ||
2477
+ (typeof isMasterDetail !== "undefined" && Boolean(isMasterDetail)) ||
2478
+ (typeof masterDetailIndex !== "undefined" && masterDetailIndex !== null);
2479
+
2480
+ if (isTargetMD) {
2481
+ if (fnMD) {
2482
+ fnMD(toName, options);
2483
+ } else if (fnForm) {
2484
+ fnForm(toName, options);
2485
+ }
2486
+ } else {
2487
+ // Thử map vào DynamicForm trước
2488
+ if (fnForm) {
2489
+ fnForm(toName, options);
2490
+ }
2491
+ // Fallback sang Master Detail nếu target chứa '_' hoặc không có element trên Form
2492
+ if (
2493
+ fnMD &&
2494
+ toName &&
2495
+ (toName.includes("_") ||
2496
+ (typeof document !== "undefined" &&
2497
+ !document.getElementById(`field_${toName}`) &&
2498
+ !document.getElementById(toName)))
2499
+ ) {
2500
+ fnMD(toName, options);
2501
+ }
2502
+ }
2503
+ }
2504
+
2505
+ /**
2506
+ * Kích hoạt cập nhật options cho tất cả các mapping phụ thuộc vào Master Detail nguồn
2507
+ * @param {string} [fromMasterDetailName] - Tên Master Detail nguồn, nếu không truyền sẽ trigger toàn bộ
2508
+ */
2509
+ function triggerMasterDetailOptionMappings(fromMasterDetailName) {
2510
+ if (
2511
+ typeof window === "undefined" ||
2512
+ !window.masterDetailOptionMappings ||
2513
+ window.masterDetailOptionMappings.size === 0
2514
+ ) {
2515
+ return;
2516
+ }
2517
+ window.masterDetailOptionMappings.forEach((mapping) => {
2518
+ if (
2519
+ !fromMasterDetailName ||
2520
+ mapping.fromMasterDetailName === fromMasterDetailName
2521
+ ) {
2522
+ try {
2523
+ applyMasterDetailOptionMapping(mapping);
2524
+ } catch (err) {
2525
+ console.error(
2526
+ "triggerMasterDetailOptionMappings error for mapping:",
2527
+ mapping,
2528
+ err,
2529
+ );
2530
+ }
2531
+ }
2532
+ });
2533
+ }
2534
+
2535
+ /**
2536
+ * Quét toàn bộ scripts trong workflow để phát hiện các hàm mapOptionsFromMasterDetail
2537
+ * và đăng ký trước vào Registry, giúp tự động hiển thị options ngay khi tải lại trang
2538
+ * @param {any} workflow - Model workflow cần quét
2539
+ */
2540
+ function scanAndRegisterMasterDetailOptionMappings(workflow) {
2541
+ if (!workflow || typeof window === "undefined") return;
2542
+ try {
2543
+ const scriptsToScan = [];
2544
+
2545
+ // 1. Quét properties của workflow
2546
+ if (Array.isArray(workflow.properties)) {
2547
+ workflow.properties.forEach((prop) => {
2548
+ if (prop && prop.executeJS) {
2549
+ scriptsToScan.push({ script: prop.executeJS, target: prop.name });
2550
+ }
2551
+ });
2552
+ }
2553
+
2554
+ // 2. Quét workflowSteps nếu có
2555
+ if (Array.isArray(workflow.workflowSteps)) {
2556
+ workflow.workflowSteps.forEach((step) => {
2557
+ if (Array.isArray(step.stepFields)) {
2558
+ step.stepFields.forEach((field) => {
2559
+ if (field && field.executeJS) {
2560
+ scriptsToScan.push({
2561
+ script: field.executeJS,
2562
+ target: field.name,
2563
+ });
2564
+ }
2565
+ });
2566
+ }
2567
+ });
2568
+ }
2569
+
2570
+ // 3. Quét masterDetails nếu có
2571
+ if (Array.isArray(workflow.masterDetails)) {
2572
+ workflow.masterDetails.forEach((md) => {
2573
+ const checkRows = (rows) => {
2574
+ if (Array.isArray(rows)) {
2575
+ rows.forEach((r) => {
2576
+ const cols = Array.isArray(r) ? r : r.row || [];
2577
+ cols.forEach((col) => {
2578
+ if (col && (col.executeJs || col.executeJS)) {
2579
+ scriptsToScan.push({
2580
+ script: col.executeJs || col.executeJS,
2581
+ target: col.name,
2582
+ isMD: true,
2583
+ });
2584
+ }
2585
+ });
2586
+ });
2587
+ }
2588
+ };
2589
+ checkRows(md.masterDetailRows);
2590
+ checkRows(md.row);
2591
+ });
2592
+ }
2593
+
2594
+ const regex =
2595
+ /mapOptionsFromMasterDetail\s*\(\s*(['"][^'"]+['"])\s*,\s*(['"][^'"]+['"])\s*,\s*(['"][^'"]+['"])(?:\s*,\s*(['"][^'"]+['"]))?(?:\s*,\s*(true|false))?\s*\)/g;
2596
+
2597
+ scriptsToScan.forEach(({ script, target, isMD }) => {
2598
+ let match;
2599
+ regex.lastIndex = 0;
2600
+ while ((match = regex.exec(script)) !== null) {
2601
+ const fromMD = match[1].replace(/['"]/g, "").trim();
2602
+ const valName = match[2].replace(/['"]/g, "").trim();
2603
+ const lblName = match[3].replace(/['"]/g, "").trim();
2604
+ const explicitTarget = match[4]
2605
+ ? match[4].replace(/['"]/g, "").trim()
2606
+ : target;
2607
+ const explicitIsMD = match[5] ? match[5] === "true" : isMD;
2608
+
2609
+ if (fromMD && explicitTarget) {
2610
+ registerMasterDetailOptionMapping(
2611
+ fromMD,
2612
+ valName,
2613
+ lblName,
2614
+ explicitTarget,
2615
+ explicitIsMD,
2616
+ );
2617
+
2618
+ // Nếu MD nguồn đã có data sẵn thì map ngay
2619
+ if (
2620
+ (window.masterDetailData &&
2621
+ window.masterDetailData[fromMD] &&
2622
+ window.masterDetailData[fromMD].length > 0) ||
2623
+ (typeof window.getMasterDetailData === "function" &&
2624
+ window.getMasterDetailData(fromMD)?.length > 0)
2625
+ ) {
2626
+ triggerMasterDetailOptionMappings(fromMD);
2627
+ }
2628
+ }
2629
+ }
2630
+ });
2631
+ } catch (e) {
2632
+ console.error("scanAndRegisterMasterDetailOptionMappings error:", e);
2633
+ }
2634
+ }
2635
+
2636
+ /**
2637
+ * Trích xuất dữ liệu từ một Master Detail và ánh xạ thành danh sách Options (value, label)
2638
+ * cho trường dropdown trên DynamicForm hoặc Master Detail khác.
2639
+ * Đồng thời tự động đăng ký vào Registry để tự động đồng bộ khi Master Detail nguồn có dữ liệu mới.
2640
+ *
2641
+ * @param {string} fromMasterDetailName - Tên Master Detail nguồn (ví dụ: "DS_SanPham")
2642
+ * @param {string | ((item: Record<string, any>, lang: string) => any)} valueName - Tên field hoặc function resolver lấy value
2643
+ * @param {string | ((item: Record<string, any>, lang: string) => any)} labelName - Tên field hoặc function resolver lấy label
2644
+ * @param {string} [toName] - Tên trường dropdown đích trên DynamicForm hoặc MasterDetail (mặc định lấy trường đang chạy script)
2645
+ * @param {boolean} [isMD] - Chỉ định rõ ràng đích đến là Master Detail (nếu không truyền sẽ tự động nhận diện)
2646
+ */
2647
+ function mapOptionsFromMasterDetail(
2648
+ fromMasterDetailName,
2649
+ valueName,
2650
+ labelName,
2651
+ toName,
2652
+ isMD,
2653
+ ) {
2654
+ try {
2655
+ const target =
2656
+ toName ||
2657
+ (typeof colName !== "undefined" ? colName : undefined) ||
2658
+ (typeof getCurrentId === "function" ? getCurrentId() : undefined);
2659
+
2660
+ if (!target) {
2661
+ console.warn(
2662
+ "mapOptionsFromMasterDetail: target field (toName) is required.",
2663
+ );
2664
+ return;
2665
+ }
2666
+
2667
+ if (!fromMasterDetailName) {
2668
+ console.warn(
2669
+ "mapOptionsFromMasterDetail: fromMasterDetailName is required.",
2670
+ );
2671
+ return;
2672
+ }
2673
+
2674
+ // Luôn đăng ký vào Registry để tự động kích hoạt lại khi Master Detail nguồn cập nhật dữ liệu
2675
+ registerMasterDetailOptionMapping(
2676
+ fromMasterDetailName,
2677
+ valueName,
2678
+ labelName,
2679
+ target,
2680
+ isMD,
2681
+ );
2682
+
2683
+ // Tiến hành ánh xạ dữ liệu
2684
+ applyMasterDetailOptionMapping({
2685
+ fromMasterDetailName,
2686
+ valueName,
2687
+ labelName,
2688
+ toName: target,
2689
+ isMD,
2690
+ });
2691
+ } catch (e) {
2692
+ console.error("mapOptionsFromMasterDetail error:", e);
2693
+ }
2694
+ }
2695
+
2696
+ // Expose ra window để đảm bảo gọi được ở mọi nơi
2697
+ window.resolveFieldValue = resolveFieldValue;
2698
+ window.mapOptionsFromMasterDetail = mapOptionsFromMasterDetail;
2699
+ window.registerMasterDetailOptionMapping = registerMasterDetailOptionMapping;
2700
+ window.applyMasterDetailOptionMapping = applyMasterDetailOptionMapping;
2701
+ window.triggerMasterDetailOptionMappings = triggerMasterDetailOptionMappings;
2702
+ window.scanAndRegisterMasterDetailOptionMappings =
2703
+ scanAndRegisterMasterDetailOptionMappings;
2704
+
2705
+ /**
2706
+ * Khi xóa bất kỳ dòng nào trong Master Detail có liên quan đến mapOptions,
2707
+ * tìm tất cả các trường đang sử dụng options được trích xuất từ Master Detail này
2708
+ * và set giá trị của chúng về undefined.
2709
+ * @param {string} fromMasterDetailName - Tên Master Detail nguồn
2710
+ * @param {any} [formInstance] - Form chính (userWorkflowForm)
2711
+ */
2712
+ function resetDependentFieldsOfMasterDetail(
2713
+ fromMasterDetailName,
2714
+ formInstance,
2715
+ ) {
2716
+ if (!fromMasterDetailName || typeof window === "undefined") return;
2717
+ try {
2718
+ if (
2719
+ window.masterDetailOptionMappings &&
2720
+ window.masterDetailOptionMappings.size > 0
2721
+ ) {
2722
+ window.masterDetailOptionMappings.forEach((mapping) => {
2723
+ if (
2724
+ mapping &&
2725
+ mapping.fromMasterDetailName === fromMasterDetailName &&
2726
+ mapping.toName
2727
+ ) {
2728
+ const target = mapping.toName;
2729
+ const targetForm =
2730
+ formInstance ||
2731
+ (typeof window.userWorkflowForm !== "undefined"
2732
+ ? window.userWorkflowForm
2733
+ : null);
2734
+
2735
+ if (targetForm && typeof targetForm.setFieldValue === "function") {
2736
+ targetForm.setFieldValue(target, undefined);
2737
+ }
2738
+ }
2739
+ });
2740
+ }
2741
+ } catch (err) {
2742
+ console.error("resetDependentFieldsOfMasterDetail error:", err);
2743
+ }
2744
+ }
2745
+
2746
+ window.resetDependentFieldsOfMasterDetail = resetDependentFieldsOfMasterDetail;