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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +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 +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 +279 -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 +792 -149
  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 +12 -3
  100. package/lib/entity/vas-job.model.d.ts +28 -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,541 @@ 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
+ 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)
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
+ function isLabourWorkCategory(value) {
3463
+ return ['check', 'rr', 'repair', 'removed'].includes(value);
3464
+ }
3465
+ function isPaintWorkCategory(value) {
3466
+ return ['blend', 'paint'].includes(value);
3467
+ }
3468
+ class EstimateModel {
3469
+ id = v4();
3470
+ source = 'manual';
3471
+ label = '';
3472
+ comment = '';
3473
+ timestamp = moment().toISOString();
3474
+ version = 1;
3475
+ net = '0.00';
3476
+ vat = '0.00';
3477
+ gross = '0.00';
3478
+ labourRate = '1';
3479
+ vatRate = '0.00';
3480
+ sundryRate = '0.00';
3481
+ sundryMax = '0.00';
3482
+ sundryFixed = '0.00';
3483
+ sundryMethod = 'fixed';
3484
+ partsDiscount = '0.00';
3485
+ paintDiscount = '0.00';
3486
+ timeBasis = '1';
3487
+ adjusted = false;
3488
+ items = [];
3489
+ summaryNet = {
3490
+ labour: '0.00',
3491
+ none: '0.00',
3492
+ parts: '0.00',
3493
+ pm: '0.00',
3494
+ pl: '0.00',
3495
+ specialist: '0.00',
3496
+ other: '0.00',
3497
+ gross: '0.00',
3498
+ net: '0.00',
3499
+ hours: '0.00',
3500
+ vat: '0.00',
3501
+ labourRate: '0.00',
3502
+ };
3503
+ summary = {
3504
+ labour: '0.00',
3505
+ none: '0.00',
3506
+ parts: '0.00',
3507
+ pm: '0.00',
3508
+ pl: '0.00',
3509
+ specialist: '0.00',
3510
+ other: '0.00',
3511
+ gross: '0.00',
3512
+ net: '0.00',
3513
+ hours: '0.00',
3514
+ vat: '0.00',
3515
+ labourRate: '0.00',
3516
+ };
3517
+ summaryAdjusted = {
3518
+ labour: '0.00',
3519
+ none: '0.00',
3520
+ parts: '0.00',
3521
+ pm: '0.00',
3522
+ pl: '0.00',
3523
+ specialist: '0.00',
3524
+ other: '0.00',
3525
+ gross: '0.00',
3526
+ net: '0.00',
3527
+ hours: '0.00',
3528
+ vat: '0.00',
3529
+ labourRate: '0.00',
3530
+ };
3531
+ get timeBasisSafe() {
3532
+ return (this.timeBasis || '1') === '0' ? '1' : this.timeBasis;
3533
+ }
3534
+ static create(source = 'manual') {
3535
+ const model = new EstimateModel();
3536
+ model.source = source;
3537
+ return model;
3538
+ }
3539
+ static fromDto(dto) {
3540
+ const model = new this();
3541
+ Object.assign(model, {
3542
+ ...dto,
3543
+ items: dto.items?.map(item => EstimateItemModel.fromDto(item)),
3544
+ });
3545
+ model.calculate();
3546
+ return model;
3547
+ }
3548
+ addItem(modelOrDto, opts) {
3549
+ this.items.push(modelOrDto instanceof EstimateItemModel ?
3550
+ modelOrDto :
3551
+ EstimateItemModel.fromDto(modelOrDto));
3552
+ if (!opts || (opts && opts.calculate)) {
3553
+ this.calculate();
3554
+ }
3555
+ }
3556
+ addItems(items, opts) {
3557
+ this.items = this.items.concat(items);
3558
+ if (!opts || (opts && opts.calculate)) {
3559
+ this.calculate();
3560
+ }
3561
+ }
3562
+ calculate() {
3563
+ this.summaryNet = this.getSummaryNet(false);
3564
+ if (this.adjusted) {
3565
+ this.summaryAdjusted = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, this.adjusted);
3566
+ }
3567
+ this.summary = this.getSummary(this.summaryNet, this.sundryRate, this.sundryMax, this.sundryFixed, this.sundryMethod, this.partsDiscount, this.paintDiscount, this.adjusted);
3568
+ this.net = b$1(0)
3569
+ .add(this.summary.labour)
3570
+ .add(this.summary.none)
3571
+ .add(this.summary.parts)
3572
+ .add(this.summary.pm)
3573
+ .add(this.summary.specialist)
3574
+ .toFixed(2);
3575
+ this.vat = b$1(this.net || '0').mul(this.vatRate || '0').toFixed(2);
3576
+ this.gross = b$1(this.net || '0').add(this.vat || '0').toFixed(2);
3577
+ }
3578
+ /**
3579
+ * Returns the net summary (before sundries and discounts) of the estimate.
3580
+ * By summary, we mean grouping the costs by cost category.
3581
+ *
3582
+ * @param adjusted if the values should be calculated using the original or
3583
+ * adjusted values
3584
+ */
3585
+ getSummaryNet(adjusted = false) {
3586
+ const summary = this.items.reduce((map, item) => {
3587
+ map[item.costCategory] = b$1(map[item.costCategory] || '0')
3588
+ .add(item.getLineValue(this.labourRate, this.timeBasisSafe, adjusted))
3589
+ .toFixed(2);
3590
+ return map;
3591
+ }, {
3592
+ labour: '0.00',
3593
+ none: '0.00',
3594
+ parts: '0.00',
3595
+ pm: '0.00',
3596
+ pl: '0.00',
3597
+ specialist: '0.00',
3598
+ other: '0.00',
3599
+ net: '0.00',
3600
+ vat: '0.00',
3601
+ gross: '0.00',
3602
+ hours: '0.00',
3603
+ labourRate: '0',
3604
+ });
3605
+ if (this.source === 'audatex') {
3606
+ const mechHours = this.items
3607
+ .reduce((h, item) => {
3608
+ return isLabourWorkCategory(item.workCategory) ?
3609
+ h.add(b$1(item.quantity || '0').mul(item.units || '0')) :
3610
+ h;
3611
+ }, b$1())
3612
+ .div(this.timeBasisSafe || '1')
3613
+ .round(2);
3614
+ const paintHours = this.items
3615
+ .reduce((h, item) => {
3616
+ return isPaintWorkCategory(item.workCategory) ?
3617
+ h.add(b$1(item.quantity || '0').mul(item.units || '0')) :
3618
+ h;
3619
+ }, b$1())
3620
+ .div(this.timeBasisSafe || '1')
3621
+ .round(1);
3622
+ const mechCost = mechHours.mul(this.labourRate || '0');
3623
+ const paintCost = paintHours.mul(this.labourRate || '0');
3624
+ summary.labour = mechCost.add(paintCost).toFixed(2);
3625
+ }
3626
+ return summary;
3627
+ }
3628
+ /**
3629
+ * Applies the sundry and discount values to the net summary.
3630
+ */
3631
+ getSummary(summaryNet, sundryRate, sundryMax, sundryFixed, sundryMethod, partsDiscount, paintDiscount, adjusted) {
3632
+ const partsDisc = b$1('1').minus(partsDiscount || '0');
3633
+ const paintDisc = b$1('1').minus(paintDiscount || '0');
3634
+ let partsSundry = '0.00';
3635
+ if (sundryMethod === 'percentage') {
3636
+ partsSundry = b$1(summaryNet.parts).mul(sundryRate || '1').toFixed(2);
3637
+ }
3638
+ else if (sundryMethod === 'percentage-with-max') {
3639
+ partsSundry = b$1(summaryNet.parts).mul(sundryRate || '1').toFixed(2);
3640
+ partsSundry = Math.min(parseFloat(sundryMax || '0'), parseFloat(partsSundry)).toFixed(2);
3641
+ }
3642
+ else if (sundryMethod === 'fixed') {
3643
+ partsSundry = b$1(sundryFixed).toFixed(2);
3644
+ }
3645
+ const parts = b$1(summaryNet.parts).add(partsSundry);
3646
+ const net = b$1(0)
3647
+ .add(summaryNet.labour)
3648
+ .add(summaryNet.none)
3649
+ .add(parts)
3650
+ .add(summaryNet.pm)
3651
+ .add(summaryNet.specialist)
3652
+ .toFixed(2);
3653
+ const vat = b$1(net || '0')
3654
+ .mul(this.vatRate || '0')
3655
+ .toFixed(2);
3656
+ const gross = b$1(net || '0').add(vat || '0').toFixed(2);
3657
+ return {
3658
+ labour: summaryNet.labour,
3659
+ none: summaryNet.none,
3660
+ parts: b$1(parts).mul(partsDisc).toFixed(2),
3661
+ pm: b$1(summaryNet.pm).mul(paintDisc).toFixed(2),
3662
+ pl: summaryNet.pl,
3663
+ specialist: summaryNet.specialist,
3664
+ other: summaryNet.other,
3665
+ net: net,
3666
+ vat: vat,
3667
+ gross: gross,
3668
+ labourRate: this.labourRate,
3669
+ hours: this.getTotalHours(adjusted)
3670
+ };
3671
+ }
3672
+ toDto() {
3673
+ return {
3674
+ id: this.id,
3675
+ source: this.source,
3676
+ label: this.label,
3677
+ comment: this.comment,
3678
+ timestamp: this.timestamp,
3679
+ version: this.version,
3680
+ net: this.net,
3681
+ vat: this.vat,
3682
+ gross: this.gross,
3683
+ labourRate: this.labourRate,
3684
+ vatRate: this.vatRate,
3685
+ sundryRate: this.sundryRate,
3686
+ sundryMax: this.sundryMax,
3687
+ sundryFixed: this.sundryFixed,
3688
+ sundryMethod: this.sundryMethod,
3689
+ partsDiscount: this.partsDiscount,
3690
+ paintDiscount: this.paintDiscount,
3691
+ timeBasis: this.timeBasisSafe,
3692
+ items: this.items.map(i => i.toDto()),
3693
+ adjusted: this.adjusted,
3694
+ summary: this.summary,
3695
+ summaryAdjusted: this.summaryAdjusted
3696
+ };
3697
+ }
3698
+ getTotalHours(adjusted = false) {
3699
+ if (this.source === 'audatex') {
3700
+ const mechUnitsHours = this.items
3701
+ .filter(item => item.workCategory !== 'paint')
3702
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3703
+ .round(2)
3704
+ .div(this.timeBasisSafe);
3705
+ const paintHours = this.items
3706
+ .filter(item => item.workCategory === 'paint')
3707
+ .reduce((h, item) => h.add(b$1(item.getTotalUnits(adjusted))), b$1())
3708
+ .round(1)
3709
+ .div(this.timeBasisSafe);
3710
+ return mechUnitsHours.add(paintHours).toFixed(2);
3711
+ }
3712
+ const units = this.items
3713
+ .filter(i => i.costCategory === 'labour')
3714
+ .reduce((p, c) => p.add(b$1(c.getTotalUnits(adjusted))), b$1('0'))
3715
+ .toFixed(2);
3716
+ return b$1(units).div(this.timeBasisSafe).toFixed(2);
3717
+ }
3718
+ }
3719
+
3720
+ const b = (value = '0') => new Big(value);
3721
+ /**
3722
+ * Utility class for computing the summary value for an estimate control given
3723
+ * the first and last estimates
3724
+ */
3725
+ class EstimateControlValueModel {
3726
+ static calculateSummary(original, latest) {
3727
+ const o = original.summary;
3728
+ const l = latest.adjusted ?
3729
+ latest.summaryAdjusted :
3730
+ latest.summary;
3731
+ const s = {
3732
+ labour: b(o.labour).minus(l.labour).toFixed(2),
3733
+ none: b(o.none).minus(l.none).toFixed(2),
3734
+ parts: b(o.parts).minus(l.parts).toFixed(2),
3735
+ pm: b(o.pm).minus(l.pm).toFixed(2),
3736
+ pl: b(o.pm).minus(l.pl).toFixed(2),
3737
+ specialist: b(o.specialist).minus(l.specialist).toFixed(2),
3738
+ other: b(o.other).minus(l.other).toFixed(2),
3739
+ net: b(o.net).minus(l.net).toFixed(2),
3740
+ vat: b(o.vat).minus(l.vat).toFixed(2),
3741
+ gross: b(o.gross).minus(l.gross).toFixed(2),
3742
+ hours: b(o.hours).minus(l.hours).toFixed(0),
3743
+ labourRate: b(o.labourRate).minus(l.labourRate).toFixed(0)
3744
+ };
3745
+ return {
3746
+ original: o,
3747
+ latest: l,
3748
+ savings: s
3749
+ };
3750
+ }
3751
+ static getEmptySummary() {
3752
+ return {
3753
+ original: {
3754
+ labour: '0.00',
3755
+ none: '0.00',
3756
+ parts: '0.00',
3757
+ pm: '0.00',
3758
+ pl: '0.00',
3759
+ specialist: '0.00',
3760
+ other: '0.00',
3761
+ net: '0.00',
3762
+ vat: '0.00',
3763
+ gross: '0.00',
3764
+ hours: '0',
3765
+ labourRate: '0.00'
3766
+ },
3767
+ latest: {
3768
+ labour: '0.00',
3769
+ none: '0.00',
3770
+ parts: '0.00',
3771
+ pm: '0.00',
3772
+ pl: '0.00',
3773
+ specialist: '0.00',
3774
+ other: '0.00',
3775
+ net: '0.00',
3776
+ vat: '0.00',
3777
+ gross: '0.00',
3778
+ hours: '0',
3779
+ labourRate: '0.00'
3780
+ },
3781
+ savings: {
3782
+ labour: '0.00',
3783
+ none: '0.00',
3784
+ parts: '0.00',
3785
+ pm: '0.00',
3786
+ pl: '0.00',
3787
+ specialist: '0.00',
3788
+ other: '0.00',
3789
+ net: '0.00',
3790
+ vat: '0.00',
3791
+ gross: '0.00',
3792
+ hours: '0',
3793
+ labourRate: '0.00'
3794
+ }
3795
+ };
3796
+ }
3797
+ }
3324
3798
 
3325
3799
  const getEmptyGeoLocation = () => ({
3326
3800
  accuracy: 0,
@@ -3335,8 +3809,44 @@ const getEmptyGeoLocation = () => ({
3335
3809
  });
3336
3810
 
3337
3811
  /**
3338
- * Try to parse the value as JSON if it looks like a JSON encoded string
3339
- * @param value
3812
+ * Return true IFF the array contains only strings
3813
+ */
3814
+ const isArrayString = (value) => {
3815
+ return Array.isArray(value) && value.every(i => typeof i === 'string');
3816
+ };
3817
+
3818
+ /**
3819
+ * Return true if the argument is a plain object {}, i.e. not an array, null.
3820
+ */
3821
+ function isPlainObject(value) {
3822
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3823
+ }
3824
+
3825
+ /**
3826
+ * Return an object omitting properties specified by key(s)
3827
+ *
3828
+ * @param obj object to be processed
3829
+ * @param omitKey key or array of keys to be omitted
3830
+ */
3831
+ const omit = (obj, omitKey) => {
3832
+ return Object.keys(obj).reduce((result, key) => {
3833
+ if (Array.isArray(omitKey)) {
3834
+ if (!omitKey.includes(key)) {
3835
+ result[key] = obj[key];
3836
+ }
3837
+ }
3838
+ else {
3839
+ if (omitKey !== key) {
3840
+ result[key] = obj[key];
3841
+ }
3842
+ }
3843
+ return result;
3844
+ }, {});
3845
+ };
3846
+
3847
+ /**
3848
+ * Try to parse the value as JSON if it looks like a JSON encoded string, return
3849
+ * the original value if it's not a JSON string or parsing fails.
3340
3850
  */
3341
3851
  const tryJson = (value) => {
3342
3852
  if (typeof value === 'string' && ['{', '['].includes(value.substring(0, 1))) {
@@ -3350,21 +3860,20 @@ const tryJson = (value) => {
3350
3860
  return value;
3351
3861
  };
3352
3862
 
3353
- function isPlainObject(value) {
3354
- return typeof value === 'object' && value !== null && !Array.isArray(value);
3355
- }
3356
-
3863
+ const nullables = new Set([
3864
+ 'created',
3865
+ 'modified',
3866
+ 'reference',
3867
+ 'jobDate',
3868
+ 'followUpDate',
3869
+ 'followUpStatus',
3870
+ 'followUpNote',
3871
+ ]);
3357
3872
  let jobStaticProps;
3358
- function getJobStaticProperties() {
3359
- if (jobStaticProps) {
3360
- return jobStaticProps;
3361
- }
3362
- return jobStaticProps = new Set(Object.getOwnPropertyNames(VasJobModel.empty()));
3363
- }
3364
3873
  /**
3365
- * JobModel
3874
+ * VasJobBaseModel
3366
3875
  */
3367
- class VasJobModel extends VasRestrictedAccountObjectModel {
3876
+ class VasJobBaseModel extends VasRestrictedAccountObjectModel {
3368
3877
  id;
3369
3878
  created;
3370
3879
  serverCreated;
@@ -3379,6 +3888,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3379
3888
  reference;
3380
3889
  jobDate;
3381
3890
  jobStatus;
3891
+ jobStatusLastUpdate;
3382
3892
  jobType;
3383
3893
  assigneeId;
3384
3894
  formId;
@@ -3386,6 +3896,9 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3386
3896
  pendingFields;
3387
3897
  childModified;
3388
3898
  version;
3899
+ followUpDate;
3900
+ followUpStatus;
3901
+ followUpNote;
3389
3902
  geoLocation;
3390
3903
  files;
3391
3904
  fields;
@@ -3394,7 +3907,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3394
3907
  reports;
3395
3908
  tasks;
3396
3909
  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) {
3910
+ constructor(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup, reference, jobDate, jobStatus, jobStatusLastUpdate, jobType, assigneeId, formId, timeZoneOffset, pendingFields, childModified, version, followUpDate, followUpStatus, followUpNote, geoLocation, files, fields, jobEmails, notes, reports, tasks, instruction) {
3398
3911
  super(id, created, serverCreated, createdBy, modified, serverModified, modifiedBy, createdByName, modifiedByName, account, accessGroup);
3399
3912
  this.id = id;
3400
3913
  this.created = created;
@@ -3410,6 +3923,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3410
3923
  this.reference = reference;
3411
3924
  this.jobDate = jobDate;
3412
3925
  this.jobStatus = jobStatus;
3926
+ this.jobStatusLastUpdate = jobStatusLastUpdate;
3413
3927
  this.jobType = jobType;
3414
3928
  this.assigneeId = assigneeId;
3415
3929
  this.formId = formId;
@@ -3417,6 +3931,9 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3417
3931
  this.pendingFields = pendingFields;
3418
3932
  this.childModified = childModified;
3419
3933
  this.version = version;
3934
+ this.followUpDate = followUpDate;
3935
+ this.followUpStatus = followUpStatus;
3936
+ this.followUpNote = followUpNote;
3420
3937
  this.geoLocation = geoLocation;
3421
3938
  this.files = files;
3422
3939
  this.fields = fields;
@@ -3439,49 +3956,8 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3439
3956
  if (jobStaticProps) {
3440
3957
  return jobStaticProps;
3441
3958
  }
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);
3959
+ return jobStaticProps =
3960
+ new Set(Object.getOwnPropertyNames(VasJobDocumentModel.empty()));
3485
3961
  }
3486
3962
  /**
3487
3963
  * Returns an object describing how many attachments were added to this job
@@ -3522,39 +3998,6 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3522
3998
  getValueByPath(path = []) {
3523
3999
  return getValueByPath(Array.isArray(path) ? path : path.split('.'), this);
3524
4000
  }
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
4001
  /**
3559
4002
  * This method will parse a syntax that allows us to build strings from the
3560
4003
  * values of from the job model. For example if the job model was:
@@ -3587,7 +4030,6 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3587
4030
  if (!value || typeof value !== 'string') {
3588
4031
  return '';
3589
4032
  }
3590
- const fields = this.fields;
3591
4033
  return value.replace(/({[^}]*})/g, (match, token) => {
3592
4034
  const syntax = token.substring(1, token.length - 1);
3593
4035
  let [key, filter] = syntax.split('|');
@@ -3613,8 +4055,8 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3613
4055
  if (objectKey === 'job') {
3614
4056
  result = (this.getValueByPath(path) || '').toString();
3615
4057
  }
3616
- else if (objectKey === 'fields' || this.fields[path[0]] !== undefined) {
3617
- result = (getValueByPath(path, fields) || '').toString();
4058
+ else if (objectKey === 'fields') {
4059
+ result = (this.getValueByPath(['fields', ...path]) || '').toString();
3618
4060
  }
3619
4061
  else if (objectKey.length && options.objects && options.objects[objectKey] !== undefined) {
3620
4062
  result = (getValueByPath(path, options.objects[objectKey]) || '').toString();
@@ -3672,8 +4114,209 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3672
4114
  'assigneeId',
3673
4115
  'timeZoneOffset',
3674
4116
  'pendingFields',
3675
- 'formId'
3676
- ]);
4117
+ 'formId',
4118
+ 'followUpDate',
4119
+ 'followUpStatus',
4120
+ 'followUpNote',
4121
+ ], (value, key) => value[key] === null && nullables.has(key) ? '~' : value[key]);
4122
+ }
4123
+ }
4124
+ class VasJobRelationalModel extends VasJobBaseModel {
4125
+ getValueByPath(path = []) {
4126
+ const parts = Array.isArray(path) ? path : path.split('.');
4127
+ if (parts[0] === 'fields') {
4128
+ return this.getFieldValueByPath(parts[1], parts.slice(2));
4129
+ }
4130
+ return getValueByPath(parts, this);
4131
+ }
4132
+ getFieldsMap() {
4133
+ return this.fields.reduce((p, c) => {
4134
+ p[c.controlName] = c.value;
4135
+ return p;
4136
+ }, {});
4137
+ }
4138
+ getFieldValueByPath(controlName, path) {
4139
+ for (const field of this.fields) {
4140
+ if (field.controlName === controlName) {
4141
+ if (path.length === 0) {
4142
+ return field.value;
4143
+ }
4144
+ if (field.value === null) {
4145
+ return path.length >= 1 ? undefined : null;
4146
+ }
4147
+ if (isPlainObject(field.value)) {
4148
+ return getValueByPath(path, field.value);
4149
+ }
4150
+ return undefined;
4151
+ }
4152
+ }
4153
+ return undefined;
4154
+ }
4155
+ static fromDto(dto) {
4156
+ if (dto.fields !== undefined && !Array.isArray(dto.fields)) {
4157
+ throw Error('fields must be an array');
4158
+ }
4159
+ return new VasJobRelationalModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobStatusLastUpdate || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.followUpDate || '', dto.followUpStatus || '', dto.followUpNote || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], (dto.fields || []).map(f => VasFieldModel.fromDto(f)), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4160
+ }
4161
+ static empty() {
4162
+ return new VasJobRelationalModel('', // id
4163
+ '', // created
4164
+ '', // serverCreated
4165
+ '', // createdBy
4166
+ '', // modified
4167
+ '', // serverModified
4168
+ '', // modifiedBy
4169
+ '', // createdByName
4170
+ '', // modifiedByName
4171
+ '', // account
4172
+ '', // accessGroup
4173
+ '', // reference
4174
+ '', // jobDate
4175
+ '', // jobStatus
4176
+ '', // jobStatusLastUpdate
4177
+ '', // jobType
4178
+ '', // assigneeId
4179
+ '', // formId
4180
+ moment().utcOffset(), // timeZoneOffset
4181
+ 0, // pendingFields: number,
4182
+ '', // childModified: string,
4183
+ 0, // version: number,
4184
+ '', // followUpDate: string,
4185
+ '', // followUpStatus: string,
4186
+ '', // followUpNote: string,
4187
+ getEmptyGeoLocation(), // geoLocation: GeoLocation,
4188
+ [], // files: Array<VasFileDto>,
4189
+ [], // fields: FType,
4190
+ [], // jobEmails: Array<VasJobEmailDto>,
4191
+ [], // notes: Array<VasNoteDto>,
4192
+ [], // reports: Array<VasReportDto>,
4193
+ [], // tasks: Array<VasTaskDto>,
4194
+ null // instruction: VasInstructionDto | null
4195
+ );
4196
+ }
4197
+ toDto() {
4198
+ return {
4199
+ id: this.id,
4200
+ created: this.created,
4201
+ createdBy: this.createdBy,
4202
+ modified: this.modified,
4203
+ modifiedBy: this.modifiedBy,
4204
+ serverCreated: this.serverCreated,
4205
+ serverModified: this.serverModified,
4206
+ createdByName: this.createdByName,
4207
+ modifiedByName: this.modifiedByName,
4208
+ account: this.account,
4209
+ accessGroup: this.accessGroup,
4210
+ reference: this.reference,
4211
+ jobDate: this.jobDate,
4212
+ jobStatus: this.jobStatus,
4213
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4214
+ jobType: this.jobType,
4215
+ assigneeId: this.assigneeId,
4216
+ formId: this.formId,
4217
+ timeZoneOffset: this.timeZoneOffset,
4218
+ pendingFields: this.pendingFields,
4219
+ childModified: this.childModified,
4220
+ version: this.version,
4221
+ followUpDate: this.followUpDate,
4222
+ followUpStatus: this.followUpStatus,
4223
+ followUpNote: this.followUpNote,
4224
+ geoLocation: this.geoLocation,
4225
+ files: this.files,
4226
+ fields: this.fields.map(f => f.toDto()),
4227
+ jobEmails: this.jobEmails,
4228
+ notes: this.notes,
4229
+ reports: this.reports,
4230
+ tasks: this.tasks,
4231
+ instruction: this.instruction
4232
+ };
4233
+ }
4234
+ }
4235
+ class VasJobDocumentModel extends VasJobBaseModel {
4236
+ static fromRelationalDto(dto) {
4237
+ return new VasJobDocumentModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobStatusLastUpdate || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.followUpDate || '', dto.followUpStatus || '', dto.followUpNote || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], (dto.fields || []).reduce((p, c) => {
4238
+ p[c.controlName.replace('fields.', '')] = c.value;
4239
+ return p;
4240
+ }, {}), dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4241
+ }
4242
+ static fromDto(dto) {
4243
+ if (dto.fields !== undefined && !isPlainObject(dto.fields)) {
4244
+ throw Error('fields must be an object');
4245
+ }
4246
+ return new VasJobDocumentModel(dto.id || '', dto.created || '', dto.serverCreated || '', dto.createdBy || '', dto.modified || '', dto.serverModified || '', dto.modifiedBy || '', dto.createdByName || '', dto.modifiedByName || '', dto.account || '', dto.accessGroup || '', dto.reference || '', dto.jobDate || '', dto.jobStatus || '', dto.jobStatusLastUpdate || '', dto.jobType || '', dto.assigneeId || '', dto.formId || dto.lastForm || '', dto.timeZoneOffset || moment().utcOffset(), dto.pendingFields || 0, dto.childModified || '', dto.version || 0, dto.followUpDate || '', dto.followUpStatus || '', dto.followUpNote || '', dto.geoLocation || getEmptyGeoLocation(), dto.files || [], dto.fields || {}, dto.jobEmails || [], dto.notes || [], dto.reports || [], dto.tasks || [], dto.instruction || null);
4247
+ }
4248
+ static empty() {
4249
+ return new VasJobDocumentModel('', // id
4250
+ '', // created
4251
+ '', // serverCreated
4252
+ '', // createdBy
4253
+ '', // modified
4254
+ '', // serverModified
4255
+ '', // modifiedBy
4256
+ '', // createdByName
4257
+ '', // modifiedByName
4258
+ '', // account
4259
+ '', // accessGroup
4260
+ '', // reference
4261
+ '', // jobDate
4262
+ '', // jobStatus
4263
+ '', // jobStatusLastUpdate
4264
+ '', // jobType
4265
+ '', // assigneeId
4266
+ '', // formId
4267
+ moment().utcOffset(), // timeZoneOffset
4268
+ 0, // pendingFields: number,
4269
+ '', // childModified: string,
4270
+ 0, // version: number,
4271
+ '', // followUpDate: string,
4272
+ '', // followUpStatus: string,
4273
+ '', // followUpNote: string,
4274
+ getEmptyGeoLocation(), // geoLocation: GeoLocation,
4275
+ [], // files: Array<VasFileDto>,
4276
+ {}, // fields: FType,
4277
+ [], // jobEmails: Array<VasJobEmailDto>,
4278
+ [], // notes: Array<VasNoteDto>,
4279
+ [], // reports: Array<VasReportDto>,
4280
+ [], // tasks: Array<VasTaskDto>,
4281
+ null // instruction: VasInstructionDto | null
4282
+ );
4283
+ }
4284
+ toDto() {
4285
+ return {
4286
+ id: this.id,
4287
+ created: this.created,
4288
+ createdBy: this.createdBy,
4289
+ modified: this.modified,
4290
+ modifiedBy: this.modifiedBy,
4291
+ serverCreated: this.serverCreated,
4292
+ serverModified: this.serverModified,
4293
+ createdByName: this.createdByName,
4294
+ modifiedByName: this.modifiedByName,
4295
+ account: this.account,
4296
+ accessGroup: this.accessGroup,
4297
+ reference: this.reference,
4298
+ jobDate: this.jobDate,
4299
+ jobStatus: this.jobStatus,
4300
+ jobStatusLastUpdate: this.jobStatusLastUpdate,
4301
+ jobType: this.jobType,
4302
+ assigneeId: this.assigneeId,
4303
+ formId: this.formId,
4304
+ timeZoneOffset: this.timeZoneOffset,
4305
+ pendingFields: this.pendingFields,
4306
+ childModified: this.childModified,
4307
+ version: this.version,
4308
+ followUpDate: this.followUpDate,
4309
+ followUpStatus: this.followUpStatus,
4310
+ followUpNote: this.followUpNote,
4311
+ geoLocation: this.geoLocation,
4312
+ files: this.files,
4313
+ fields: this.fields,
4314
+ jobEmails: this.jobEmails,
4315
+ notes: this.notes,
4316
+ reports: this.reports,
4317
+ tasks: this.tasks,
4318
+ instruction: this.instruction
4319
+ };
3677
4320
  }
3678
4321
  }
3679
4322
 
@@ -4284,5 +4927,5 @@ class VasVehicleModel extends VasAccountObjectModel {
4284
4927
  * Generated bundle index. Do not edit.
4285
4928
  */
4286
4929
 
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 };
4930
+ export { EstimateControlValueModel, EstimateItemModel, EstimateModel, VasAccessGroupModel, VasAccountIndexingMode, VasAccountObjectModel, VasBaseModel, VasBranchModel, VasContactModel, VasContactTypeModel, VasControlConfigDirection, VasControlModel, VasControlTypeModel, VasFieldModel, VasFileModel, VasFireUserModel, VasFormConfigModel, VasFormModel, VasGroupModel, VasInstructionJobFieldModel, VasInstructionJobModel, VasInstructionModel, VasInstructionProviderModel, VasInvitationModel, VasInvitationTypeEnum, VasJobActionControlsStepModel, VasJobActionEmailFormStepModel, VasJobActionInstructionStepModel, VasJobActionModel, VasJobActionNotePromptStepModel, VasJobActionSetDatetimeStepModel, VasJobActionSetJobStatusStepModel, VasJobActionSetValueStepModel, VasJobActionWebhookStepModel, VasJobBaseModel, VasJobCopyModel, VasJobDataModel, VasJobDocumentModel, VasJobEmailModel, VasJobRelationalModel, VasJobStatusModel, VasJobTypeModel, VasLookupModel, VasLookupTypeModel, VasMembershipModel, VasNoteModel, VasReportLayoutModel, VasReportModel, VasReportRequestModel, VasRestrictedAccountObjectModel, VasTaskModel, VasTaskTypeModel, VasUserModel, VasVehicleModel, getEmptyGeoLocation, getValueByPath, isArrayString, isCameraControlValueV1, isCameraControlValueV2, isFileDto, isJobAutomationStep, isJobControlsStep, isJobEmailFormStep, isJobInstructionStep, isJobNotePromptStep, isJobSetDatetimeStep, isJobSetJobStatusStep, isJobSetValueStep, isJobWebhookStep, isNumeric, isPlainObject, omit, only, prepareFieldApiValue, stepModelFactory, tryJson };
4288
4931
  //# sourceMappingURL=ironcode-vas-lib.mjs.map