@ironcode/vas-lib 3.0.0-alpha.1 → 3.0.0-alpha.12

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 (124) hide show
  1. package/cjs/lib/control-value/vas-camera-control-value.model.d.ts +4 -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.map +1 -1
  4. package/cjs/lib/entity/vas-base.model.d.ts +1 -1
  5. package/cjs/lib/entity/vas-base.model.d.ts.map +1 -1
  6. package/cjs/lib/entity/vas-base.model.js.map +1 -1
  7. package/cjs/lib/entity/vas-field.dto.d.ts +3 -4
  8. package/cjs/lib/entity/vas-field.dto.d.ts.map +1 -1
  9. package/cjs/lib/entity/vas-field.dto.js.map +1 -1
  10. package/cjs/lib/entity/vas-field.model.js +1 -1
  11. package/cjs/lib/entity/vas-field.model.js.map +1 -1
  12. package/cjs/lib/entity/vas-form.model.d.ts +1 -1
  13. package/cjs/lib/entity/vas-form.model.d.ts.map +1 -1
  14. package/cjs/lib/entity/vas-form.model.js.map +1 -1
  15. package/cjs/lib/entity/vas-job-copy.d.ts +12 -12
  16. package/cjs/lib/entity/vas-job-copy.d.ts.map +1 -1
  17. package/cjs/lib/entity/vas-job-copy.js.map +1 -1
  18. package/cjs/lib/entity/vas-job.dto.d.ts +12 -3
  19. package/cjs/lib/entity/vas-job.dto.d.ts.map +1 -1
  20. package/cjs/lib/entity/vas-job.dto.js.map +1 -1
  21. package/cjs/lib/entity/vas-job.model.d.ts +28 -13
  22. package/cjs/lib/entity/vas-job.model.d.ts.map +1 -1
  23. package/cjs/lib/entity/vas-job.model.js +230 -96
  24. package/cjs/lib/entity/vas-job.model.js.map +1 -1
  25. package/cjs/lib/model/estimate-control/estimate-control-value.dto.d.ts +14 -0
  26. package/cjs/lib/model/estimate-control/estimate-control-value.dto.d.ts.map +1 -0
  27. package/cjs/lib/model/estimate-control/estimate-control-value.dto.js +3 -0
  28. package/cjs/lib/model/estimate-control/estimate-control-value.dto.js.map +1 -0
  29. package/cjs/lib/model/estimate-control/estimate-control-value.model.d.ts +11 -0
  30. package/cjs/lib/model/estimate-control/estimate-control-value.model.d.ts.map +1 -0
  31. package/cjs/lib/model/estimate-control/estimate-control-value.model.js +84 -0
  32. package/cjs/lib/model/estimate-control/estimate-control-value.model.js.map +1 -0
  33. package/cjs/lib/model/estimate-control/estimate-item.dto.d.ts +17 -0
  34. package/cjs/lib/model/estimate-control/estimate-item.dto.d.ts.map +1 -0
  35. package/cjs/lib/model/estimate-control/estimate-item.dto.js +3 -0
  36. package/cjs/lib/model/estimate-control/estimate-item.dto.js.map +1 -0
  37. package/cjs/lib/model/estimate-control/estimate-item.model.d.ts +25 -0
  38. package/cjs/lib/model/estimate-control/estimate-item.model.d.ts.map +1 -0
  39. package/cjs/lib/model/estimate-control/estimate-item.model.js +126 -0
  40. package/cjs/lib/model/estimate-control/estimate-item.model.js.map +1 -0
  41. package/cjs/lib/model/estimate-control/estimate-source.d.ts +2 -0
  42. package/cjs/lib/model/estimate-control/estimate-source.d.ts.map +1 -0
  43. package/cjs/lib/model/estimate-control/estimate-source.js +3 -0
  44. package/cjs/lib/model/estimate-control/estimate-source.js.map +1 -0
  45. package/cjs/lib/model/estimate-control/estimate.dto.d.ts +38 -0
  46. package/cjs/lib/model/estimate-control/estimate.dto.d.ts.map +1 -0
  47. package/cjs/lib/model/estimate-control/estimate.dto.js +3 -0
  48. package/cjs/lib/model/estimate-control/estimate.dto.js.map +1 -0
  49. package/cjs/lib/model/estimate-control/estimate.model.d.ts +55 -0
  50. package/cjs/lib/model/estimate-control/estimate.model.d.ts.map +1 -0
  51. package/cjs/lib/model/estimate-control/estimate.model.js +278 -0
  52. package/cjs/lib/model/estimate-control/estimate.model.js.map +1 -0
  53. package/cjs/lib/model/estimate-control/estimate.types.d.ts +3 -0
  54. package/cjs/lib/model/estimate-control/estimate.types.d.ts.map +1 -0
  55. package/cjs/lib/model/estimate-control/estimate.types.js +3 -0
  56. package/cjs/lib/model/estimate-control/estimate.types.js.map +1 -0
  57. package/cjs/lib/model/estimate-control/index.d.ts +9 -0
  58. package/cjs/lib/model/estimate-control/index.d.ts.map +1 -0
  59. package/cjs/lib/model/estimate-control/index.js +11 -0
  60. package/cjs/lib/model/estimate-control/index.js.map +1 -0
  61. package/cjs/lib/model/index.d.ts +2 -0
  62. package/cjs/lib/model/index.d.ts.map +1 -1
  63. package/cjs/lib/model/index.js +2 -0
  64. package/cjs/lib/model/index.js.map +1 -1
  65. package/cjs/lib/model/report-invocation.d.ts +40 -0
  66. package/cjs/lib/model/report-invocation.d.ts.map +1 -0
  67. package/cjs/lib/model/report-invocation.js +3 -0
  68. package/cjs/lib/model/report-invocation.js.map +1 -0
  69. package/cjs/lib/utils/get-value-by-path.d.ts +5 -15
  70. package/cjs/lib/utils/get-value-by-path.d.ts.map +1 -1
  71. package/cjs/lib/utils/get-value-by-path.js +48 -59
  72. package/cjs/lib/utils/get-value-by-path.js.map +1 -1
  73. package/cjs/lib/utils/index.d.ts +8 -0
  74. package/cjs/lib/utils/index.d.ts.map +1 -0
  75. package/cjs/lib/utils/index.js +11 -0
  76. package/cjs/lib/utils/index.js.map +1 -0
  77. package/cjs/lib/utils/is-array-string.d.ts +3 -0
  78. package/cjs/lib/utils/is-array-string.d.ts.map +1 -1
  79. package/cjs/lib/utils/is-array-string.js +3 -0
  80. package/cjs/lib/utils/is-array-string.js.map +1 -1
  81. package/cjs/lib/utils/is-numeric.d.ts +2 -0
  82. package/cjs/lib/utils/is-numeric.d.ts.map +1 -0
  83. package/cjs/lib/utils/is-numeric.js +10 -0
  84. package/cjs/lib/utils/is-numeric.js.map +1 -0
  85. package/cjs/lib/utils/is-plain-object.d.ts +3 -0
  86. package/cjs/lib/utils/is-plain-object.d.ts.map +1 -1
  87. package/cjs/lib/utils/is-plain-object.js +3 -0
  88. package/cjs/lib/utils/is-plain-object.js.map +1 -1
  89. package/cjs/lib/utils/try-json.d.ts +2 -2
  90. package/cjs/lib/utils/try-json.js +2 -2
  91. package/cjs/lib/utils/try-json.js.map +1 -1
  92. package/cjs/public-api.d.ts +1 -0
  93. package/cjs/public-api.d.ts.map +1 -1
  94. package/cjs/public-api.js +1 -0
  95. package/cjs/public-api.js.map +1 -1
  96. package/fesm2022/ironcode-vas-lib.mjs +797 -149
  97. package/fesm2022/ironcode-vas-lib.mjs.map +1 -1
  98. package/lib/control-value/vas-camera-control-value.model.d.ts +4 -2
  99. package/lib/entity/vas-base.model.d.ts +1 -1
  100. package/lib/entity/vas-field.dto.d.ts +3 -4
  101. package/lib/entity/vas-form.model.d.ts +1 -1
  102. package/lib/entity/vas-job-copy.d.ts +12 -12
  103. package/lib/entity/vas-job.dto.d.ts +12 -3
  104. package/lib/entity/vas-job.model.d.ts +28 -13
  105. package/lib/model/estimate-control/estimate-control-value.dto.d.ts +13 -0
  106. package/lib/model/estimate-control/estimate-control-value.model.d.ts +10 -0
  107. package/lib/model/estimate-control/estimate-item.dto.d.ts +16 -0
  108. package/lib/model/estimate-control/estimate-item.model.d.ts +24 -0
  109. package/lib/model/estimate-control/estimate-source.d.ts +1 -0
  110. package/lib/model/estimate-control/estimate.dto.d.ts +37 -0
  111. package/lib/model/estimate-control/estimate.model.d.ts +54 -0
  112. package/lib/model/estimate-control/estimate.types.d.ts +2 -0
  113. package/lib/model/estimate-control/index.d.ts +8 -0
  114. package/lib/model/index.d.ts +2 -0
  115. package/lib/model/report-invocation.d.ts +39 -0
  116. package/lib/utils/get-value-by-path.d.ts +5 -15
  117. package/lib/utils/index.d.ts +7 -0
  118. package/lib/utils/is-array-string.d.ts +4 -0
  119. package/lib/utils/is-numeric.d.ts +1 -0
  120. package/lib/utils/is-plain-object.d.ts +3 -0
  121. package/lib/utils/omit.d.ts +7 -0
  122. package/lib/utils/try-json.d.ts +2 -2
  123. package/package.json +3 -2
  124. package/public-api.d.ts +1 -0
@@ -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
@@ -1537,7 +1539,7 @@ class VasFieldModel extends VasJobDataModel {
1537
1539
  * @return {VasFieldModel}
1538
1540
  */
1539
1541
  static fromDto(fieldDto) {
1540
- 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 || '');
1541
1543
  }
1542
1544
  /**
1543
1545
  * @return {VasFieldDto}
@@ -3258,69 +3260,546 @@ class VasJobTypeModel extends VasAccountObjectModel {
3258
3260
  }
3259
3261
  }
3260
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
+ }
3261
3302
  /**
3262
- * A non sophisticated way to get values from the job via paths. For example:
3303
+ * Get a value from an object by path.
3263
3304
  *
3264
- * getValueByPath(
3265
- * ['foo', 'bar'],
3266
- * {
3267
- * foo: {
3268
- * bar: 'value'
3269
- * }
3270
- * }
3271
- * );
3272
- *
3273
- * will return "value"
3274
- * @param {string[]} path
3275
- * @param {Record<string, FieldValueType>} object
3276
- * @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
3277
3308
  */
3278
- const getValueByPath = (path, object) => {
3279
- switch (path.length) {
3280
- case 0: {
3281
- 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;
3282
3317
  }
3283
- case 1: {
3284
- 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, ''));
3285
3327
  }
3286
- case 2: {
3287
- const val0 = object[path[0]];
3288
- if (!val0) {
3289
- return undefined;
3290
- }
3291
- 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
+ vatExcluded;
3350
+ constructor(id, description, type, quantity, quantityAdjusted, units, unitsAdjusted, price, priceAdjusted, workCategory, costCategory, comment, vatExcluded) {
3351
+ this.id = id;
3352
+ this.description = description;
3353
+ this.type = type;
3354
+ this.quantity = quantity;
3355
+ this.quantityAdjusted = quantityAdjusted;
3356
+ this.units = units;
3357
+ this.unitsAdjusted = unitsAdjusted;
3358
+ this.price = price;
3359
+ this.priceAdjusted = priceAdjusted;
3360
+ this.workCategory = workCategory;
3361
+ this.costCategory = costCategory;
3362
+ this.comment = comment;
3363
+ this.vatExcluded = vatExcluded;
3364
+ }
3365
+ getTotalUnits(adjusted = false) {
3366
+ return adjusted ?
3367
+ b$2(this.quantityAdjusted || '0').mul(this.unitsAdjusted || '0').toFixed(2) :
3368
+ b$2(this.quantity || '0').mul(this.units || '0').toFixed(2);
3369
+ }
3370
+ getLineValue(labourRate = '1', timeBasis = '1', adjusted = false, vatExcluded) {
3371
+ const cat = this.costCategory || 'none';
3372
+ if (this.workCategory === 'removed') {
3373
+ return '0';
3292
3374
  }
3293
- case 3: {
3294
- const val0 = object[path[0]];
3295
- if (!val0) {
3296
- return undefined;
3297
- }
3298
- const val1 = val0[path[1]];
3299
- if (!val1) {
3300
- return undefined;
3301
- }
3302
- return val1[path[2]];
3375
+ if (vatExcluded !== undefined && this.vatExcluded !== vatExcluded) {
3376
+ return '0';
3303
3377
  }
3304
- case 4: {
3305
- const val0 = object[path[0]];
3306
- if (!val0) {
3307
- return undefined;
3308
- }
3309
- const val1 = val0[path[1]];
3310
- if (!val1) {
3311
- return undefined;
3312
- }
3313
- const val2 = val1[path[2]];
3314
- if (!val2) {
3315
- return undefined;
3378
+ const units = (adjusted ? this.unitsAdjusted : this.units) || 0;
3379
+ const quantity = (adjusted ? this.quantityAdjusted : this.quantity) || 0;
3380
+ const price = (adjusted ? this.priceAdjusted : this.price) || '';
3381
+ try {
3382
+ switch (cat) {
3383
+ case 'labour': {
3384
+ return price != '' ?
3385
+ price :
3386
+ b$2(quantity || '0')
3387
+ .mul(units || '0')
3388
+ .mul(labourRate || '0')
3389
+ .div(timeBasis)
3390
+ .toFixed(2);
3391
+ }
3392
+ case 'pm': {
3393
+ return b$2(price || '0')
3394
+ .mul(quantity || '0')
3395
+ .toFixed(2);
3396
+ }
3397
+ case 'parts': {
3398
+ return b$2(price || '0')
3399
+ .mul(quantity || '0')
3400
+ .toFixed(2);
3401
+ }
3402
+ case 'specialist': {
3403
+ return b$2(price || '0')
3404
+ .mul(quantity || '0')
3405
+ .toFixed(2);
3406
+ }
3407
+ default: {
3408
+ return '0';
3409
+ }
3316
3410
  }
3317
- return val2[path[3]];
3318
3411
  }
3319
- default: {
3320
- throw Error('path has too many segments');
3412
+ catch (e) {
3413
+ console.warn(e);
3414
+ }
3415
+ return '0';
3416
+ }
3417
+ toDto() {
3418
+ return {
3419
+ id: this.id,
3420
+ description: this.description,
3421
+ type: this.type,
3422
+ quantity: this.quantity,
3423
+ quantityAdjusted: this.quantityAdjusted,
3424
+ units: this.units,
3425
+ unitsAdjusted: this.unitsAdjusted,
3426
+ price: this.price,
3427
+ priceAdjusted: this.priceAdjusted,
3428
+ workCategory: this.workCategory,
3429
+ costCategory: this.costCategory,
3430
+ comment: this.comment,
3431
+ vatExcluded: this.vatExcluded
3432
+ };
3433
+ }
3434
+ static create() {
3435
+ return new EstimateItemModel(v4(), '', '', 1, 1, 0, 0, '', '', 'none', 'none', '', false);
3436
+ }
3437
+ static fromDto(dto) {
3438
+ 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 || '', dto.vatExcluded || false);
3439
+ }
3440
+ static equal(a, b) {
3441
+ return (a?.id === b?.id &&
3442
+ a?.description === b?.description &&
3443
+ a?.type === b?.type &&
3444
+ a?.quantity === b?.quantity &&
3445
+ a?.quantityAdjusted === b?.quantityAdjusted &&
3446
+ a?.units === b?.units &&
3447
+ a?.unitsAdjusted === b?.unitsAdjusted &&
3448
+ a?.price === b?.price &&
3449
+ a?.priceAdjusted === b?.priceAdjusted &&
3450
+ a?.workCategory === b?.workCategory &&
3451
+ a?.costCategory === b?.costCategory &&
3452
+ a?.comment === b?.comment);
3453
+ }
3454
+ }
3455
+
3456
+ const b$1 = (value = '0') => {
3457
+ try {
3458
+ if (typeof value === 'string') {
3459
+ return new Big(value.replace(/[^\d^.]/g, ''));
3321
3460
  }
3461
+ return new Big(value);
3462
+ }
3463
+ catch (e) {
3464
+ console.error(`failed to construct Big from value '${value}'`);
3465
+ return new Big('0');
3322
3466
  }
3323
3467
  };
3468
+ function isLabourWorkCategory(value) {
3469
+ return ['check', 'rr', 'repair', 'removed'].includes(value);
3470
+ }
3471
+ function isPaintWorkCategory(value) {
3472
+ return ['blend', 'paint'].includes(value);
3473
+ }
3474
+ class EstimateModel {
3475
+ id = v4();
3476
+ source = 'manual';
3477
+ label = '';
3478
+ comment = '';
3479
+ timestamp = moment().toISOString();
3480
+ version = 1;
3481
+ net = '0.00';
3482
+ vat = '0.00';
3483
+ gross = '0.00';
3484
+ labourRate = '1';
3485
+ vatRate = '0.00';
3486
+ sundryRate = '0.00';
3487
+ sundryMax = '0.00';
3488
+ sundryFixed = '0.00';
3489
+ sundryMethod = 'fixed';
3490
+ partsDiscount = '0.00';
3491
+ paintDiscount = '0.00';
3492
+ timeBasis = '1';
3493
+ adjusted = false;
3494
+ items = [];
3495
+ summaryNet = {
3496
+ labour: '0.00',
3497
+ none: '0.00',
3498
+ parts: '0.00',
3499
+ pm: '0.00',
3500
+ pl: '0.00',
3501
+ specialist: '0.00',
3502
+ other: '0.00',
3503
+ gross: '0.00',
3504
+ net: '0.00',
3505
+ hours: '0.00',
3506
+ vat: '0.00',
3507
+ labourRate: '0.00',
3508
+ };
3509
+ summary = {
3510
+ labour: '0.00',
3511
+ none: '0.00',
3512
+ parts: '0.00',
3513
+ pm: '0.00',
3514
+ pl: '0.00',
3515
+ specialist: '0.00',
3516
+ other: '0.00',
3517
+ gross: '0.00',
3518
+ net: '0.00',
3519
+ hours: '0.00',
3520
+ vat: '0.00',
3521
+ labourRate: '0.00',
3522
+ };
3523
+ summaryAdjusted = {
3524
+ labour: '0.00',
3525
+ none: '0.00',
3526
+ parts: '0.00',
3527
+ pm: '0.00',
3528
+ pl: '0.00',
3529
+ specialist: '0.00',
3530
+ other: '0.00',
3531
+ gross: '0.00',
3532
+ net: '0.00',
3533
+ hours: '0.00',
3534
+ vat: '0.00',
3535
+ labourRate: '0.00',
3536
+ };
3537
+ get timeBasisSafe() {
3538
+ return (this.timeBasis || '1') === '0' ? '1' : this.timeBasis;
3539
+ }
3540
+ static create(source = 'manual') {
3541
+ const model = new EstimateModel();
3542
+ model.source = source;
3543
+ return model;
3544
+ }
3545
+ static fromDto(dto) {
3546
+ const model = new this();
3547
+ Object.assign(model, {
3548
+ ...dto,
3549
+ items: dto.items?.map(item => EstimateItemModel.fromDto(item)),
3550
+ });
3551
+ model.calculate();
3552
+ return model;
3553
+ }
3554
+ addItem(modelOrDto, opts) {
3555
+ this.items.push(modelOrDto instanceof EstimateItemModel ?
3556
+ modelOrDto :
3557
+ EstimateItemModel.fromDto(modelOrDto));
3558
+ if (!opts || (opts && opts.calculate)) {
3559
+ this.calculate();
3560
+ }
3561
+ }
3562
+ addItems(items, opts) {
3563
+ this.items = this.items.concat(items);
3564
+ if (!opts || (opts && opts.calculate)) {
3565
+ this.calculate();
3566
+ }
3567
+ }
3568
+ calculate() {
3569
+ this.summaryNet = this.getSummaryNet(false);
3570
+ if (this.adjusted) {
3571
+ this.summaryAdjusted = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, true);
3572
+ }
3573
+ this.summary = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, false);
3574
+ this.net = this.summary.net;
3575
+ this.vat = this.summary.vat;
3576
+ this.gross = this.summary.gross;
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, this.source === 'gtmotive' ? false : undefined))
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
+ let gross = b$1(net || '0').add(vat || '0').toFixed(2);
3657
+ // calculate the total value of lines that are VAT excluded
3658
+ const vatExcludeTotal = this.items.reduce((p, c) => c.vatExcluded ?
3659
+ p.add(c.getLineValue(this.labourRate, this.timeBasisSafe, adjusted, true)) :
3660
+ p, b$1());
3661
+ gross = b$1(gross).add(vatExcludeTotal).toFixed(2);
3662
+ return {
3663
+ labour: summaryNet.labour,
3664
+ none: summaryNet.none,
3665
+ parts: b$1(parts).mul(partsDisc).toFixed(2),
3666
+ pm: b$1(summaryNet.pm).mul(paintDisc).toFixed(2),
3667
+ pl: summaryNet.pl,
3668
+ specialist: summaryNet.specialist,
3669
+ other: summaryNet.other,
3670
+ net: net,
3671
+ vat: vat,
3672
+ gross: gross,
3673
+ labourRate: this.labourRate,
3674
+ hours: this.getTotalHours(adjusted)
3675
+ };
3676
+ }
3677
+ toDto() {
3678
+ return {
3679
+ id: this.id,
3680
+ source: this.source,
3681
+ label: this.label,
3682
+ comment: this.comment,
3683
+ timestamp: this.timestamp,
3684
+ version: this.version,
3685
+ net: this.net,
3686
+ vat: this.vat,
3687
+ gross: this.gross,
3688
+ labourRate: this.labourRate,
3689
+ vatRate: this.vatRate,
3690
+ sundryRate: this.sundryRate,
3691
+ sundryMax: this.sundryMax,
3692
+ sundryFixed: this.sundryFixed,
3693
+ sundryMethod: this.sundryMethod,
3694
+ partsDiscount: this.partsDiscount,
3695
+ paintDiscount: this.paintDiscount,
3696
+ timeBasis: this.timeBasisSafe,
3697
+ items: this.items.map(i => i.toDto()),
3698
+ adjusted: this.adjusted,
3699
+ summary: this.summary,
3700
+ summaryAdjusted: this.summaryAdjusted
3701
+ };
3702
+ }
3703
+ getTotalHours(adjusted = false) {
3704
+ if (this.source === 'audatex') {
3705
+ const mechUnitsHours = this.items
3706
+ .filter(item => item.workCategory !== 'paint')
3707
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3708
+ .round(2)
3709
+ .div(this.timeBasisSafe);
3710
+ const paintHours = this.items
3711
+ .filter(item => item.workCategory === 'paint')
3712
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3713
+ .round(1)
3714
+ .div(this.timeBasisSafe);
3715
+ return mechUnitsHours.add(paintHours).toFixed(2);
3716
+ }
3717
+ const units = this.items
3718
+ .filter(i => i.costCategory === 'labour')
3719
+ .reduce((p, c) => p.add(b$1(c.getTotalUnits(adjusted))), b$1('0'))
3720
+ .toFixed(2);
3721
+ return b$1(units).div(this.timeBasisSafe).toFixed(2);
3722
+ }
3723
+ }
3724
+
3725
+ const b = (value = '0') => new Big(value);
3726
+ /**
3727
+ * Utility class for computing the summary value for an estimate control given
3728
+ * the first and last estimates
3729
+ */
3730
+ class EstimateControlValueModel {
3731
+ static calculateSummary(original, latest) {
3732
+ const o = original.summary;
3733
+ const l = latest.adjusted ?
3734
+ latest.summaryAdjusted :
3735
+ latest.summary;
3736
+ const s = {
3737
+ labour: b(o.labour).minus(l.labour).toFixed(2),
3738
+ none: b(o.none).minus(l.none).toFixed(2),
3739
+ parts: b(o.parts).minus(l.parts).toFixed(2),
3740
+ pm: b(o.pm).minus(l.pm).toFixed(2),
3741
+ pl: b(o.pm).minus(l.pl).toFixed(2),
3742
+ specialist: b(o.specialist).minus(l.specialist).toFixed(2),
3743
+ other: b(o.other).minus(l.other).toFixed(2),
3744
+ net: b(o.net).minus(l.net).toFixed(2),
3745
+ vat: b(o.vat).minus(l.vat).toFixed(2),
3746
+ gross: b(o.gross).minus(l.gross).toFixed(2),
3747
+ hours: b(o.hours).minus(l.hours).toFixed(0),
3748
+ labourRate: b(o.labourRate).minus(l.labourRate).toFixed(0)
3749
+ };
3750
+ return {
3751
+ original: o,
3752
+ latest: l,
3753
+ savings: s
3754
+ };
3755
+ }
3756
+ static getEmptySummary() {
3757
+ return {
3758
+ original: {
3759
+ labour: '0.00',
3760
+ none: '0.00',
3761
+ parts: '0.00',
3762
+ pm: '0.00',
3763
+ pl: '0.00',
3764
+ specialist: '0.00',
3765
+ other: '0.00',
3766
+ net: '0.00',
3767
+ vat: '0.00',
3768
+ gross: '0.00',
3769
+ hours: '0',
3770
+ labourRate: '0.00'
3771
+ },
3772
+ latest: {
3773
+ labour: '0.00',
3774
+ none: '0.00',
3775
+ parts: '0.00',
3776
+ pm: '0.00',
3777
+ pl: '0.00',
3778
+ specialist: '0.00',
3779
+ other: '0.00',
3780
+ net: '0.00',
3781
+ vat: '0.00',
3782
+ gross: '0.00',
3783
+ hours: '0',
3784
+ labourRate: '0.00'
3785
+ },
3786
+ savings: {
3787
+ labour: '0.00',
3788
+ none: '0.00',
3789
+ parts: '0.00',
3790
+ pm: '0.00',
3791
+ pl: '0.00',
3792
+ specialist: '0.00',
3793
+ other: '0.00',
3794
+ net: '0.00',
3795
+ vat: '0.00',
3796
+ gross: '0.00',
3797
+ hours: '0',
3798
+ labourRate: '0.00'
3799
+ }
3800
+ };
3801
+ }
3802
+ }
3324
3803
 
3325
3804
  const getEmptyGeoLocation = () => ({
3326
3805
  accuracy: 0,
@@ -3335,8 +3814,44 @@ const getEmptyGeoLocation = () => ({
3335
3814
  });
3336
3815
 
3337
3816
  /**
3338
- * Try to parse the value as JSON if it looks like a JSON encoded string
3339
- * @param value
3817
+ * Return true IFF the array contains only strings
3818
+ */
3819
+ const isArrayString = (value) => {
3820
+ return Array.isArray(value) && value.every(i => typeof i === 'string');
3821
+ };
3822
+
3823
+ /**
3824
+ * Return true if the argument is a plain object {}, i.e. not an array, null.
3825
+ */
3826
+ function isPlainObject(value) {
3827
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3828
+ }
3829
+
3830
+ /**
3831
+ * Return an object omitting properties specified by key(s)
3832
+ *
3833
+ * @param obj object to be processed
3834
+ * @param omitKey key or array of keys to be omitted
3835
+ */
3836
+ const omit = (obj, omitKey) => {
3837
+ return Object.keys(obj).reduce((result, key) => {
3838
+ if (Array.isArray(omitKey)) {
3839
+ if (!omitKey.includes(key)) {
3840
+ result[key] = obj[key];
3841
+ }
3842
+ }
3843
+ else {
3844
+ if (omitKey !== key) {
3845
+ result[key] = obj[key];
3846
+ }
3847
+ }
3848
+ return result;
3849
+ }, {});
3850
+ };
3851
+
3852
+ /**
3853
+ * Try to parse the value as JSON if it looks like a JSON encoded string, return
3854
+ * the original value if it's not a JSON string or parsing fails.
3340
3855
  */
3341
3856
  const tryJson = (value) => {
3342
3857
  if (typeof value === 'string' && ['{', '['].includes(value.substring(0, 1))) {
@@ -3350,21 +3865,20 @@ const tryJson = (value) => {
3350
3865
  return value;
3351
3866
  };
3352
3867
 
3353
- function isPlainObject(value) {
3354
- return typeof value === 'object' && value !== null && !Array.isArray(value);
3355
- }
3356
-
3868
+ const nullables = new Set([
3869
+ 'created',
3870
+ 'modified',
3871
+ 'reference',
3872
+ 'jobDate',
3873
+ 'followUpDate',
3874
+ 'followUpStatus',
3875
+ 'followUpNote',
3876
+ ]);
3357
3877
  let jobStaticProps;
3358
- function getJobStaticProperties() {
3359
- if (jobStaticProps) {
3360
- return jobStaticProps;
3361
- }
3362
- return jobStaticProps = new Set(Object.getOwnPropertyNames(VasJobModel.empty()));
3363
- }
3364
3878
  /**
3365
- * JobModel
3879
+ * VasJobBaseModel
3366
3880
  */
3367
- class VasJobModel extends VasRestrictedAccountObjectModel {
3881
+ class VasJobBaseModel extends VasRestrictedAccountObjectModel {
3368
3882
  id;
3369
3883
  created;
3370
3884
  serverCreated;
@@ -3379,6 +3893,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3379
3893
  reference;
3380
3894
  jobDate;
3381
3895
  jobStatus;
3896
+ jobStatusLastUpdate;
3382
3897
  jobType;
3383
3898
  assigneeId;
3384
3899
  formId;
@@ -3386,6 +3901,9 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3386
3901
  pendingFields;
3387
3902
  childModified;
3388
3903
  version;
3904
+ followUpDate;
3905
+ followUpStatus;
3906
+ followUpNote;
3389
3907
  geoLocation;
3390
3908
  files;
3391
3909
  fields;
@@ -3394,7 +3912,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3394
3912
  reports;
3395
3913
  tasks;
3396
3914
  instruction;
3397
- constructor(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup, reference, jobDate, jobStatus, jobType, assigneeId, formId, timeZoneOffset, pendingFields, childModified, version, geoLocation, files, fields, jobEmails, notes, reports, tasks, instruction) {
3915
+ 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) {
3398
3916
  super(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup);
3399
3917
  this.id = id;
3400
3918
  this.created = created;
@@ -3410,6 +3928,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3410
3928
  this.reference = reference;
3411
3929
  this.jobDate = jobDate;
3412
3930
  this.jobStatus = jobStatus;
3931
+ this.jobStatusLastUpdate = jobStatusLastUpdate;
3413
3932
  this.jobType = jobType;
3414
3933
  this.assigneeId = assigneeId;
3415
3934
  this.formId = formId;
@@ -3417,6 +3936,9 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3417
3936
  this.pendingFields = pendingFields;
3418
3937
  this.childModified = childModified;
3419
3938
  this.version = version;
3939
+ this.followUpDate = followUpDate;
3940
+ this.followUpStatus = followUpStatus;
3941
+ this.followUpNote = followUpNote;
3420
3942
  this.geoLocation = geoLocation;
3421
3943
  this.files = files;
3422
3944
  this.fields = fields;
@@ -3439,49 +3961,8 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3439
3961
  if (jobStaticProps) {
3440
3962
  return jobStaticProps;
3441
3963
  }
3442
- return jobStaticProps = new Set(Object.getOwnPropertyNames(VasJobModel.empty()));
3443
- }
3444
- /**
3445
- * @return {VasJobModel}
3446
- */
3447
- static empty() {
3448
- return new VasJobModel('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', moment().utcOffset(), 0, '', 0, getEmptyGeoLocation(), [], {}, [], [], [], [], null);
3449
- }
3450
- static fromDto(dto) {
3451
- // we have changed the job model and stopped using the dynamic properties
3452
- // from form group names to hold fields. Instead, we store a simple object
3453
- // where the control name (minus the "fields." prefix) is the key, and the
3454
- // field value is the value. However, we have to cater for data that may
3455
- // be persisted in the old structure, or an API response where fields is an
3456
- // array of VasFieldDto.
3457
- // 1: if dto.fields is already an object, we use the existing value
3458
- const fields = isPlainObject(dto.fields) ?
3459
- dto.fields :
3460
- {};
3461
- // 2: if dto.fields is an array, we're handling a vas-rest response, we
3462
- // iterate over the fields and update the fields object.
3463
- Array.isArray(dto.fields) && dto.fields.forEach((field) => {
3464
- fields[field.controlName.replace('fields.', '')] = tryJson(field.value);
3465
- });
3466
- // 3: if `fields` still has no keys, we're restoring a VasJobDto object that
3467
- // was saved in the old "form-group" style, where dynamic keys are
3468
- // derived from the form-group names. We'll iterate over those keys, and
3469
- // update the `fields` object. Once this model is serialized back to a
3470
- // dto, it will have the new shape.
3471
- if (Object.keys(fields).length === 0) {
3472
- Object
3473
- .keys(dto)
3474
- .filter(key => !getJobStaticProperties().has(key))
3475
- .forEach(groupName => {
3476
- Object
3477
- .keys(dto[groupName])
3478
- .forEach(controlName => {
3479
- const cn = controlName.replace('fields.', '');
3480
- fields[cn] = dto[groupName][cn];
3481
- });
3482
- });
3483
- }
3484
- return 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.geoLocation || getEmptyGeoLocation(), dto.files || [], fields, dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
3964
+ return jobStaticProps =
3965
+ new Set(Object.getOwnPropertyNames(VasJobDocumentModel.empty()));
3485
3966
  }
3486
3967
  /**
3487
3968
  * Returns an object describing how many attachments were added to this job
@@ -3522,39 +4003,6 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3522
4003
  getValueByPath(path = []) {
3523
4004
  return getValueByPath(Array.isArray(path) ? path : path.split('.'), this);
3524
4005
  }
3525
- toDto() {
3526
- return {
3527
- id: this.id,
3528
- created: this.created,
3529
- createdBy: this.createdBy,
3530
- modified: this.modified,
3531
- modifiedBy: this.modifiedBy,
3532
- serverCreated: this.serverCreated,
3533
- serverModified: this.serverModified,
3534
- createdByName: this.createdByName,
3535
- modifiedByName: this.modifiedByName,
3536
- account: this.account,
3537
- accessGroup: this.accessGroup,
3538
- reference: this.reference,
3539
- jobDate: this.jobDate,
3540
- jobStatus: this.jobStatus,
3541
- jobType: this.jobType,
3542
- assigneeId: this.assigneeId,
3543
- formId: this.formId,
3544
- timeZoneOffset: this.timeZoneOffset,
3545
- pendingFields: this.pendingFields,
3546
- childModified: this.childModified,
3547
- version: this.version,
3548
- geoLocation: this.geoLocation,
3549
- files: this.files,
3550
- fields: this.fields,
3551
- jobEmails: this.jobEmails,
3552
- notes: this.notes,
3553
- reports: this.reports,
3554
- tasks: this.tasks,
3555
- instruction: this.instruction
3556
- };
3557
- }
3558
4006
  /**
3559
4007
  * This method will parse a syntax that allows us to build strings from the
3560
4008
  * values of from the job model. For example if the job model was:
@@ -3587,7 +4035,6 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3587
4035
  if (!value || typeof value !== 'string') {
3588
4036
  return '';
3589
4037
  }
3590
- const fields = this.fields;
3591
4038
  return value.replace(/({[^}]*})/g, (match, token) => {
3592
4039
  const syntax = token.substring(1, token.length - 1);
3593
4040
  let [key, filter] = syntax.split('|');
@@ -3613,8 +4060,8 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3613
4060
  if (objectKey === 'job') {
3614
4061
  result = (this.getValueByPath(path) || '').toString();
3615
4062
  }
3616
- else if (objectKey === 'fields' || this.fields[path[0]] !== undefined) {
3617
- result = (getValueByPath(path, fields) || '').toString();
4063
+ else if (objectKey === 'fields') {
4064
+ result = (this.getValueByPath(['fields', ...path]) || '').toString();
3618
4065
  }
3619
4066
  else if (objectKey.length && options.objects && options.objects[objectKey] !== undefined) {
3620
4067
  result = (getValueByPath(path, options.objects[objectKey]) || '').toString();
@@ -3672,8 +4119,209 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3672
4119
  'assigneeId',
3673
4120
  'timeZoneOffset',
3674
4121
  'pendingFields',
3675
- 'formId'
3676
- ]);
4122
+ 'formId',
4123
+ 'followUpDate',
4124
+ 'followUpStatus',
4125
+ 'followUpNote',
4126
+ ], (value, key) => value[key] === null && nullables.has(key) ? '~' : value[key]);
4127
+ }
4128
+ }
4129
+ class VasJobRelationalModel extends VasJobBaseModel {
4130
+ getValueByPath(path = []) {
4131
+ const parts = Array.isArray(path) ? path : path.split('.');
4132
+ if (parts[0] === 'fields') {
4133
+ return this.getFieldValueByPath(parts[1], parts.slice(2));
4134
+ }
4135
+ return getValueByPath(parts, this);
4136
+ }
4137
+ getFieldsMap() {
4138
+ return this.fields.reduce((p, c) => {
4139
+ p[c.controlName] = c.value;
4140
+ return p;
4141
+ }, {});
4142
+ }
4143
+ getFieldValueByPath(controlName, path) {
4144
+ for (const field of this.fields) {
4145
+ if (field.controlName === controlName) {
4146
+ if (path.length === 0) {
4147
+ return field.value;
4148
+ }
4149
+ if (field.value === null) {
4150
+ return path.length >= 1 ? undefined : null;
4151
+ }
4152
+ if (isPlainObject(field.value)) {
4153
+ return getValueByPath(path, field.value);
4154
+ }
4155
+ return undefined;
4156
+ }
4157
+ }
4158
+ return undefined;
4159
+ }
4160
+ static fromDto(dto) {
4161
+ if (dto.fields !== undefined && !Array.isArray(dto.fields)) {
4162
+ throw Error('fields must be an array');
4163
+ }
4164
+ 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);
4165
+ }
4166
+ static empty() {
4167
+ return new VasJobRelationalModel('', // id
4168
+ '', // created
4169
+ '', // serverCreated
4170
+ '', // createdBy
4171
+ '', // modified
4172
+ '', // serverModified
4173
+ '', // modifiedBy
4174
+ '', // createdByName
4175
+ '', // modifiedByName
4176
+ '', // account
4177
+ '', // accessGroup
4178
+ '', // reference
4179
+ '', // jobDate
4180
+ '', // jobStatus
4181
+ '', // jobStatusLastUpdate
4182
+ '', // jobType
4183
+ '', // assigneeId
4184
+ '', // formId
4185
+ moment().utcOffset(), // timeZoneOffset
4186
+ 0, // pendingFields: number,
4187
+ '', // childModified: string,
4188
+ 0, // version: number,
4189
+ '', // followUpDate: string,
4190
+ '', // followUpStatus: string,
4191
+ '', // followUpNote: string,
4192
+ getEmptyGeoLocation(), // geoLocation: GeoLocation,
4193
+ [], // files: Array<VasFileDto>,
4194
+ [], // fields: FType,
4195
+ [], // jobEmails: Array<VasJobEmailDto>,
4196
+ [], // notes: Array<VasNoteDto>,
4197
+ [], // reports: Array<VasReportDto>,
4198
+ [], // tasks: Array<VasTaskDto>,
4199
+ null // instruction: VasInstructionDto | null
4200
+ );
4201
+ }
4202
+ toDto() {
4203
+ return {
4204
+ id: this.id,
4205
+ created: this.created,
4206
+ createdBy: this.createdBy,
4207
+ modified: this.modified,
4208
+ modifiedBy: this.modifiedBy,
4209
+ serverCreated: this.serverCreated,
4210
+ serverModified: this.serverModified,
4211
+ createdByName: this.createdByName,
4212
+ modifiedByName: this.modifiedByName,
4213
+ account: this.account,
4214
+ accessGroup: this.accessGroup,
4215
+ reference: this.reference,
4216
+ jobDate: this.jobDate,
4217
+ jobStatus: this.jobStatus,
4218
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4219
+ jobType: this.jobType,
4220
+ assigneeId: this.assigneeId,
4221
+ formId: this.formId,
4222
+ timeZoneOffset: this.timeZoneOffset,
4223
+ pendingFields: this.pendingFields,
4224
+ childModified: this.childModified,
4225
+ version: this.version,
4226
+ followUpDate: this.followUpDate,
4227
+ followUpStatus: this.followUpStatus,
4228
+ followUpNote: this.followUpNote,
4229
+ geoLocation: this.geoLocation,
4230
+ files: this.files,
4231
+ fields: this.fields.map(f => f.toDto()),
4232
+ jobEmails: this.jobEmails,
4233
+ notes: this.notes,
4234
+ reports: this.reports,
4235
+ tasks: this.tasks,
4236
+ instruction: this.instruction
4237
+ };
4238
+ }
4239
+ }
4240
+ class VasJobDocumentModel extends VasJobBaseModel {
4241
+ static fromRelationalDto(dto) {
4242
+ 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) => {
4243
+ p[c.controlName.replace('fields.', '')] = c.value;
4244
+ return p;
4245
+ }, {}), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4246
+ }
4247
+ static fromDto(dto) {
4248
+ if (dto.fields !== undefined && !isPlainObject(dto.fields)) {
4249
+ throw Error('fields must be an object');
4250
+ }
4251
+ 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);
4252
+ }
4253
+ static empty() {
4254
+ return new VasJobDocumentModel('', // id
4255
+ '', // created
4256
+ '', // serverCreated
4257
+ '', // createdBy
4258
+ '', // modified
4259
+ '', // serverModified
4260
+ '', // modifiedBy
4261
+ '', // createdByName
4262
+ '', // modifiedByName
4263
+ '', // account
4264
+ '', // accessGroup
4265
+ '', // reference
4266
+ '', // jobDate
4267
+ '', // jobStatus
4268
+ '', // jobStatusLastUpdate
4269
+ '', // jobType
4270
+ '', // assigneeId
4271
+ '', // formId
4272
+ moment().utcOffset(), // timeZoneOffset
4273
+ 0, // pendingFields: number,
4274
+ '', // childModified: string,
4275
+ 0, // version: number,
4276
+ '', // followUpDate: string,
4277
+ '', // followUpStatus: string,
4278
+ '', // followUpNote: string,
4279
+ getEmptyGeoLocation(), // geoLocation: GeoLocation,
4280
+ [], // files: Array<VasFileDto>,
4281
+ {}, // fields: FType,
4282
+ [], // jobEmails: Array<VasJobEmailDto>,
4283
+ [], // notes: Array<VasNoteDto>,
4284
+ [], // reports: Array<VasReportDto>,
4285
+ [], // tasks: Array<VasTaskDto>,
4286
+ null // instruction: VasInstructionDto | null
4287
+ );
4288
+ }
4289
+ toDto() {
4290
+ return {
4291
+ id: this.id,
4292
+ created: this.created,
4293
+ createdBy: this.createdBy,
4294
+ modified: this.modified,
4295
+ modifiedBy: this.modifiedBy,
4296
+ serverCreated: this.serverCreated,
4297
+ serverModified: this.serverModified,
4298
+ createdByName: this.createdByName,
4299
+ modifiedByName: this.modifiedByName,
4300
+ account: this.account,
4301
+ accessGroup: this.accessGroup,
4302
+ reference: this.reference,
4303
+ jobDate: this.jobDate,
4304
+ jobStatus: this.jobStatus,
4305
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4306
+ jobType: this.jobType,
4307
+ assigneeId: this.assigneeId,
4308
+ formId: this.formId,
4309
+ timeZoneOffset: this.timeZoneOffset,
4310
+ pendingFields: this.pendingFields,
4311
+ childModified: this.childModified,
4312
+ version: this.version,
4313
+ followUpDate: this.followUpDate,
4314
+ followUpStatus: this.followUpStatus,
4315
+ followUpNote: this.followUpNote,
4316
+ geoLocation: this.geoLocation,
4317
+ files: this.files,
4318
+ fields: this.fields,
4319
+ jobEmails: this.jobEmails,
4320
+ notes: this.notes,
4321
+ reports: this.reports,
4322
+ tasks: this.tasks,
4323
+ instruction: this.instruction
4324
+ };
3677
4325
  }
3678
4326
  }
3679
4327
 
@@ -4284,5 +4932,5 @@ class VasVehicleModel extends VasAccountObjectModel {
4284
4932
  * Generated bundle index. Do not edit.
4285
4933
  */
4286
4934
 
4287
- 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, isCameraControlValueV2, isFileDto, isJobAutomationStep, isJobControlsStep, isJobEmailFormStep, isJobInstructionStep, isJobNotePromptStep, isJobSetDatetimeStep, isJobSetJobStatusStep, isJobSetValueStep, isJobWebhookStep, prepareFieldApiValue, stepModelFactory };
4935
+ 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 };
4288
4936
  //# sourceMappingURL=ironcode-vas-lib.mjs.map