@ironcode/vas-lib 3.0.0-alpha.2 → 3.0.0-alpha.5

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 (119) 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 +9 -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 +25 -13
  22. package/cjs/lib/entity/vas-job.model.d.ts.map +1 -1
  23. package/cjs/lib/entity/vas-job.model.js +139 -90
  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 +16 -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 +24 -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 +121 -0
  40. package/cjs/lib/model/estimate-control/estimate-item.model.js.map +1 -0
  41. package/cjs/lib/model/estimate-control/estimate.dto.d.ts +37 -0
  42. package/cjs/lib/model/estimate-control/estimate.dto.d.ts.map +1 -0
  43. package/cjs/lib/model/estimate-control/estimate.dto.js +3 -0
  44. package/cjs/lib/model/estimate-control/estimate.dto.js.map +1 -0
  45. package/cjs/lib/model/estimate-control/estimate.model.d.ts +55 -0
  46. package/cjs/lib/model/estimate-control/estimate.model.d.ts.map +1 -0
  47. package/cjs/lib/model/estimate-control/estimate.model.js +265 -0
  48. package/cjs/lib/model/estimate-control/estimate.model.js.map +1 -0
  49. package/cjs/lib/model/estimate-control/estimate.types.d.ts +3 -0
  50. package/cjs/lib/model/estimate-control/estimate.types.d.ts.map +1 -0
  51. package/cjs/lib/model/estimate-control/estimate.types.js +3 -0
  52. package/cjs/lib/model/estimate-control/estimate.types.js.map +1 -0
  53. package/cjs/lib/model/estimate-control/index.d.ts +8 -0
  54. package/cjs/lib/model/estimate-control/index.d.ts.map +1 -0
  55. package/cjs/lib/model/estimate-control/index.js +11 -0
  56. package/cjs/lib/model/estimate-control/index.js.map +1 -0
  57. package/cjs/lib/model/index.d.ts +2 -0
  58. package/cjs/lib/model/index.d.ts.map +1 -1
  59. package/cjs/lib/model/index.js +2 -0
  60. package/cjs/lib/model/index.js.map +1 -1
  61. package/cjs/lib/model/report-invocation.d.ts +40 -0
  62. package/cjs/lib/model/report-invocation.d.ts.map +1 -0
  63. package/cjs/lib/model/report-invocation.js +3 -0
  64. package/cjs/lib/model/report-invocation.js.map +1 -0
  65. package/cjs/lib/utils/get-value-by-path.d.ts +5 -15
  66. package/cjs/lib/utils/get-value-by-path.d.ts.map +1 -1
  67. package/cjs/lib/utils/get-value-by-path.js +48 -59
  68. package/cjs/lib/utils/get-value-by-path.js.map +1 -1
  69. package/cjs/lib/utils/index.d.ts +8 -0
  70. package/cjs/lib/utils/index.d.ts.map +1 -0
  71. package/cjs/lib/utils/index.js +11 -0
  72. package/cjs/lib/utils/index.js.map +1 -0
  73. package/cjs/lib/utils/is-array-string.d.ts +3 -0
  74. package/cjs/lib/utils/is-array-string.d.ts.map +1 -1
  75. package/cjs/lib/utils/is-array-string.js +3 -0
  76. package/cjs/lib/utils/is-array-string.js.map +1 -1
  77. package/cjs/lib/utils/is-numeric.d.ts +2 -0
  78. package/cjs/lib/utils/is-numeric.d.ts.map +1 -0
  79. package/cjs/lib/utils/is-numeric.js +10 -0
  80. package/cjs/lib/utils/is-numeric.js.map +1 -0
  81. package/cjs/lib/utils/is-plain-object.d.ts +3 -0
  82. package/cjs/lib/utils/is-plain-object.d.ts.map +1 -1
  83. package/cjs/lib/utils/is-plain-object.js +3 -0
  84. package/cjs/lib/utils/is-plain-object.js.map +1 -1
  85. package/cjs/lib/utils/try-json.d.ts +2 -2
  86. package/cjs/lib/utils/try-json.js +2 -2
  87. package/cjs/lib/utils/try-json.js.map +1 -1
  88. package/cjs/public-api.d.ts +1 -0
  89. package/cjs/public-api.d.ts.map +1 -1
  90. package/cjs/public-api.js +1 -0
  91. package/cjs/public-api.js.map +1 -1
  92. package/fesm2022/ironcode-vas-lib.mjs +694 -154
  93. package/fesm2022/ironcode-vas-lib.mjs.map +1 -1
  94. package/lib/control-value/vas-camera-control-value.model.d.ts +4 -2
  95. package/lib/entity/vas-base.model.d.ts +1 -1
  96. package/lib/entity/vas-field.dto.d.ts +3 -4
  97. package/lib/entity/vas-form.model.d.ts +1 -1
  98. package/lib/entity/vas-job-copy.d.ts +12 -12
  99. package/lib/entity/vas-job.dto.d.ts +9 -3
  100. package/lib/entity/vas-job.model.d.ts +25 -13
  101. package/lib/model/estimate-control/estimate-control-value.dto.d.ts +13 -0
  102. package/lib/model/estimate-control/estimate-control-value.model.d.ts +10 -0
  103. package/lib/model/estimate-control/estimate-item.dto.d.ts +15 -0
  104. package/lib/model/estimate-control/estimate-item.model.d.ts +23 -0
  105. package/lib/model/estimate-control/estimate.dto.d.ts +36 -0
  106. package/lib/model/estimate-control/estimate.model.d.ts +54 -0
  107. package/lib/model/estimate-control/estimate.types.d.ts +2 -0
  108. package/lib/model/estimate-control/index.d.ts +7 -0
  109. package/lib/model/index.d.ts +2 -0
  110. package/lib/model/report-invocation.d.ts +39 -0
  111. package/lib/utils/get-value-by-path.d.ts +5 -15
  112. package/lib/utils/index.d.ts +7 -0
  113. package/lib/utils/is-array-string.d.ts +4 -0
  114. package/lib/utils/is-numeric.d.ts +1 -0
  115. package/lib/utils/is-plain-object.d.ts +3 -0
  116. package/lib/utils/omit.d.ts +7 -0
  117. package/lib/utils/try-json.d.ts +2 -2
  118. package/package.json +3 -2
  119. 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,527 @@ 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:
3263
- *
3264
- * getValueByPath(
3265
- * ['foo', 'bar'],
3266
- * {
3267
- * foo: {
3268
- * bar: 'value'
3269
- * }
3270
- * }
3271
- * );
3303
+ * Get a value from an object by path.
3272
3304
  *
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
+ 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';
3292
3372
  }
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;
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 || '1')
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
+ }
3301
3405
  }
3302
- return val1[path[2]];
3303
3406
  }
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;
3316
- }
3317
- return val2[path[3]];
3407
+ catch (e) {
3408
+ console.warn(e);
3318
3409
  }
3319
- default: {
3320
- 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, ''));
3321
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');
3322
3460
  }
3323
3461
  };
3462
+ class EstimateModel {
3463
+ id = v4();
3464
+ source = 'manual';
3465
+ label = '';
3466
+ comment = '';
3467
+ timestamp = moment().toISOString();
3468
+ version = 1;
3469
+ net = '0.00';
3470
+ vat = '0.00';
3471
+ gross = '0.00';
3472
+ labourRate = '1';
3473
+ vatRate = '0.00';
3474
+ sundryRate = '0.00';
3475
+ sundryMax = '0.00';
3476
+ sundryFixed = '0.00';
3477
+ sundryMethod = 'fixed';
3478
+ partsDiscount = '0.00';
3479
+ paintDiscount = '0.00';
3480
+ timeBasis = '1';
3481
+ adjusted = false;
3482
+ items = [];
3483
+ summaryNet = {
3484
+ labour: '0.00',
3485
+ none: '0.00',
3486
+ parts: '0.00',
3487
+ pm: '0.00',
3488
+ pl: '0.00',
3489
+ specialist: '0.00',
3490
+ other: '0.00',
3491
+ gross: '0.00',
3492
+ net: '0.00',
3493
+ hours: '0.00',
3494
+ vat: '0.00',
3495
+ labourRate: '0.00',
3496
+ };
3497
+ summary = {
3498
+ labour: '0.00',
3499
+ none: '0.00',
3500
+ parts: '0.00',
3501
+ pm: '0.00',
3502
+ pl: '0.00',
3503
+ specialist: '0.00',
3504
+ other: '0.00',
3505
+ gross: '0.00',
3506
+ net: '0.00',
3507
+ hours: '0.00',
3508
+ vat: '0.00',
3509
+ labourRate: '0.00',
3510
+ };
3511
+ summaryAdjusted = {
3512
+ labour: '0.00',
3513
+ none: '0.00',
3514
+ parts: '0.00',
3515
+ pm: '0.00',
3516
+ pl: '0.00',
3517
+ specialist: '0.00',
3518
+ other: '0.00',
3519
+ gross: '0.00',
3520
+ net: '0.00',
3521
+ hours: '0.00',
3522
+ vat: '0.00',
3523
+ labourRate: '0.00',
3524
+ };
3525
+ static create(source = 'manual') {
3526
+ const model = new EstimateModel();
3527
+ model.source = source;
3528
+ return model;
3529
+ }
3530
+ static fromDto(dto) {
3531
+ const model = new EstimateModel();
3532
+ Object.assign(model, {
3533
+ ...dto,
3534
+ items: dto.items?.map(item => EstimateItemModel.fromDto(item)),
3535
+ });
3536
+ model.calculate();
3537
+ return model;
3538
+ }
3539
+ addItem(modelOrDto, opts) {
3540
+ this.items.push(modelOrDto instanceof EstimateItemModel ?
3541
+ modelOrDto :
3542
+ EstimateItemModel.fromDto(modelOrDto));
3543
+ if (!opts || (opts && opts.calculate)) {
3544
+ this.calculate();
3545
+ }
3546
+ }
3547
+ addItems(items, opts) {
3548
+ this.items = this.items.concat(items);
3549
+ if (!opts || (opts && opts.calculate)) {
3550
+ this.calculate();
3551
+ }
3552
+ }
3553
+ calculate() {
3554
+ this.summaryNet = this.getSummaryNet(false);
3555
+ if (this.adjusted) {
3556
+ this.summaryAdjusted = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, this.adjusted);
3557
+ }
3558
+ this.summary = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, this.adjusted);
3559
+ this.net = b$1(0)
3560
+ .add(this.summary.labour)
3561
+ .add(this.summary.none)
3562
+ .add(this.summary.parts)
3563
+ .add(this.summary.pm)
3564
+ .add(this.summary.specialist)
3565
+ .toFixed(2);
3566
+ this.vat = b$1(this.net || '0').mul(this.vatRate || '0').toFixed(2);
3567
+ this.gross = b$1(this.net || '0').add(this.vat || '0').toFixed(2);
3568
+ }
3569
+ /**
3570
+ * Returns the net summary (before sundries and discounts) of the estimate.
3571
+ * By summary, we mean grouping the costs by cost category.
3572
+ *
3573
+ * @param adjusted if the values should be calculated using the original or
3574
+ * adjusted values
3575
+ */
3576
+ getSummaryNet(adjusted = false) {
3577
+ const summary = this.items.reduce((map, item) => {
3578
+ map[item.costCategory] = b$1(map[item.costCategory] || '0')
3579
+ .add(item.getLineValue(this.labourRate, this.timeBasis, adjusted))
3580
+ .toFixed(2);
3581
+ return map;
3582
+ }, {
3583
+ labour: '0.00',
3584
+ none: '0.00',
3585
+ parts: '0.00',
3586
+ pm: '0.00',
3587
+ pl: '0.00',
3588
+ specialist: '0.00',
3589
+ other: '0.00',
3590
+ net: '0.00',
3591
+ vat: '0.00',
3592
+ gross: '0.00',
3593
+ hours: '0.00',
3594
+ labourRate: '0',
3595
+ });
3596
+ if (this.source === 'audatex') {
3597
+ const mechHours = this.items
3598
+ .filter(item => item.workCategory !== 'paint')
3599
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3600
+ .div(this.timeBasis || '1')
3601
+ .round(2);
3602
+ const paintHours = this.items
3603
+ .filter(item => item.workCategory === 'paint')
3604
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3605
+ .div(this.timeBasis || '1')
3606
+ .round(1);
3607
+ const mechCost = mechHours.mul(this.labourRate || '0');
3608
+ const paintCost = paintHours.mul(this.labourRate || '0');
3609
+ summary.labour = mechCost.add(paintCost).toFixed(2);
3610
+ }
3611
+ return summary;
3612
+ }
3613
+ /**
3614
+ * Applies the sundry and discount values to the net summary.
3615
+
3616
+ */
3617
+ getSummary(summaryNet, sundryRate, sundryMax, sundryFixed, sundryMethod, partsDiscount, paintDiscount, adjusted) {
3618
+ const partsDisc = b$1('1').minus(partsDiscount || '0');
3619
+ const paintDisc = b$1('1').minus(paintDiscount || '0');
3620
+ let partsSundry = '0.00';
3621
+ if (sundryMethod === 'percentage') {
3622
+ partsSundry = b$1(summaryNet.parts).mul(sundryRate || '1').toFixed(2);
3623
+ }
3624
+ else if (sundryMethod === 'percentage-with-max') {
3625
+ partsSundry = b$1(summaryNet.parts).mul(sundryRate || '1').toFixed(2);
3626
+ partsSundry = Math.min(parseFloat(sundryMax || '0'), parseFloat(partsSundry)).toFixed();
3627
+ }
3628
+ else if (sundryMethod === 'fixed') {
3629
+ partsSundry = b$1(sundryFixed).toFixed(2);
3630
+ }
3631
+ const parts = b$1(summaryNet.parts).add(partsSundry);
3632
+ const net = b$1(0)
3633
+ .add(summaryNet.labour)
3634
+ .add(summaryNet.none)
3635
+ .add(parts)
3636
+ .add(summaryNet.pm)
3637
+ .add(summaryNet.specialist)
3638
+ .toFixed(2);
3639
+ const vat = b$1(net || '0')
3640
+ .mul(this.vatRate || '0')
3641
+ .toFixed(2);
3642
+ const gross = b$1(net || '0').add(vat || '0').toFixed(2);
3643
+ return {
3644
+ labour: summaryNet.labour,
3645
+ none: summaryNet.none,
3646
+ parts: b$1(parts).mul(partsDisc).toFixed(2),
3647
+ pm: b$1(summaryNet.pm).mul(paintDisc).toFixed(2),
3648
+ pl: summaryNet.pl,
3649
+ specialist: summaryNet.specialist,
3650
+ other: summaryNet.other,
3651
+ net: net,
3652
+ vat: vat,
3653
+ gross: gross,
3654
+ labourRate: this.labourRate,
3655
+ hours: this.getTotalHours(adjusted)
3656
+ };
3657
+ }
3658
+ toDto() {
3659
+ return {
3660
+ id: this.id,
3661
+ source: this.source,
3662
+ label: this.label,
3663
+ comment: this.comment,
3664
+ timestamp: this.timestamp,
3665
+ version: this.version,
3666
+ net: this.net,
3667
+ vat: this.vat,
3668
+ gross: this.gross,
3669
+ labourRate: this.labourRate,
3670
+ vatRate: this.vatRate,
3671
+ sundryRate: this.sundryRate,
3672
+ sundryMax: this.sundryMax,
3673
+ sundryFixed: this.sundryFixed,
3674
+ sundryMethod: this.sundryMethod,
3675
+ partsDiscount: this.partsDiscount,
3676
+ paintDiscount: this.paintDiscount,
3677
+ timeBasis: this.timeBasis,
3678
+ items: this.items.map(i => i.toDto()),
3679
+ adjusted: this.adjusted,
3680
+ summary: this.summary,
3681
+ summaryAdjusted: this.summaryAdjusted
3682
+ };
3683
+ }
3684
+ getTotalHours(adjusted = false) {
3685
+ if (this.source === 'audatex') {
3686
+ const mechUnitsHours = this.items
3687
+ .filter(item => item.workCategory !== 'paint')
3688
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3689
+ .round(2)
3690
+ .div(this.timeBasis || '1');
3691
+ const paintHours = this.items
3692
+ .filter(item => item.workCategory === 'paint')
3693
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3694
+ .round(1)
3695
+ .div(this.timeBasis || '1');
3696
+ return mechUnitsHours.add(paintHours).toFixed(2);
3697
+ }
3698
+ const units = this.items
3699
+ .filter(i => i.costCategory === 'labour')
3700
+ .reduce((p, c) => p.add(b$1(c.getTotalUnits(adjusted))), b$1('0'))
3701
+ .toFixed(2);
3702
+ return b$1(units).div(this.timeBasis || '1').toFixed(2);
3703
+ }
3704
+ }
3705
+
3706
+ const b = (value = '0') => new Big(value);
3707
+ /**
3708
+ * Utility class for computing the summary value for an estimate control given
3709
+ * the first and last estimates
3710
+ */
3711
+ class EstimateControlValueModel {
3712
+ static calculateSummary(original, latest) {
3713
+ const o = original.summary;
3714
+ const l = latest.adjusted ?
3715
+ latest.summaryAdjusted :
3716
+ latest.summary;
3717
+ const s = {
3718
+ labour: b(o.labour).minus(l.labour).toFixed(2),
3719
+ none: b(o.none).minus(l.none).toFixed(2),
3720
+ parts: b(o.parts).minus(l.parts).toFixed(2),
3721
+ pm: b(o.pm).minus(l.pm).toFixed(2),
3722
+ pl: b(o.pm).minus(l.pl).toFixed(2),
3723
+ specialist: b(o.specialist).minus(l.specialist).toFixed(2),
3724
+ other: b(o.other).minus(l.other).toFixed(2),
3725
+ net: b(o.net).minus(l.net).toFixed(2),
3726
+ vat: b(o.vat).minus(l.vat).toFixed(2),
3727
+ gross: b(o.gross).minus(l.gross).toFixed(2),
3728
+ hours: b(o.hours).minus(l.hours).toFixed(0),
3729
+ labourRate: b(o.labourRate).minus(l.labourRate).toFixed(0)
3730
+ };
3731
+ return {
3732
+ original: o,
3733
+ latest: l,
3734
+ savings: s
3735
+ };
3736
+ }
3737
+ static getEmptySummary() {
3738
+ return {
3739
+ original: {
3740
+ labour: '0.00',
3741
+ none: '0.00',
3742
+ parts: '0.00',
3743
+ pm: '0.00',
3744
+ pl: '0.00',
3745
+ specialist: '0.00',
3746
+ other: '0.00',
3747
+ net: '0.00',
3748
+ vat: '0.00',
3749
+ gross: '0.00',
3750
+ hours: '0',
3751
+ labourRate: '0.00'
3752
+ },
3753
+ latest: {
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
+ savings: {
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
+ };
3782
+ }
3783
+ }
3324
3784
 
3325
3785
  const getEmptyGeoLocation = () => ({
3326
3786
  accuracy: 0,
@@ -3335,36 +3795,17 @@ const getEmptyGeoLocation = () => ({
3335
3795
  });
3336
3796
 
3337
3797
  /**
3338
- * Try to parse the value as JSON if it looks like a JSON encoded string
3339
- * @param value
3798
+ * Return true if the argument is a plain object {}, i.e. not an array, null.
3340
3799
  */
3341
- const tryJson = (value) => {
3342
- if (typeof value === 'string' && ['{', '['].includes(value.substring(0, 1))) {
3343
- try {
3344
- return JSON.parse(value);
3345
- }
3346
- catch (e) {
3347
- return value;
3348
- }
3349
- }
3350
- return value;
3351
- };
3352
-
3353
3800
  function isPlainObject(value) {
3354
3801
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3355
3802
  }
3356
3803
 
3357
3804
  let jobStaticProps;
3358
- function getJobStaticProperties() {
3359
- if (jobStaticProps) {
3360
- return jobStaticProps;
3361
- }
3362
- return jobStaticProps = new Set(Object.getOwnPropertyNames(VasJobModel.empty()));
3363
- }
3364
3805
  /**
3365
- * JobModel
3806
+ * VasJobBaseModel
3366
3807
  */
3367
- class VasJobModel extends VasRestrictedAccountObjectModel {
3808
+ class VasJobBaseModel extends VasRestrictedAccountObjectModel {
3368
3809
  id;
3369
3810
  created;
3370
3811
  serverCreated;
@@ -3379,6 +3820,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3379
3820
  reference;
3380
3821
  jobDate;
3381
3822
  jobStatus;
3823
+ jobStatusLastUpdate;
3382
3824
  jobType;
3383
3825
  assigneeId;
3384
3826
  formId;
@@ -3394,7 +3836,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3394
3836
  reports;
3395
3837
  tasks;
3396
3838
  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) {
3839
+ constructor(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup, reference, jobDate, jobStatus, jobStatusLastUpdate, jobType, assigneeId, formId, timeZoneOffset, pendingFields, childModified, version, geoLocation, files, fields, jobEmails, notes, reports, tasks, instruction) {
3398
3840
  super(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup);
3399
3841
  this.id = id;
3400
3842
  this.created = created;
@@ -3410,6 +3852,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3410
3852
  this.reference = reference;
3411
3853
  this.jobDate = jobDate;
3412
3854
  this.jobStatus = jobStatus;
3855
+ this.jobStatusLastUpdate = jobStatusLastUpdate;
3413
3856
  this.jobType = jobType;
3414
3857
  this.assigneeId = assigneeId;
3415
3858
  this.formId = formId;
@@ -3439,49 +3882,8 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3439
3882
  if (jobStaticProps) {
3440
3883
  return jobStaticProps;
3441
3884
  }
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][controlName];
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);
3885
+ return jobStaticProps =
3886
+ new Set(Object.getOwnPropertyNames(VasJobDocumentModel.empty()));
3485
3887
  }
3486
3888
  /**
3487
3889
  * Returns an object describing how many attachments were added to this job
@@ -3522,39 +3924,6 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3522
3924
  getValueByPath(path = []) {
3523
3925
  return getValueByPath(Array.isArray(path) ? path : path.split('.'), this);
3524
3926
  }
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
3927
  /**
3559
3928
  * This method will parse a syntax that allows us to build strings from the
3560
3929
  * values of from the job model. For example if the job model was:
@@ -3613,8 +3982,8 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3613
3982
  if (objectKey === 'job') {
3614
3983
  result = (this.getValueByPath(path) || '').toString();
3615
3984
  }
3616
- else if (objectKey === 'fields' || this.fields[path[0]] !== undefined) {
3617
- result = (getValueByPath(path, fields) || '').toString();
3985
+ else if (objectKey === 'fields') {
3986
+ result = (this.getValueByPath(['fields', ...path]) || '').toString();
3618
3987
  }
3619
3988
  else if (objectKey.length && options.objects && options.objects[objectKey] !== undefined) {
3620
3989
  result = (getValueByPath(path, options.objects[objectKey]) || '').toString();
@@ -3676,6 +4045,132 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3676
4045
  ]);
3677
4046
  }
3678
4047
  }
4048
+ class VasJobRelationalModel extends VasJobBaseModel {
4049
+ getValueByPath(path = []) {
4050
+ const parts = Array.isArray(path) ? path : path.split('.');
4051
+ if (parts[0] === 'fields') {
4052
+ return this.getFieldValueByPath(parts[1], parts.slice(2));
4053
+ }
4054
+ return getValueByPath(parts, this);
4055
+ }
4056
+ getFieldsMap() {
4057
+ return this.fields.reduce((p, c) => {
4058
+ p[c.controlName] = c.value;
4059
+ return p;
4060
+ }, {});
4061
+ }
4062
+ getFieldValueByPath(controlName, path) {
4063
+ for (const field of this.fields) {
4064
+ if (field.controlName === controlName) {
4065
+ if (path.length === 0) {
4066
+ return field.value;
4067
+ }
4068
+ if (field.value === null) {
4069
+ return path.length >= 1 ? undefined : null;
4070
+ }
4071
+ if (isPlainObject(field.value)) {
4072
+ return getValueByPath(path, field.value);
4073
+ }
4074
+ return undefined;
4075
+ }
4076
+ }
4077
+ return undefined;
4078
+ }
4079
+ static fromDto(dto) {
4080
+ if (dto.fields !== undefined && !Array.isArray(dto.fields)) {
4081
+ throw Error('fields must be an array');
4082
+ }
4083
+ 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.geoLocation || getEmptyGeoLocation(), dto.files || [], (dto.fields || []).map(f => VasFieldModel.fromDto(f)), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4084
+ }
4085
+ static empty() {
4086
+ return new VasJobRelationalModel('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', moment().utcOffset(), 0, '', 0, getEmptyGeoLocation(), [], [], [], [], [], [], null);
4087
+ }
4088
+ toDto() {
4089
+ return {
4090
+ id: this.id,
4091
+ created: this.created,
4092
+ createdBy: this.createdBy,
4093
+ modified: this.modified,
4094
+ modifiedBy: this.modifiedBy,
4095
+ serverCreated: this.serverCreated,
4096
+ serverModified: this.serverModified,
4097
+ createdByName: this.createdByName,
4098
+ modifiedByName: this.modifiedByName,
4099
+ account: this.account,
4100
+ accessGroup: this.accessGroup,
4101
+ reference: this.reference,
4102
+ jobDate: this.jobDate,
4103
+ jobStatus: this.jobStatus,
4104
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4105
+ jobType: this.jobType,
4106
+ assigneeId: this.assigneeId,
4107
+ formId: this.formId,
4108
+ timeZoneOffset: this.timeZoneOffset,
4109
+ pendingFields: this.pendingFields,
4110
+ childModified: this.childModified,
4111
+ version: this.version,
4112
+ geoLocation: this.geoLocation,
4113
+ files: this.files,
4114
+ fields: this.fields.map(f => f.toDto()),
4115
+ jobEmails: this.jobEmails,
4116
+ notes: this.notes,
4117
+ reports: this.reports,
4118
+ tasks: this.tasks,
4119
+ instruction: this.instruction
4120
+ };
4121
+ }
4122
+ }
4123
+ class VasJobDocumentModel extends VasJobBaseModel {
4124
+ static fromRelationalDto(dto) {
4125
+ 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.geoLocation || getEmptyGeoLocation(), dto.files || [], (dto.fields || []).reduce((p, c) => {
4126
+ p[c.controlName.replace('fields.', '')] = c.value;
4127
+ return p;
4128
+ }, {}), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4129
+ }
4130
+ static fromDto(dto) {
4131
+ if (dto.fields !== undefined && !isPlainObject(dto.fields)) {
4132
+ throw Error('fields must be an object');
4133
+ }
4134
+ 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.geoLocation || getEmptyGeoLocation(), dto.files || [], dto.fields || {}, dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4135
+ }
4136
+ static empty() {
4137
+ return new VasJobDocumentModel('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', moment().utcOffset(), 0, '', 0, getEmptyGeoLocation(), [], {}, [], [], [], [], null);
4138
+ }
4139
+ toDto() {
4140
+ return {
4141
+ id: this.id,
4142
+ created: this.created,
4143
+ createdBy: this.createdBy,
4144
+ modified: this.modified,
4145
+ modifiedBy: this.modifiedBy,
4146
+ serverCreated: this.serverCreated,
4147
+ serverModified: this.serverModified,
4148
+ createdByName: this.createdByName,
4149
+ modifiedByName: this.modifiedByName,
4150
+ account: this.account,
4151
+ accessGroup: this.accessGroup,
4152
+ reference: this.reference,
4153
+ jobDate: this.jobDate,
4154
+ jobStatus: this.jobStatus,
4155
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4156
+ jobType: this.jobType,
4157
+ assigneeId: this.assigneeId,
4158
+ formId: this.formId,
4159
+ timeZoneOffset: this.timeZoneOffset,
4160
+ pendingFields: this.pendingFields,
4161
+ childModified: this.childModified,
4162
+ version: this.version,
4163
+ geoLocation: this.geoLocation,
4164
+ files: this.files,
4165
+ fields: this.fields,
4166
+ jobEmails: this.jobEmails,
4167
+ notes: this.notes,
4168
+ reports: this.reports,
4169
+ tasks: this.tasks,
4170
+ instruction: this.instruction
4171
+ };
4172
+ }
4173
+ }
3679
4174
 
3680
4175
  class VasLookupTypeModel extends VasAccountObjectModel {
3681
4176
  id;
@@ -4280,9 +4775,54 @@ class VasVehicleModel extends VasAccountObjectModel {
4280
4775
  }
4281
4776
  }
4282
4777
 
4778
+ /**
4779
+ * Return true IFF the array contains only strings
4780
+ */
4781
+ const isArrayString = (value) => {
4782
+ return Array.isArray(value) && value.every(i => typeof i === 'string');
4783
+ };
4784
+
4785
+ /**
4786
+ * Return an object omitting properties specified by key(s)
4787
+ *
4788
+ * @param obj object to be processed
4789
+ * @param omitKey key or array of keys to be omitted
4790
+ */
4791
+ const omit = (obj, omitKey) => {
4792
+ return Object.keys(obj).reduce((result, key) => {
4793
+ if (Array.isArray(omitKey)) {
4794
+ if (!omitKey.includes(key)) {
4795
+ result[key] = obj[key];
4796
+ }
4797
+ }
4798
+ else {
4799
+ if (omitKey !== key) {
4800
+ result[key] = obj[key];
4801
+ }
4802
+ }
4803
+ return result;
4804
+ }, {});
4805
+ };
4806
+
4807
+ /**
4808
+ * Try to parse the value as JSON if it looks like a JSON encoded string, return
4809
+ * the original value if it's not a JSON string or parsing fails.
4810
+ */
4811
+ const tryJson = (value) => {
4812
+ if (typeof value === 'string' && ['{', '['].includes(value.substring(0, 1))) {
4813
+ try {
4814
+ return JSON.parse(value);
4815
+ }
4816
+ catch (e) {
4817
+ return value;
4818
+ }
4819
+ }
4820
+ return value;
4821
+ };
4822
+
4283
4823
  /**
4284
4824
  * Generated bundle index. Do not edit.
4285
4825
  */
4286
4826
 
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 };
4827
+ 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
4828
  //# sourceMappingURL=ironcode-vas-lib.mjs.map