@ironcode/vas-lib 2.3.0 → 3.0.0-alpha.10

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 (143) hide show
  1. package/cjs/lib/control-value/vas-camera-control-value.model.d.ts +6 -2
  2. package/cjs/lib/control-value/vas-camera-control-value.model.d.ts.map +1 -1
  3. package/cjs/lib/control-value/vas-camera-control-value.model.js +10 -0
  4. package/cjs/lib/control-value/vas-camera-control-value.model.js.map +1 -1
  5. package/cjs/lib/entity/vas-base.model.d.ts +1 -1
  6. package/cjs/lib/entity/vas-base.model.d.ts.map +1 -1
  7. package/cjs/lib/entity/vas-base.model.js.map +1 -1
  8. package/cjs/lib/entity/vas-control.model.d.ts +4 -4
  9. package/cjs/lib/entity/vas-control.model.d.ts.map +1 -1
  10. package/cjs/lib/entity/vas-control.model.js +1 -1
  11. package/cjs/lib/entity/vas-control.model.js.map +1 -1
  12. package/cjs/lib/entity/vas-field.dto.d.ts +4 -7
  13. package/cjs/lib/entity/vas-field.dto.d.ts.map +1 -1
  14. package/cjs/lib/entity/vas-field.dto.js.map +1 -1
  15. package/cjs/lib/entity/vas-field.model.d.ts +4 -4
  16. package/cjs/lib/entity/vas-field.model.d.ts.map +1 -1
  17. package/cjs/lib/entity/vas-field.model.js +1 -1
  18. package/cjs/lib/entity/vas-field.model.js.map +1 -1
  19. package/cjs/lib/entity/vas-form.model.d.ts +4 -4
  20. package/cjs/lib/entity/vas-form.model.d.ts.map +1 -1
  21. package/cjs/lib/entity/vas-form.model.js +1 -1
  22. package/cjs/lib/entity/vas-form.model.js.map +1 -1
  23. package/cjs/lib/entity/vas-group.model.d.ts +2 -2
  24. package/cjs/lib/entity/vas-group.model.d.ts.map +1 -1
  25. package/cjs/lib/entity/vas-group.model.js.map +1 -1
  26. package/cjs/lib/entity/vas-job-copy.d.ts +12 -12
  27. package/cjs/lib/entity/vas-job-copy.d.ts.map +1 -1
  28. package/cjs/lib/entity/vas-job-copy.js.map +1 -1
  29. package/cjs/lib/entity/vas-job.dto.d.ts +12 -6
  30. package/cjs/lib/entity/vas-job.dto.d.ts.map +1 -1
  31. package/cjs/lib/entity/vas-job.dto.js.map +1 -1
  32. package/cjs/lib/entity/vas-job.model.d.ts +34 -142
  33. package/cjs/lib/entity/vas-job.model.d.ts.map +1 -1
  34. package/cjs/lib/entity/vas-job.model.js +248 -341
  35. package/cjs/lib/entity/vas-job.model.js.map +1 -1
  36. package/cjs/lib/model/estimate-control/estimate-control-value.dto.d.ts +14 -0
  37. package/cjs/lib/model/estimate-control/estimate-control-value.dto.d.ts.map +1 -0
  38. package/cjs/lib/model/estimate-control/estimate-control-value.dto.js +3 -0
  39. package/cjs/lib/model/estimate-control/estimate-control-value.dto.js.map +1 -0
  40. package/cjs/lib/model/estimate-control/estimate-control-value.model.d.ts +11 -0
  41. package/cjs/lib/model/estimate-control/estimate-control-value.model.d.ts.map +1 -0
  42. package/cjs/lib/model/estimate-control/estimate-control-value.model.js +84 -0
  43. package/cjs/lib/model/estimate-control/estimate-control-value.model.js.map +1 -0
  44. package/cjs/lib/model/estimate-control/estimate-item.dto.d.ts +16 -0
  45. package/cjs/lib/model/estimate-control/estimate-item.dto.d.ts.map +1 -0
  46. package/cjs/lib/model/estimate-control/estimate-item.dto.js +3 -0
  47. package/cjs/lib/model/estimate-control/estimate-item.dto.js.map +1 -0
  48. package/cjs/lib/model/estimate-control/estimate-item.model.d.ts +24 -0
  49. package/cjs/lib/model/estimate-control/estimate-item.model.d.ts.map +1 -0
  50. package/cjs/lib/model/estimate-control/estimate-item.model.js +121 -0
  51. package/cjs/lib/model/estimate-control/estimate-item.model.js.map +1 -0
  52. package/cjs/lib/model/estimate-control/estimate.dto.d.ts +37 -0
  53. package/cjs/lib/model/estimate-control/estimate.dto.d.ts.map +1 -0
  54. package/cjs/lib/model/estimate-control/estimate.dto.js +3 -0
  55. package/cjs/lib/model/estimate-control/estimate.dto.js.map +1 -0
  56. package/cjs/lib/model/estimate-control/estimate.model.d.ts +55 -0
  57. package/cjs/lib/model/estimate-control/estimate.model.d.ts.map +1 -0
  58. package/cjs/lib/model/estimate-control/estimate.model.js +279 -0
  59. package/cjs/lib/model/estimate-control/estimate.model.js.map +1 -0
  60. package/cjs/lib/model/estimate-control/estimate.types.d.ts +3 -0
  61. package/cjs/lib/model/estimate-control/estimate.types.d.ts.map +1 -0
  62. package/cjs/lib/model/estimate-control/estimate.types.js +3 -0
  63. package/cjs/lib/model/estimate-control/estimate.types.js.map +1 -0
  64. package/cjs/lib/model/estimate-control/index.d.ts +8 -0
  65. package/cjs/lib/model/estimate-control/index.d.ts.map +1 -0
  66. package/cjs/lib/model/estimate-control/index.js +11 -0
  67. package/cjs/lib/model/estimate-control/index.js.map +1 -0
  68. package/cjs/lib/model/firebase-document-meta.interface.d.ts +6 -0
  69. package/cjs/lib/model/firebase-document-meta.interface.d.ts.map +1 -0
  70. package/cjs/lib/model/firebase-document-meta.interface.js +3 -0
  71. package/cjs/lib/model/firebase-document-meta.interface.js.map +1 -0
  72. package/cjs/lib/model/index.d.ts +3 -0
  73. package/cjs/lib/model/index.d.ts.map +1 -1
  74. package/cjs/lib/model/index.js +3 -0
  75. package/cjs/lib/model/index.js.map +1 -1
  76. package/cjs/lib/model/report-invocation.d.ts +40 -0
  77. package/cjs/lib/model/report-invocation.d.ts.map +1 -0
  78. package/cjs/lib/model/report-invocation.js +3 -0
  79. package/cjs/lib/model/report-invocation.js.map +1 -0
  80. package/cjs/lib/utils/get-value-by-path.d.ts +5 -16
  81. package/cjs/lib/utils/get-value-by-path.d.ts.map +1 -1
  82. package/cjs/lib/utils/get-value-by-path.js +48 -59
  83. package/cjs/lib/utils/get-value-by-path.js.map +1 -1
  84. package/cjs/lib/utils/index.d.ts +8 -0
  85. package/cjs/lib/utils/index.d.ts.map +1 -0
  86. package/cjs/lib/utils/index.js +11 -0
  87. package/cjs/lib/utils/index.js.map +1 -0
  88. package/cjs/lib/utils/is-array-string.d.ts +3 -0
  89. package/cjs/lib/utils/is-array-string.d.ts.map +1 -1
  90. package/cjs/lib/utils/is-array-string.js +3 -0
  91. package/cjs/lib/utils/is-array-string.js.map +1 -1
  92. package/cjs/lib/utils/is-numeric.d.ts +2 -0
  93. package/cjs/lib/utils/is-numeric.d.ts.map +1 -0
  94. package/cjs/lib/utils/is-numeric.js +10 -0
  95. package/cjs/lib/utils/is-numeric.js.map +1 -0
  96. package/cjs/lib/utils/is-plain-object.d.ts +5 -0
  97. package/cjs/lib/utils/is-plain-object.d.ts.map +1 -0
  98. package/cjs/lib/utils/is-plain-object.js +10 -0
  99. package/cjs/lib/utils/is-plain-object.js.map +1 -0
  100. package/cjs/lib/utils/try-json.d.ts +6 -0
  101. package/cjs/lib/utils/try-json.d.ts.map +1 -0
  102. package/cjs/lib/utils/try-json.js +20 -0
  103. package/cjs/lib/utils/try-json.js.map +1 -0
  104. package/cjs/public-api.d.ts +1 -0
  105. package/cjs/public-api.d.ts.map +1 -1
  106. package/cjs/public-api.js +1 -0
  107. package/cjs/public-api.js.map +1 -1
  108. package/fesm2022/ironcode-vas-lib.mjs +831 -390
  109. package/fesm2022/ironcode-vas-lib.mjs.map +1 -1
  110. package/lib/control-value/vas-camera-control-value.model.d.ts +6 -2
  111. package/lib/entity/vas-base.model.d.ts +1 -1
  112. package/lib/entity/vas-control.model.d.ts +4 -4
  113. package/lib/entity/vas-field.dto.d.ts +4 -7
  114. package/lib/entity/vas-field.model.d.ts +4 -4
  115. package/lib/entity/vas-form.model.d.ts +4 -4
  116. package/lib/entity/vas-group.model.d.ts +2 -2
  117. package/lib/entity/vas-job-copy.d.ts +12 -12
  118. package/lib/entity/vas-job.dto.d.ts +12 -6
  119. package/lib/entity/vas-job.model.d.ts +34 -142
  120. package/lib/model/estimate-control/estimate-control-value.dto.d.ts +13 -0
  121. package/lib/model/estimate-control/estimate-control-value.model.d.ts +10 -0
  122. package/lib/model/estimate-control/estimate-item.dto.d.ts +15 -0
  123. package/lib/model/estimate-control/estimate-item.model.d.ts +23 -0
  124. package/lib/model/estimate-control/estimate.dto.d.ts +36 -0
  125. package/lib/model/estimate-control/estimate.model.d.ts +54 -0
  126. package/lib/model/estimate-control/estimate.types.d.ts +2 -0
  127. package/lib/model/estimate-control/index.d.ts +7 -0
  128. package/lib/model/firebase-document-meta.interface.d.ts +5 -0
  129. package/lib/model/index.d.ts +3 -0
  130. package/lib/model/report-invocation.d.ts +39 -0
  131. package/lib/utils/get-value-by-path.d.ts +5 -16
  132. package/lib/utils/index.d.ts +7 -0
  133. package/lib/utils/is-array-string.d.ts +3 -0
  134. package/lib/utils/is-numeric.d.ts +1 -0
  135. package/lib/utils/is-plain-object.d.ts +4 -0
  136. package/lib/utils/omit.d.ts +7 -0
  137. package/lib/utils/try-json.d.ts +5 -0
  138. package/package.json +3 -2
  139. package/public-api.d.ts +1 -0
  140. package/cjs/lib/entity/vas-job.model.spec-data.d.ts +0 -2
  141. package/cjs/lib/entity/vas-job.model.spec-data.d.ts.map +0 -1
  142. package/cjs/lib/entity/vas-job.model.spec-data.js +0 -29
  143. package/cjs/lib/entity/vas-job.model.spec-data.js.map +0 -1
@@ -1,5 +1,7 @@
1
1
  import { UUID } from 'angular2-uuid';
2
2
  import moment from 'moment';
3
+ import { Big } from 'big.js';
4
+ import { v4 } from 'uuid';
3
5
 
4
6
  function isCameraControlValueV1(object) {
5
7
  return object instanceof Object
@@ -8,6 +10,15 @@ function isCameraControlValueV1(object) {
8
10
  && 'url' in object
9
11
  && 'id' in object;
10
12
  }
13
+ function isCameraControlValueV2(object) {
14
+ return object instanceof Object
15
+ && 'id' in object
16
+ && 'name' in object
17
+ && 'fileSize' in object
18
+ && 'mimeType' in object
19
+ && 'sequence' in object
20
+ && 'config' in object;
21
+ }
11
22
 
12
23
  var VasControlConfigDirection;
13
24
  (function (VasControlConfigDirection) {
@@ -1341,7 +1352,7 @@ class VasControlModel extends VasRestrictedAccountObjectModel {
1341
1352
  }
1342
1353
  /**
1343
1354
  * @param {any} value the value to be parsed
1344
- * @return {VasFieldDtoValue} the parsed value
1355
+ * @return {FieldValueType} the parsed value
1345
1356
  */
1346
1357
  parseMaybeJson(value) {
1347
1358
  if (typeof value !== 'string') {
@@ -1528,7 +1539,7 @@ class VasFieldModel extends VasJobDataModel {
1528
1539
  * @return {VasFieldModel}
1529
1540
  */
1530
1541
  static fromDto(fieldDto) {
1531
- return new VasFieldModel(fieldDto.id || '', fieldDto.created || '', fieldDto.serverCreated || '', fieldDto.createdBy || '', fieldDto.modified || '', fieldDto.serverModified || '', fieldDto.modifiedBy || '', fieldDto.createdByName || '', fieldDto.modifiedByName || '', fieldDto.account || '', fieldDto.job || '', fieldDto.control || '', fieldDto.fieldJobPointers || [], fieldDto.value || '', fieldDto.version || 0, fieldDto.controlName || '', fieldDto.controlTitle || '', fieldDto.controlTypeName || '');
1542
+ return new VasFieldModel(fieldDto.id || '', fieldDto.created || '', fieldDto.serverCreated || '', fieldDto.createdBy || '', fieldDto.modified || '', fieldDto.serverModified || '', fieldDto.modifiedBy || '', fieldDto.createdByName || '', fieldDto.modifiedByName || '', fieldDto.account || '', fieldDto.job || '', fieldDto.control || '', fieldDto.fieldJobPointers || [], fieldDto.value !== undefined ? fieldDto.value : null, fieldDto.version || 0, fieldDto.controlName || '', fieldDto.controlTitle || '', fieldDto.controlTypeName || '');
1532
1543
  }
1533
1544
  /**
1534
1545
  * @return {VasFieldDto}
@@ -2250,7 +2261,7 @@ class VasFormModel extends VasRestrictedAccountObjectModel {
2250
2261
  }
2251
2262
  /**
2252
2263
  * @param {VasJobModel} jobModel
2253
- * @return {Array<{id: string, value: VasFieldDtoValue}>}
2264
+ * @return {Array<{id: string, value: FieldValueType}>}
2254
2265
  */
2255
2266
  getValuesByControlId(jobModel) {
2256
2267
  const items = [];
@@ -3249,69 +3260,541 @@ class VasJobTypeModel extends VasAccountObjectModel {
3249
3260
  }
3250
3261
  }
3251
3262
 
3263
+ function isNumeric(value) {
3264
+ if (typeof value === 'number') {
3265
+ return !isNaN(value);
3266
+ }
3267
+ return /^-?\d+$/.test(value.trim());
3268
+ }
3269
+
3270
+ function coalescePath(pathOrPaths) {
3271
+ let keys = [];
3272
+ if (typeof pathOrPaths === 'string') {
3273
+ keys = pathOrPaths.split('.');
3274
+ }
3275
+ else if (typeof pathOrPaths === 'number') {
3276
+ keys = [pathOrPaths];
3277
+ }
3278
+ else if (Array.isArray(pathOrPaths)) {
3279
+ keys = pathOrPaths;
3280
+ }
3281
+ else {
3282
+ throw Error('Expected string, number or array of strings or numbers');
3283
+ }
3284
+ const result = [];
3285
+ keys.forEach(key => {
3286
+ if (typeof key === 'number') {
3287
+ result.push(key);
3288
+ return;
3289
+ }
3290
+ if (key.endsWith(']')) {
3291
+ const match = /^([\w_#$-]+)\[([\w_#$-]+)]$/.exec(key.toString());
3292
+ if (match !== null) {
3293
+ result.push(match[1]);
3294
+ result.push(isNumeric(match[2]) ? parseInt(match[2], 10) : match[2]);
3295
+ return;
3296
+ }
3297
+ }
3298
+ result.push(key);
3299
+ });
3300
+ return result;
3301
+ }
3252
3302
  /**
3253
- * A non sophisticated way to get values from the job via paths. For example:
3254
- *
3255
- * getValueByPath(
3256
- * ['foo', 'bar'],
3257
- * {
3258
- * foo: {
3259
- * bar: 'value'
3260
- * }
3261
- * }
3262
- * );
3303
+ * Get a value from an object by path.
3263
3304
  *
3264
- * will return "value"
3265
- * @param {string[]} path
3266
- * @param {Record<string, VasFieldDtoValue>} object
3267
- * @return {T | undefined}
3305
+ * Path can be an array of strings or numbers.
3306
+ * Path can be a string using dot notation, which can include square brackets to
3307
+ * access array positions e.g. foo.bar[0].baz
3268
3308
  */
3269
- const getValueByPath = (path, object) => {
3270
- switch (path.length) {
3271
- case 0: {
3272
- return;
3309
+ function getValueByPath(path, object) {
3310
+ const p = coalescePath(path);
3311
+ let scope = object === undefined ? null : object;
3312
+ let index = 0;
3313
+ const length = p.length;
3314
+ while (index < length) {
3315
+ if (scope === undefined || scope === null) {
3316
+ return undefined;
3273
3317
  }
3274
- case 1: {
3275
- return (object)[path[0]];
3318
+ scope = scope[p[index++]];
3319
+ }
3320
+ return scope;
3321
+ }
3322
+
3323
+ const b$2 = (value = '0') => {
3324
+ try {
3325
+ if (typeof value === 'string') {
3326
+ return new Big(value.replace(/[^\d^.]/g, ''));
3276
3327
  }
3277
- case 2: {
3278
- const val0 = object[path[0]];
3279
- if (!val0) {
3280
- return undefined;
3281
- }
3282
- return val0[path[1]];
3328
+ return new Big(value);
3329
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3330
+ }
3331
+ catch (e) {
3332
+ console.error(`failed to construct Big from value '${value}'`);
3333
+ return new Big('0');
3334
+ }
3335
+ };
3336
+ class EstimateItemModel {
3337
+ id;
3338
+ description;
3339
+ type;
3340
+ quantity;
3341
+ quantityAdjusted;
3342
+ units;
3343
+ unitsAdjusted;
3344
+ price;
3345
+ priceAdjusted;
3346
+ workCategory;
3347
+ costCategory;
3348
+ comment;
3349
+ constructor(id, description, type, quantity, quantityAdjusted, units, unitsAdjusted, price, priceAdjusted, workCategory, costCategory, comment) {
3350
+ this.id = id;
3351
+ this.description = description;
3352
+ this.type = type;
3353
+ this.quantity = quantity;
3354
+ this.quantityAdjusted = quantityAdjusted;
3355
+ this.units = units;
3356
+ this.unitsAdjusted = unitsAdjusted;
3357
+ this.price = price;
3358
+ this.priceAdjusted = priceAdjusted;
3359
+ this.workCategory = workCategory;
3360
+ this.costCategory = costCategory;
3361
+ this.comment = comment;
3362
+ }
3363
+ getTotalUnits(adjusted = false) {
3364
+ return adjusted ?
3365
+ b$2(this.quantityAdjusted || '0').mul(this.unitsAdjusted || '0').toFixed(2) :
3366
+ b$2(this.quantity || '0').mul(this.units || '0').toFixed(2);
3367
+ }
3368
+ getLineValue(labourRate = '1', timeBasis = '1', adjusted = false) {
3369
+ const cat = this.costCategory || 'none';
3370
+ if (this.workCategory === 'removed') {
3371
+ return '0';
3283
3372
  }
3284
- case 3: {
3285
- const val0 = object[path[0]];
3286
- if (!val0) {
3287
- return undefined;
3288
- }
3289
- const val1 = val0[path[1]];
3290
- if (!val1) {
3291
- return undefined;
3373
+ const units = (adjusted ? this.unitsAdjusted : this.units) || 0;
3374
+ const quantity = (adjusted ? this.quantityAdjusted : this.quantity) || 0;
3375
+ const price = (adjusted ? this.priceAdjusted : this.price) || '';
3376
+ try {
3377
+ switch (cat) {
3378
+ case 'labour': {
3379
+ return price != '' ?
3380
+ price :
3381
+ b$2(quantity || '0')
3382
+ .mul(units || '0')
3383
+ .mul(labourRate || '0')
3384
+ .div(timeBasis)
3385
+ .toFixed(2);
3386
+ }
3387
+ case 'pm': {
3388
+ return b$2(price || '0')
3389
+ .mul(quantity || '0')
3390
+ .toFixed(2);
3391
+ }
3392
+ case 'parts': {
3393
+ return b$2(price || '0')
3394
+ .mul(quantity || '0')
3395
+ .toFixed(2);
3396
+ }
3397
+ case 'specialist': {
3398
+ return b$2(price || '0')
3399
+ .mul(quantity || '0')
3400
+ .toFixed(2);
3401
+ }
3402
+ default: {
3403
+ return '0';
3404
+ }
3292
3405
  }
3293
- return val1[path[2]];
3294
3406
  }
3295
- case 4: {
3296
- const val0 = object[path[0]];
3297
- if (!val0) {
3298
- return undefined;
3299
- }
3300
- const val1 = val0[path[1]];
3301
- if (!val1) {
3302
- return undefined;
3303
- }
3304
- const val2 = val1[path[2]];
3305
- if (!val2) {
3306
- return undefined;
3307
- }
3308
- return val2[path[3]];
3407
+ catch (e) {
3408
+ console.warn(e);
3309
3409
  }
3310
- default: {
3311
- throw Error('path has too many segments');
3410
+ return '0';
3411
+ }
3412
+ toDto() {
3413
+ return {
3414
+ id: this.id,
3415
+ description: this.description,
3416
+ type: this.type,
3417
+ quantity: this.quantity,
3418
+ quantityAdjusted: this.quantityAdjusted,
3419
+ units: this.units,
3420
+ unitsAdjusted: this.unitsAdjusted,
3421
+ price: this.price,
3422
+ priceAdjusted: this.priceAdjusted,
3423
+ workCategory: this.workCategory,
3424
+ costCategory: this.costCategory,
3425
+ comment: this.comment
3426
+ };
3427
+ }
3428
+ static create() {
3429
+ return new EstimateItemModel(v4(), '', '', 1, 1, 0, 0, '', '', 'none', 'none', '');
3430
+ }
3431
+ static fromDto(dto) {
3432
+ return new EstimateItemModel(dto.id || v4(), dto.description || '', dto.type || '', dto.quantity || 0, dto.quantityAdjusted || 0, dto.units || 0, dto.unitsAdjusted || 0, dto.price || '', dto.priceAdjusted || '', dto.workCategory || 'none', dto.costCategory || 'none', dto.comment || '');
3433
+ }
3434
+ static equal(a, b) {
3435
+ return (a?.id === b?.id &&
3436
+ a?.description === b?.description &&
3437
+ a?.type === b?.type &&
3438
+ a?.quantity === b?.quantity &&
3439
+ a?.quantityAdjusted === b?.quantityAdjusted &&
3440
+ a?.units === b?.units &&
3441
+ a?.unitsAdjusted === b?.unitsAdjusted &&
3442
+ a?.price === b?.price &&
3443
+ a?.priceAdjusted === b?.priceAdjusted &&
3444
+ a?.workCategory === b?.workCategory &&
3445
+ a?.costCategory === b?.costCategory &&
3446
+ a?.comment === b?.comment);
3447
+ }
3448
+ }
3449
+
3450
+ const b$1 = (value = '0') => {
3451
+ try {
3452
+ if (typeof value === 'string') {
3453
+ return new Big(value.replace(/[^\d^.]/g, ''));
3312
3454
  }
3455
+ return new Big(value);
3456
+ }
3457
+ catch (e) {
3458
+ console.error(`failed to construct Big from value '${value}'`);
3459
+ return new Big('0');
3313
3460
  }
3314
3461
  };
3462
+ function isLabourWorkCategory(value) {
3463
+ return ['check', 'rr', 'repair', 'removed'].includes(value);
3464
+ }
3465
+ function isPaintWorkCategory(value) {
3466
+ return ['blend', 'paint'].includes(value);
3467
+ }
3468
+ class EstimateModel {
3469
+ id = v4();
3470
+ source = 'manual';
3471
+ label = '';
3472
+ comment = '';
3473
+ timestamp = moment().toISOString();
3474
+ version = 1;
3475
+ net = '0.00';
3476
+ vat = '0.00';
3477
+ gross = '0.00';
3478
+ labourRate = '1';
3479
+ vatRate = '0.00';
3480
+ sundryRate = '0.00';
3481
+ sundryMax = '0.00';
3482
+ sundryFixed = '0.00';
3483
+ sundryMethod = 'fixed';
3484
+ partsDiscount = '0.00';
3485
+ paintDiscount = '0.00';
3486
+ timeBasis = '1';
3487
+ adjusted = false;
3488
+ items = [];
3489
+ summaryNet = {
3490
+ labour: '0.00',
3491
+ none: '0.00',
3492
+ parts: '0.00',
3493
+ pm: '0.00',
3494
+ pl: '0.00',
3495
+ specialist: '0.00',
3496
+ other: '0.00',
3497
+ gross: '0.00',
3498
+ net: '0.00',
3499
+ hours: '0.00',
3500
+ vat: '0.00',
3501
+ labourRate: '0.00',
3502
+ };
3503
+ summary = {
3504
+ labour: '0.00',
3505
+ none: '0.00',
3506
+ parts: '0.00',
3507
+ pm: '0.00',
3508
+ pl: '0.00',
3509
+ specialist: '0.00',
3510
+ other: '0.00',
3511
+ gross: '0.00',
3512
+ net: '0.00',
3513
+ hours: '0.00',
3514
+ vat: '0.00',
3515
+ labourRate: '0.00',
3516
+ };
3517
+ summaryAdjusted = {
3518
+ labour: '0.00',
3519
+ none: '0.00',
3520
+ parts: '0.00',
3521
+ pm: '0.00',
3522
+ pl: '0.00',
3523
+ specialist: '0.00',
3524
+ other: '0.00',
3525
+ gross: '0.00',
3526
+ net: '0.00',
3527
+ hours: '0.00',
3528
+ vat: '0.00',
3529
+ labourRate: '0.00',
3530
+ };
3531
+ get timeBasisSafe() {
3532
+ return (this.timeBasis || '1') === '0' ? '1' : this.timeBasis;
3533
+ }
3534
+ static create(source = 'manual') {
3535
+ const model = new EstimateModel();
3536
+ model.source = source;
3537
+ return model;
3538
+ }
3539
+ static fromDto(dto) {
3540
+ const model = new this();
3541
+ Object.assign(model, {
3542
+ ...dto,
3543
+ items: dto.items?.map(item => EstimateItemModel.fromDto(item)),
3544
+ });
3545
+ model.calculate();
3546
+ return model;
3547
+ }
3548
+ addItem(modelOrDto, opts) {
3549
+ this.items.push(modelOrDto instanceof EstimateItemModel ?
3550
+ modelOrDto :
3551
+ EstimateItemModel.fromDto(modelOrDto));
3552
+ if (!opts || (opts && opts.calculate)) {
3553
+ this.calculate();
3554
+ }
3555
+ }
3556
+ addItems(items, opts) {
3557
+ this.items = this.items.concat(items);
3558
+ if (!opts || (opts && opts.calculate)) {
3559
+ this.calculate();
3560
+ }
3561
+ }
3562
+ calculate() {
3563
+ this.summaryNet = this.getSummaryNet(false);
3564
+ if (this.adjusted) {
3565
+ this.summaryAdjusted = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, this.adjusted);
3566
+ }
3567
+ this.summary = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, this.adjusted);
3568
+ this.net = b$1(0)
3569
+ .add(this.summary.labour)
3570
+ .add(this.summary.none)
3571
+ .add(this.summary.parts)
3572
+ .add(this.summary.pm)
3573
+ .add(this.summary.specialist)
3574
+ .toFixed(2);
3575
+ this.vat = b$1(this.net || '0').mul(this.vatRate || '0').toFixed(2);
3576
+ this.gross = b$1(this.net || '0').add(this.vat || '0').toFixed(2);
3577
+ }
3578
+ /**
3579
+ * Returns the net summary (before sundries and discounts) of the estimate.
3580
+ * By summary, we mean grouping the costs by cost category.
3581
+ *
3582
+ * @param adjusted if the values should be calculated using the original or
3583
+ * adjusted values
3584
+ */
3585
+ getSummaryNet(adjusted = false) {
3586
+ const summary = this.items.reduce((map, item) => {
3587
+ map[item.costCategory] = b$1(map[item.costCategory] || '0')
3588
+ .add(item.getLineValue(this.labourRate, this.timeBasisSafe, adjusted))
3589
+ .toFixed(2);
3590
+ return map;
3591
+ }, {
3592
+ labour: '0.00',
3593
+ none: '0.00',
3594
+ parts: '0.00',
3595
+ pm: '0.00',
3596
+ pl: '0.00',
3597
+ specialist: '0.00',
3598
+ other: '0.00',
3599
+ net: '0.00',
3600
+ vat: '0.00',
3601
+ gross: '0.00',
3602
+ hours: '0.00',
3603
+ labourRate: '0',
3604
+ });
3605
+ if (this.source === 'audatex') {
3606
+ const mechHours = this.items
3607
+ .reduce((h, item) => {
3608
+ return isLabourWorkCategory(item.workCategory) ?
3609
+ h.add(b$1(item.quantity || '0').mul(item.units || '0')) :
3610
+ h;
3611
+ }, b$1())
3612
+ .div(this.timeBasisSafe || '1')
3613
+ .round(2);
3614
+ const paintHours = this.items
3615
+ .reduce((h, item) => {
3616
+ return isPaintWorkCategory(item.workCategory) ?
3617
+ h.add(b$1(item.quantity || '0').mul(item.units || '0')) :
3618
+ h;
3619
+ }, b$1())
3620
+ .div(this.timeBasisSafe || '1')
3621
+ .round(1);
3622
+ const mechCost = mechHours.mul(this.labourRate || '0');
3623
+ const paintCost = paintHours.mul(this.labourRate || '0');
3624
+ summary.labour = mechCost.add(paintCost).toFixed(2);
3625
+ }
3626
+ return summary;
3627
+ }
3628
+ /**
3629
+ * Applies the sundry and discount values to the net summary.
3630
+ */
3631
+ getSummary(summaryNet, sundryRate, sundryMax, sundryFixed, sundryMethod, partsDiscount, paintDiscount, adjusted) {
3632
+ const partsDisc = b$1('1').minus(partsDiscount || '0');
3633
+ const paintDisc = b$1('1').minus(paintDiscount || '0');
3634
+ let partsSundry = '0.00';
3635
+ if (sundryMethod === 'percentage') {
3636
+ partsSundry = b$1(summaryNet.parts).mul(sundryRate || '1').toFixed(2);
3637
+ }
3638
+ else if (sundryMethod === 'percentage-with-max') {
3639
+ partsSundry = b$1(summaryNet.parts).mul(sundryRate || '1').toFixed(2);
3640
+ partsSundry = Math.min(parseFloat(sundryMax || '0'), parseFloat(partsSundry)).toFixed(2);
3641
+ }
3642
+ else if (sundryMethod === 'fixed') {
3643
+ partsSundry = b$1(sundryFixed).toFixed(2);
3644
+ }
3645
+ const parts = b$1(summaryNet.parts).add(partsSundry);
3646
+ const net = b$1(0)
3647
+ .add(summaryNet.labour)
3648
+ .add(summaryNet.none)
3649
+ .add(parts)
3650
+ .add(summaryNet.pm)
3651
+ .add(summaryNet.specialist)
3652
+ .toFixed(2);
3653
+ const vat = b$1(net || '0')
3654
+ .mul(this.vatRate || '0')
3655
+ .toFixed(2);
3656
+ const gross = b$1(net || '0').add(vat || '0').toFixed(2);
3657
+ return {
3658
+ labour: summaryNet.labour,
3659
+ none: summaryNet.none,
3660
+ parts: b$1(parts).mul(partsDisc).toFixed(2),
3661
+ pm: b$1(summaryNet.pm).mul(paintDisc).toFixed(2),
3662
+ pl: summaryNet.pl,
3663
+ specialist: summaryNet.specialist,
3664
+ other: summaryNet.other,
3665
+ net: net,
3666
+ vat: vat,
3667
+ gross: gross,
3668
+ labourRate: this.labourRate,
3669
+ hours: this.getTotalHours(adjusted)
3670
+ };
3671
+ }
3672
+ toDto() {
3673
+ return {
3674
+ id: this.id,
3675
+ source: this.source,
3676
+ label: this.label,
3677
+ comment: this.comment,
3678
+ timestamp: this.timestamp,
3679
+ version: this.version,
3680
+ net: this.net,
3681
+ vat: this.vat,
3682
+ gross: this.gross,
3683
+ labourRate: this.labourRate,
3684
+ vatRate: this.vatRate,
3685
+ sundryRate: this.sundryRate,
3686
+ sundryMax: this.sundryMax,
3687
+ sundryFixed: this.sundryFixed,
3688
+ sundryMethod: this.sundryMethod,
3689
+ partsDiscount: this.partsDiscount,
3690
+ paintDiscount: this.paintDiscount,
3691
+ timeBasis: this.timeBasisSafe,
3692
+ items: this.items.map(i => i.toDto()),
3693
+ adjusted: this.adjusted,
3694
+ summary: this.summary,
3695
+ summaryAdjusted: this.summaryAdjusted
3696
+ };
3697
+ }
3698
+ getTotalHours(adjusted = false) {
3699
+ if (this.source === 'audatex') {
3700
+ const mechUnitsHours = this.items
3701
+ .filter(item => item.workCategory !== 'paint')
3702
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3703
+ .round(2)
3704
+ .div(this.timeBasisSafe);
3705
+ const paintHours = this.items
3706
+ .filter(item => item.workCategory === 'paint')
3707
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3708
+ .round(1)
3709
+ .div(this.timeBasisSafe);
3710
+ return mechUnitsHours.add(paintHours).toFixed(2);
3711
+ }
3712
+ const units = this.items
3713
+ .filter(i => i.costCategory === 'labour')
3714
+ .reduce((p, c) => p.add(b$1(c.getTotalUnits(adjusted))), b$1('0'))
3715
+ .toFixed(2);
3716
+ return b$1(units).div(this.timeBasisSafe).toFixed(2);
3717
+ }
3718
+ }
3719
+
3720
+ const b = (value = '0') => new Big(value);
3721
+ /**
3722
+ * Utility class for computing the summary value for an estimate control given
3723
+ * the first and last estimates
3724
+ */
3725
+ class EstimateControlValueModel {
3726
+ static calculateSummary(original, latest) {
3727
+ const o = original.summary;
3728
+ const l = latest.adjusted ?
3729
+ latest.summaryAdjusted :
3730
+ latest.summary;
3731
+ const s = {
3732
+ labour: b(o.labour).minus(l.labour).toFixed(2),
3733
+ none: b(o.none).minus(l.none).toFixed(2),
3734
+ parts: b(o.parts).minus(l.parts).toFixed(2),
3735
+ pm: b(o.pm).minus(l.pm).toFixed(2),
3736
+ pl: b(o.pm).minus(l.pl).toFixed(2),
3737
+ specialist: b(o.specialist).minus(l.specialist).toFixed(2),
3738
+ other: b(o.other).minus(l.other).toFixed(2),
3739
+ net: b(o.net).minus(l.net).toFixed(2),
3740
+ vat: b(o.vat).minus(l.vat).toFixed(2),
3741
+ gross: b(o.gross).minus(l.gross).toFixed(2),
3742
+ hours: b(o.hours).minus(l.hours).toFixed(0),
3743
+ labourRate: b(o.labourRate).minus(l.labourRate).toFixed(0)
3744
+ };
3745
+ return {
3746
+ original: o,
3747
+ latest: l,
3748
+ savings: s
3749
+ };
3750
+ }
3751
+ static getEmptySummary() {
3752
+ return {
3753
+ original: {
3754
+ labour: '0.00',
3755
+ none: '0.00',
3756
+ parts: '0.00',
3757
+ pm: '0.00',
3758
+ pl: '0.00',
3759
+ specialist: '0.00',
3760
+ other: '0.00',
3761
+ net: '0.00',
3762
+ vat: '0.00',
3763
+ gross: '0.00',
3764
+ hours: '0',
3765
+ labourRate: '0.00'
3766
+ },
3767
+ latest: {
3768
+ labour: '0.00',
3769
+ none: '0.00',
3770
+ parts: '0.00',
3771
+ pm: '0.00',
3772
+ pl: '0.00',
3773
+ specialist: '0.00',
3774
+ other: '0.00',
3775
+ net: '0.00',
3776
+ vat: '0.00',
3777
+ gross: '0.00',
3778
+ hours: '0',
3779
+ labourRate: '0.00'
3780
+ },
3781
+ savings: {
3782
+ labour: '0.00',
3783
+ none: '0.00',
3784
+ parts: '0.00',
3785
+ pm: '0.00',
3786
+ pl: '0.00',
3787
+ specialist: '0.00',
3788
+ other: '0.00',
3789
+ net: '0.00',
3790
+ vat: '0.00',
3791
+ gross: '0.00',
3792
+ hours: '0',
3793
+ labourRate: '0.00'
3794
+ }
3795
+ };
3796
+ }
3797
+ }
3315
3798
 
3316
3799
  const getEmptyGeoLocation = () => ({
3317
3800
  accuracy: 0,
@@ -3325,14 +3808,72 @@ const getEmptyGeoLocation = () => ({
3325
3808
  timestamp: ''
3326
3809
  });
3327
3810
 
3811
+ /**
3812
+ * Return true IFF the array contains only strings
3813
+ */
3328
3814
  const isArrayString = (value) => {
3329
3815
  return Array.isArray(value) && value.every(i => typeof i === 'string');
3330
3816
  };
3331
3817
 
3332
3818
  /**
3333
- * JobModel
3819
+ * Return true if the argument is a plain object {}, i.e. not an array, null.
3820
+ */
3821
+ function isPlainObject(value) {
3822
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3823
+ }
3824
+
3825
+ /**
3826
+ * Return an object omitting properties specified by key(s)
3827
+ *
3828
+ * @param obj object to be processed
3829
+ * @param omitKey key or array of keys to be omitted
3830
+ */
3831
+ const omit = (obj, omitKey) => {
3832
+ return Object.keys(obj).reduce((result, key) => {
3833
+ if (Array.isArray(omitKey)) {
3834
+ if (!omitKey.includes(key)) {
3835
+ result[key] = obj[key];
3836
+ }
3837
+ }
3838
+ else {
3839
+ if (omitKey !== key) {
3840
+ result[key] = obj[key];
3841
+ }
3842
+ }
3843
+ return result;
3844
+ }, {});
3845
+ };
3846
+
3847
+ /**
3848
+ * Try to parse the value as JSON if it looks like a JSON encoded string, return
3849
+ * the original value if it's not a JSON string or parsing fails.
3850
+ */
3851
+ const tryJson = (value) => {
3852
+ if (typeof value === 'string' && ['{', '['].includes(value.substring(0, 1))) {
3853
+ try {
3854
+ return JSON.parse(value);
3855
+ }
3856
+ catch (e) {
3857
+ return value;
3858
+ }
3859
+ }
3860
+ return value;
3861
+ };
3862
+
3863
+ const nullables = new Set([
3864
+ 'created',
3865
+ 'modified',
3866
+ 'reference',
3867
+ 'jobDate',
3868
+ 'followUpDate',
3869
+ 'followUpStatus',
3870
+ 'followUpNote',
3871
+ ]);
3872
+ let jobStaticProps;
3873
+ /**
3874
+ * VasJobBaseModel
3334
3875
  */
3335
- class VasJobModel extends VasRestrictedAccountObjectModel {
3876
+ class VasJobBaseModel extends VasRestrictedAccountObjectModel {
3336
3877
  id;
3337
3878
  created;
3338
3879
  serverCreated;
@@ -3347,6 +3888,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3347
3888
  reference;
3348
3889
  jobDate;
3349
3890
  jobStatus;
3891
+ jobStatusLastUpdate;
3350
3892
  jobType;
3351
3893
  assigneeId;
3352
3894
  formId;
@@ -3354,8 +3896,9 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3354
3896
  pendingFields;
3355
3897
  childModified;
3356
3898
  version;
3357
- createdByDisplayName;
3358
- modifiedByDisplayName;
3899
+ followUpDate;
3900
+ followUpStatus;
3901
+ followUpNote;
3359
3902
  geoLocation;
3360
3903
  files;
3361
3904
  fields;
@@ -3364,7 +3907,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3364
3907
  reports;
3365
3908
  tasks;
3366
3909
  instruction;
3367
- constructor(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup, reference, jobDate, jobStatus, jobType, assigneeId, formId, timeZoneOffset, pendingFields, childModified, version, createdByDisplayName, modifiedByDisplayName, geoLocation, files, fields, jobEmails, notes, reports, tasks, instruction) {
3910
+ constructor(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup, reference, jobDate, jobStatus, jobStatusLastUpdate, jobType, assigneeId, formId, timeZoneOffset, pendingFields, childModified, version, followUpDate, followUpStatus, followUpNote, geoLocation, files, fields, jobEmails, notes, reports, tasks, instruction) {
3368
3911
  super(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup);
3369
3912
  this.id = id;
3370
3913
  this.created = created;
@@ -3380,6 +3923,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3380
3923
  this.reference = reference;
3381
3924
  this.jobDate = jobDate;
3382
3925
  this.jobStatus = jobStatus;
3926
+ this.jobStatusLastUpdate = jobStatusLastUpdate;
3383
3927
  this.jobType = jobType;
3384
3928
  this.assigneeId = assigneeId;
3385
3929
  this.formId = formId;
@@ -3387,8 +3931,9 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3387
3931
  this.pendingFields = pendingFields;
3388
3932
  this.childModified = childModified;
3389
3933
  this.version = version;
3390
- this.createdByDisplayName = createdByDisplayName;
3391
- this.modifiedByDisplayName = modifiedByDisplayName;
3934
+ this.followUpDate = followUpDate;
3935
+ this.followUpStatus = followUpStatus;
3936
+ this.followUpNote = followUpNote;
3392
3937
  this.geoLocation = geoLocation;
3393
3938
  this.files = files;
3394
3939
  this.fields = fields;
@@ -3398,102 +3943,21 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3398
3943
  this.tasks = tasks;
3399
3944
  this.instruction = instruction;
3400
3945
  }
3401
- /**
3402
- * Returns this with the dynamic interface
3403
- * @return {VasJobModelDynamicInterface}
3404
- */
3405
- get $this() {
3406
- return this;
3946
+ get createdByDisplayName() {
3947
+ return this.createdByName;
3407
3948
  }
3408
- /**
3409
- * Returns a list of Job dynamic properties i.e. those that are added by the
3410
- * dynamic forms
3411
- */
3412
- get dynamicProperties() {
3413
- const nativeProps = this.staticProperties;
3414
- return Object
3415
- .getOwnPropertyNames(this)
3416
- .filter(prop => !nativeProps.includes(prop));
3949
+ get modifiedByDisplayName() {
3950
+ return this.modifiedByName;
3417
3951
  }
3418
3952
  /**
3419
- * Returns the list of properties of the Job type
3953
+ * Return the list of Job properties
3420
3954
  */
3421
3955
  get staticProperties() {
3422
- return Object.getOwnPropertyNames(VasJobModel.empty());
3423
- }
3424
- /**
3425
- * @return {VasJobModel}
3426
- */
3427
- static empty() {
3428
- return new VasJobModel('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', moment().utcOffset(), 0, '', 0, '', '', getEmptyGeoLocation(), [], [], [], [], [], [], null);
3429
- }
3430
- static fromDto(dto) {
3431
- const model = new VasJobModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.createdByDisplayName || '', dto.modifiedByDisplayName || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], dto.fields || [], dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
3432
- const emptyKeys = model.staticProperties;
3433
- Object
3434
- .keys(dto)
3435
- .filter(key => emptyKeys.indexOf(key) === -1)
3436
- .forEach((key) => model.$this[key] = dto[key]);
3437
- return model;
3438
- }
3439
- /**
3440
- * This method will instantiate a new JobModel. The difference with this
3441
- * method of instantiation is that we are coming from a relation frame i.e.
3442
- * the job has a list of {@link VasFieldDto} instead of a Job document.
3443
- *
3444
- */
3445
- static fromRelational(dto, form) {
3446
- const model = new VasJobModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobType || '', dto.assigneeId || '', dto.formId || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.createdByDisplayName || '', dto.modifiedByDisplayName || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], dto.fields || [], dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
3447
- form.groups
3448
- .forEach(group => {
3449
- group.controls
3450
- .forEach(control => {
3451
- const field = dto.fields?.find(f => f.control === control.id);
3452
- if (!field) {
3453
- return;
3454
- }
3455
- model.getGroup(group.name)[control.name] = field.value;
3456
- });
3457
- });
3458
- return model;
3459
- }
3460
- /**
3461
- * @param {VasFormModel} formModel
3462
- * @return {Record<string, VasFieldDtoValue>}
3463
- */
3464
- getFields(formModel) {
3465
- const fields = {};
3466
- formModel.groups
3467
- .forEach(group => {
3468
- group.controls.forEach(control => {
3469
- (fields)[control.reportTemplateName] =
3470
- this.getValueByPath([group.name, control.name]) || null;
3471
- });
3472
- });
3473
- return fields;
3474
- }
3475
- /**
3476
- * Will return an object whose keys are the names of all fields in the job.
3477
- * This method is similar to `getFields` except that this method will only
3478
- * return values where a value was set, as opposed to `getFields` which will
3479
- * return a value for every control even if no value was set, because it uses
3480
- * the form to drive the logic. Note also this method will return values for
3481
- * any Fields in the fields array.
3482
- * @return {Record<string, VasFieldDtoValue>}
3483
- */
3484
- getFields2() {
3485
- let fields = {};
3486
- this.dynamicProperties
3487
- .forEach((groupName) => {
3488
- Object
3489
- .keys(this.$this[groupName])
3490
- .forEach(controlName => {
3491
- fields[controlName] =
3492
- this.getValueByPath([groupName, controlName]) || null;
3493
- });
3494
- }, {});
3495
- this.fields.forEach(field => fields[field.controlName] = field.value);
3496
- return fields;
3956
+ if (jobStaticProps) {
3957
+ return jobStaticProps;
3958
+ }
3959
+ return jobStaticProps =
3960
+ new Set(Object.getOwnPropertyNames(VasJobDocumentModel.empty()));
3497
3961
  }
3498
3962
  /**
3499
3963
  * Returns an object describing how many attachments were added to this job
@@ -3503,22 +3967,12 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3503
3967
  * job was created
3504
3968
  */
3505
3969
  getFilesUploadStatus(formModel) {
3506
- const pathsWithValue = formModel
3507
- .getCameraControlPaths()
3508
- .map(path => {
3509
- const value = this.getValueByPath(path);
3510
- if (value && !!value.id) {
3511
- return path;
3512
- }
3513
- return null;
3514
- })
3515
- .filter(path => path !== null ? path : null)
3516
- .filter(isArrayString);
3517
- let total = pathsWithValue.length;
3518
- total += this.files.length;
3970
+ const cameraFieldValues = Object
3971
+ .values(this.fields)
3972
+ .filter(value => isCameraControlValueV2(value) || isCameraControlValueV1(value));
3973
+ let total = cameraFieldValues.length + this.files.length;
3519
3974
  // calculate the number of camera images still to upload
3520
- let pending = pathsWithValue
3521
- .map(path => this.getValueByPath(path))
3975
+ let pending = cameraFieldValues
3522
3976
  .map((value) => {
3523
3977
  if (isFileDto(value)) {
3524
3978
  return value.id && value.status !== 'COMPLETE';
@@ -3531,218 +3985,18 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3531
3985
  })
3532
3986
  .filter(hasUploaded => hasUploaded)
3533
3987
  .length;
3534
- // add total from files that are pending
3535
- pending += this.files
3536
- .filter(value => value.status !== 'COMPLETE')
3537
- .length;
3988
+ // add count of job.files that are complete
3989
+ pending += this.files.filter(value => value.status !== 'COMPLETE').length;
3538
3990
  return {
3539
3991
  pending,
3540
3992
  total
3541
3993
  };
3542
3994
  }
3543
- /**
3544
- * This method will return the dynamic property from the JobModel that
3545
- * represent a group (from a form).
3546
- *
3547
- * @param name the name of the group
3548
- * @param init if true (default) and group is not found, initialise an empty
3549
- * group, otherwise throw an error
3550
- */
3551
- getGroup(name, init = true) {
3552
- let prop;
3553
- if (this.staticProperties.includes(name)) {
3554
- throw Error(`invalid group name ${name}, not a dynamic property`);
3555
- }
3556
- else if (this.$this[name] === undefined) {
3557
- if (init) {
3558
- prop = this.$this[name] = {};
3559
- }
3560
- else {
3561
- throw Error(`invalid group name ${name}, not found`);
3562
- }
3563
- }
3564
- else {
3565
- prop = this.$this[name];
3566
- if (typeof prop !== 'object') {
3567
- throw Error(`invalid group name ${name}, not an object`);
3568
- }
3569
- }
3570
- return prop;
3571
- }
3572
3995
  /**
3573
3996
  * @param path path segments
3574
3997
  */
3575
3998
  getValueByPath(path = []) {
3576
- return getValueByPath(path, this.$this);
3577
- }
3578
- /**
3579
- * In order to understand why we need this method it is important to
3580
- * understand that within the system, Jobs can be represented in one of two
3581
- * ways, document and relational.
3582
- *
3583
- * The important distinction is how values submitted by a form are stored.
3584
- *
3585
- * Jobs stored as documents (JSON objects) will store user values, as dynamic
3586
- * properties of the document.
3587
- *
3588
- * Whereas, Jobs stored as relational, will store user values in an array of
3589
- * {@link VasFieldDto} objects.
3590
- *
3591
- * Depending on where we are in the system, either one of these approaches can
3592
- * be more useful than the other.
3593
- *
3594
- * This method, assumes that the JobModel has been instantiated from a
3595
- * document representation, and serves to hydrate the fields array. In order
3596
- * to achieve this, knowledge of the {@link VasFormDto} that created the job
3597
- * is required.
3598
- *
3599
- *
3600
- * Job in document representation
3601
- * {
3602
- * id: <guid>,
3603
- * reference: "something"
3604
- * <other static job properties>...
3605
- * foo: {
3606
- * bar: "value"
3607
- * }
3608
- * }
3609
- *
3610
- * Job in relational representation
3611
- * {
3612
- * id: <guid>,
3613
- * reference: "something"
3614
- * <other static job properties>...
3615
- * <will not have dynamic properties>...
3616
- * fields: [
3617
- * {
3618
- * id: <guid>,
3619
- * <other field properties>,
3620
- * value: "value"
3621
- * }
3622
- * ]
3623
- * }
3624
- *
3625
- *
3626
- * @param formModel the VasFormModel that was used to create the job
3627
- * @param controlFieldIdMap This is used to determine the id each field.
3628
- * Either one will be found in the map, or a new one is generated.
3629
- * @param controlNames if a value is provided, it will be used
3630
- * to filter the fields that are returned.
3631
- * @return {Array<VasFieldDto>}
3632
- */
3633
- hydrateFields(formModel, controlFieldIdMap = new Map(), controlNames) {
3634
- const fields = [];
3635
- formModel.groups
3636
- .forEach(group => {
3637
- group.controls.forEach(control => {
3638
- if (!control.name.startsWith('fields.')) {
3639
- return;
3640
- }
3641
- if (controlNames && !controlNames.includes(control.name)) {
3642
- return;
3643
- }
3644
- fields.push(VasFieldModel.fromDto({
3645
- // if the controlId exists in the map, use the associated fieldId,
3646
- // otherwise generate a new id for the field.
3647
- id: controlFieldIdMap.get(control.id) || UUID.UUID(),
3648
- account: this.account,
3649
- job: this.id,
3650
- createdBy: this.createdBy,
3651
- created: this.created,
3652
- modifiedBy: this.modifiedBy,
3653
- modified: this.modified,
3654
- control: control.id,
3655
- value: this.getValueByPath([group.name, control.name])
3656
- }));
3657
- });
3658
- });
3659
- this.fields = fields;
3660
- }
3661
- /**
3662
- * A very non sophisticated way to set values in the job via paths
3663
- *
3664
- * For example:
3665
- *
3666
- * setValueByPath('value', ['foo', 'bar']);
3667
- *
3668
- * will make results in job
3669
- * {
3670
- * foo: {
3671
- * bar: 'value'
3672
- * }
3673
- * }
3674
- *
3675
- * @param value the value to set
3676
- * @param path path segments
3677
- */
3678
- setValueByPath(value, path = []) {
3679
- switch (path.length) {
3680
- case 0: {
3681
- return;
3682
- }
3683
- case 1: {
3684
- this[path[0]] = value;
3685
- return;
3686
- }
3687
- case 2: {
3688
- // eslint-disable-next-line max-len
3689
- this.$this[path[0]][path[1]] = value;
3690
- return;
3691
- }
3692
- case 3: {
3693
- // eslint-disable-next-line max-len
3694
- this.$this[path[0]][path[1]][path[2]] = value;
3695
- return;
3696
- }
3697
- default: {
3698
- throw Error('path has too many segments');
3699
- }
3700
- }
3701
- }
3702
- /**
3703
- * @param staticOnly if true, will only output values for the static
3704
- * properties in the dto
3705
- */
3706
- toDto(staticOnly = false) {
3707
- if (staticOnly) {
3708
- return {
3709
- id: this.id,
3710
- created: this.created,
3711
- createdBy: this.createdBy,
3712
- modified: this.modified,
3713
- modifiedBy: this.modifiedBy,
3714
- serverCreated: this.serverCreated,
3715
- serverModified: this.serverModified,
3716
- createdByName: this.createdByName,
3717
- modifiedByName: this.modifiedByName,
3718
- account: this.account,
3719
- accessGroup: this.accessGroup,
3720
- reference: this.reference,
3721
- jobDate: this.jobDate,
3722
- jobStatus: this.jobStatus,
3723
- jobType: this.jobType,
3724
- assigneeId: this.assigneeId,
3725
- formId: this.formId,
3726
- timeZoneOffset: this.timeZoneOffset,
3727
- pendingFields: this.pendingFields,
3728
- childModified: this.childModified,
3729
- version: this.version,
3730
- createdByDisplayName: this.createdByDisplayName,
3731
- modifiedByDisplayName: this.modifiedByDisplayName,
3732
- geoLocation: this.geoLocation,
3733
- files: this.files,
3734
- fields: this.fields,
3735
- jobEmails: this.jobEmails,
3736
- notes: this.notes,
3737
- reports: this.reports,
3738
- tasks: this.tasks,
3739
- instruction: this.instruction
3740
- };
3741
- }
3742
- const dto = {};
3743
- [...this.staticProperties, ...this.dynamicProperties]
3744
- .forEach(prop => dto[prop] = this.$this[prop]);
3745
- return dto;
3999
+ return getValueByPath(Array.isArray(path) ? path : path.split('.'), this);
3746
4000
  }
3747
4001
  /**
3748
4002
  * This method will parse a syntax that allows us to build strings from the
@@ -3776,7 +4030,6 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3776
4030
  if (!value || typeof value !== 'string') {
3777
4031
  return '';
3778
4032
  }
3779
- const fields = this.getFields2();
3780
4033
  return value.replace(/({[^}]*})/g, (match, token) => {
3781
4034
  const syntax = token.substring(1, token.length - 1);
3782
4035
  let [key, filter] = syntax.split('|');
@@ -3803,23 +4056,10 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3803
4056
  result = (this.getValueByPath(path) || '').toString();
3804
4057
  }
3805
4058
  else if (objectKey === 'fields') {
3806
- result = (getValueByPath(['fields.' + path.shift(), ...path], fields) || '').toString();
4059
+ result = (this.getValueByPath(['fields', ...path]) || '').toString();
3807
4060
  }
3808
- else if (objectKey.length) {
3809
- if (options.objects) {
3810
- if (options.objects[objectKey] === undefined) {
3811
- console.debug(`objectKey ${objectKey} is not present in options.object`);
3812
- result = '';
3813
- }
3814
- else {
3815
- result = (getValueByPath(path, options.objects[objectKey]) || '')
3816
- .toString();
3817
- }
3818
- }
3819
- else {
3820
- console.debug(`objectKey ${objectKey} was used but options.object is not set`);
3821
- return '';
3822
- }
4061
+ else if (objectKey.length && options.objects && options.objects[objectKey] !== undefined) {
4062
+ result = (getValueByPath(path, options.objects[objectKey]) || '').toString();
3823
4063
  }
3824
4064
  else {
3825
4065
  return '';
@@ -3874,8 +4114,209 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3874
4114
  'assigneeId',
3875
4115
  'timeZoneOffset',
3876
4116
  'pendingFields',
3877
- 'lastForm'
3878
- ]);
4117
+ 'formId',
4118
+ 'followUpDate',
4119
+ 'followUpStatus',
4120
+ 'followUpNote',
4121
+ ], (value, key) => value[key] === null && nullables.has(key) ? '~' : value[key]);
4122
+ }
4123
+ }
4124
+ class VasJobRelationalModel extends VasJobBaseModel {
4125
+ getValueByPath(path = []) {
4126
+ const parts = Array.isArray(path) ? path : path.split('.');
4127
+ if (parts[0] === 'fields') {
4128
+ return this.getFieldValueByPath(parts[1], parts.slice(2));
4129
+ }
4130
+ return getValueByPath(parts, this);
4131
+ }
4132
+ getFieldsMap() {
4133
+ return this.fields.reduce((p, c) => {
4134
+ p[c.controlName] = c.value;
4135
+ return p;
4136
+ }, {});
4137
+ }
4138
+ getFieldValueByPath(controlName, path) {
4139
+ for (const field of this.fields) {
4140
+ if (field.controlName === controlName) {
4141
+ if (path.length === 0) {
4142
+ return field.value;
4143
+ }
4144
+ if (field.value === null) {
4145
+ return path.length >= 1 ? undefined : null;
4146
+ }
4147
+ if (isPlainObject(field.value)) {
4148
+ return getValueByPath(path, field.value);
4149
+ }
4150
+ return undefined;
4151
+ }
4152
+ }
4153
+ return undefined;
4154
+ }
4155
+ static fromDto(dto) {
4156
+ if (dto.fields !== undefined && !Array.isArray(dto.fields)) {
4157
+ throw Error('fields must be an array');
4158
+ }
4159
+ return new VasJobRelationalModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobStatusLastUpdate || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.followUpDate || '', dto.followUpStatus || '', dto.followUpNote || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], (dto.fields || []).map(f => VasFieldModel.fromDto(f)), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4160
+ }
4161
+ static empty() {
4162
+ return new VasJobRelationalModel('', // id
4163
+ '', // created
4164
+ '', // serverCreated
4165
+ '', // createdBy
4166
+ '', // modified
4167
+ '', // serverModified
4168
+ '', // modifiedBy
4169
+ '', // createdByName
4170
+ '', // modifiedByName
4171
+ '', // account
4172
+ '', // accessGroup
4173
+ '', // reference
4174
+ '', // jobDate
4175
+ '', // jobStatus
4176
+ '', // jobStatusLastUpdate
4177
+ '', // jobType
4178
+ '', // assigneeId
4179
+ '', // formId
4180
+ moment().utcOffset(), // timeZoneOffset
4181
+ 0, // pendingFields: number,
4182
+ '', // childModified: string,
4183
+ 0, // version: number,
4184
+ '', // followUpDate: string,
4185
+ '', // followUpStatus: string,
4186
+ '', // followUpNote: string,
4187
+ getEmptyGeoLocation(), // geoLocation: GeoLocation,
4188
+ [], // files: Array<VasFileDto>,
4189
+ [], // fields: FType,
4190
+ [], // jobEmails: Array<VasJobEmailDto>,
4191
+ [], // notes: Array<VasNoteDto>,
4192
+ [], // reports: Array<VasReportDto>,
4193
+ [], // tasks: Array<VasTaskDto>,
4194
+ null // instruction: VasInstructionDto | null
4195
+ );
4196
+ }
4197
+ toDto() {
4198
+ return {
4199
+ id: this.id,
4200
+ created: this.created,
4201
+ createdBy: this.createdBy,
4202
+ modified: this.modified,
4203
+ modifiedBy: this.modifiedBy,
4204
+ serverCreated: this.serverCreated,
4205
+ serverModified: this.serverModified,
4206
+ createdByName: this.createdByName,
4207
+ modifiedByName: this.modifiedByName,
4208
+ account: this.account,
4209
+ accessGroup: this.accessGroup,
4210
+ reference: this.reference,
4211
+ jobDate: this.jobDate,
4212
+ jobStatus: this.jobStatus,
4213
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4214
+ jobType: this.jobType,
4215
+ assigneeId: this.assigneeId,
4216
+ formId: this.formId,
4217
+ timeZoneOffset: this.timeZoneOffset,
4218
+ pendingFields: this.pendingFields,
4219
+ childModified: this.childModified,
4220
+ version: this.version,
4221
+ followUpDate: this.followUpDate,
4222
+ followUpStatus: this.followUpStatus,
4223
+ followUpNote: this.followUpNote,
4224
+ geoLocation: this.geoLocation,
4225
+ files: this.files,
4226
+ fields: this.fields.map(f => f.toDto()),
4227
+ jobEmails: this.jobEmails,
4228
+ notes: this.notes,
4229
+ reports: this.reports,
4230
+ tasks: this.tasks,
4231
+ instruction: this.instruction
4232
+ };
4233
+ }
4234
+ }
4235
+ class VasJobDocumentModel extends VasJobBaseModel {
4236
+ static fromRelationalDto(dto) {
4237
+ return new VasJobDocumentModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobStatusLastUpdate || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.followUpDate || '', dto.followUpStatus || '', dto.followUpNote || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], (dto.fields || []).reduce((p, c) => {
4238
+ p[c.controlName.replace('fields.', '')] = c.value;
4239
+ return p;
4240
+ }, {}), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4241
+ }
4242
+ static fromDto(dto) {
4243
+ if (dto.fields !== undefined && !isPlainObject(dto.fields)) {
4244
+ throw Error('fields must be an object');
4245
+ }
4246
+ return new VasJobDocumentModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobStatusLastUpdate || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.followUpDate || '', dto.followUpStatus || '', dto.followUpNote || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], dto.fields || {}, dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4247
+ }
4248
+ static empty() {
4249
+ return new VasJobDocumentModel('', // id
4250
+ '', // created
4251
+ '', // serverCreated
4252
+ '', // createdBy
4253
+ '', // modified
4254
+ '', // serverModified
4255
+ '', // modifiedBy
4256
+ '', // createdByName
4257
+ '', // modifiedByName
4258
+ '', // account
4259
+ '', // accessGroup
4260
+ '', // reference
4261
+ '', // jobDate
4262
+ '', // jobStatus
4263
+ '', // jobStatusLastUpdate
4264
+ '', // jobType
4265
+ '', // assigneeId
4266
+ '', // formId
4267
+ moment().utcOffset(), // timeZoneOffset
4268
+ 0, // pendingFields: number,
4269
+ '', // childModified: string,
4270
+ 0, // version: number,
4271
+ '', // followUpDate: string,
4272
+ '', // followUpStatus: string,
4273
+ '', // followUpNote: string,
4274
+ getEmptyGeoLocation(), // geoLocation: GeoLocation,
4275
+ [], // files: Array<VasFileDto>,
4276
+ {}, // fields: FType,
4277
+ [], // jobEmails: Array<VasJobEmailDto>,
4278
+ [], // notes: Array<VasNoteDto>,
4279
+ [], // reports: Array<VasReportDto>,
4280
+ [], // tasks: Array<VasTaskDto>,
4281
+ null // instruction: VasInstructionDto | null
4282
+ );
4283
+ }
4284
+ toDto() {
4285
+ return {
4286
+ id: this.id,
4287
+ created: this.created,
4288
+ createdBy: this.createdBy,
4289
+ modified: this.modified,
4290
+ modifiedBy: this.modifiedBy,
4291
+ serverCreated: this.serverCreated,
4292
+ serverModified: this.serverModified,
4293
+ createdByName: this.createdByName,
4294
+ modifiedByName: this.modifiedByName,
4295
+ account: this.account,
4296
+ accessGroup: this.accessGroup,
4297
+ reference: this.reference,
4298
+ jobDate: this.jobDate,
4299
+ jobStatus: this.jobStatus,
4300
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4301
+ jobType: this.jobType,
4302
+ assigneeId: this.assigneeId,
4303
+ formId: this.formId,
4304
+ timeZoneOffset: this.timeZoneOffset,
4305
+ pendingFields: this.pendingFields,
4306
+ childModified: this.childModified,
4307
+ version: this.version,
4308
+ followUpDate: this.followUpDate,
4309
+ followUpStatus: this.followUpStatus,
4310
+ followUpNote: this.followUpNote,
4311
+ geoLocation: this.geoLocation,
4312
+ files: this.files,
4313
+ fields: this.fields,
4314
+ jobEmails: this.jobEmails,
4315
+ notes: this.notes,
4316
+ reports: this.reports,
4317
+ tasks: this.tasks,
4318
+ instruction: this.instruction
4319
+ };
3879
4320
  }
3880
4321
  }
3881
4322
 
@@ -4486,5 +4927,5 @@ class VasVehicleModel extends VasAccountObjectModel {
4486
4927
  * Generated bundle index. Do not edit.
4487
4928
  */
4488
4929
 
4489
- export { VasAccessGroupModel, VasAccountIndexingMode, VasAccountObjectModel, VasBaseModel, VasBranchModel, VasContactModel, VasContactTypeModel, VasControlConfigDirection, VasControlModel, VasControlTypeModel, VasFieldModel, VasFileModel, VasFireUserModel, VasFormConfigModel, VasFormModel, VasGroupModel, VasInstructionJobFieldModel, VasInstructionJobModel, VasInstructionModel, VasInstructionProviderModel, VasInvitationModel, VasInvitationTypeEnum, VasJobActionControlsStepModel, VasJobActionEmailFormStepModel, VasJobActionInstructionStepModel, VasJobActionModel, VasJobActionNotePromptStepModel, VasJobActionSetDatetimeStepModel, VasJobActionSetJobStatusStepModel, VasJobActionSetValueStepModel, VasJobActionWebhookStepModel, VasJobCopyModel, VasJobDataModel, VasJobEmailModel, VasJobModel, VasJobStatusModel, VasJobTypeModel, VasLookupModel, VasLookupTypeModel, VasMembershipModel, VasNoteModel, VasReportLayoutModel, VasReportModel, VasReportRequestModel, VasRestrictedAccountObjectModel, VasTaskModel, VasTaskTypeModel, VasUserModel, VasVehicleModel, getEmptyGeoLocation, isCameraControlValueV1, isFileDto, isJobAutomationStep, isJobControlsStep, isJobEmailFormStep, isJobInstructionStep, isJobNotePromptStep, isJobSetDatetimeStep, isJobSetJobStatusStep, isJobSetValueStep, isJobWebhookStep, prepareFieldApiValue, stepModelFactory };
4930
+ export { EstimateControlValueModel, EstimateItemModel, EstimateModel, VasAccessGroupModel, VasAccountIndexingMode, VasAccountObjectModel, VasBaseModel, VasBranchModel, VasContactModel, VasContactTypeModel, VasControlConfigDirection, VasControlModel, VasControlTypeModel, VasFieldModel, VasFileModel, VasFireUserModel, VasFormConfigModel, VasFormModel, VasGroupModel, VasInstructionJobFieldModel, VasInstructionJobModel, VasInstructionModel, VasInstructionProviderModel, VasInvitationModel, VasInvitationTypeEnum, VasJobActionControlsStepModel, VasJobActionEmailFormStepModel, VasJobActionInstructionStepModel, VasJobActionModel, VasJobActionNotePromptStepModel, VasJobActionSetDatetimeStepModel, VasJobActionSetJobStatusStepModel, VasJobActionSetValueStepModel, VasJobActionWebhookStepModel, VasJobBaseModel, VasJobCopyModel, VasJobDataModel, VasJobDocumentModel, VasJobEmailModel, VasJobRelationalModel, VasJobStatusModel, VasJobTypeModel, VasLookupModel, VasLookupTypeModel, VasMembershipModel, VasNoteModel, VasReportLayoutModel, VasReportModel, VasReportRequestModel, VasRestrictedAccountObjectModel, VasTaskModel, VasTaskTypeModel, VasUserModel, VasVehicleModel, getEmptyGeoLocation, getValueByPath, isArrayString, isCameraControlValueV1, isCameraControlValueV2, isFileDto, isJobAutomationStep, isJobControlsStep, isJobEmailFormStep, isJobInstructionStep, isJobNotePromptStep, isJobSetDatetimeStep, isJobSetJobStatusStep, isJobSetValueStep, isJobWebhookStep, isNumeric, isPlainObject, omit, only, prepareFieldApiValue, stepModelFactory, tryJson };
4490
4931
  //# sourceMappingURL=ironcode-vas-lib.mjs.map