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

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 (98) 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-field.dto.d.ts +3 -4
  5. package/cjs/lib/entity/vas-field.dto.d.ts.map +1 -1
  6. package/cjs/lib/entity/vas-field.dto.js.map +1 -1
  7. package/cjs/lib/entity/vas-job.model.js +1 -1
  8. package/cjs/lib/entity/vas-job.model.js.map +1 -1
  9. package/cjs/lib/model/estimate-control/estimate-control-value.dto.d.ts +14 -0
  10. package/cjs/lib/model/estimate-control/estimate-control-value.dto.d.ts.map +1 -0
  11. package/cjs/lib/model/estimate-control/estimate-control-value.dto.js +3 -0
  12. package/cjs/lib/model/estimate-control/estimate-control-value.dto.js.map +1 -0
  13. package/cjs/lib/model/estimate-control/estimate-control-value.model.d.ts +11 -0
  14. package/cjs/lib/model/estimate-control/estimate-control-value.model.d.ts.map +1 -0
  15. package/cjs/lib/model/estimate-control/estimate-control-value.model.js +84 -0
  16. package/cjs/lib/model/estimate-control/estimate-control-value.model.js.map +1 -0
  17. package/cjs/lib/model/estimate-control/estimate-item.dto.d.ts +16 -0
  18. package/cjs/lib/model/estimate-control/estimate-item.dto.d.ts.map +1 -0
  19. package/cjs/lib/model/estimate-control/estimate-item.dto.js +3 -0
  20. package/cjs/lib/model/estimate-control/estimate-item.dto.js.map +1 -0
  21. package/cjs/lib/model/estimate-control/estimate-item.model.d.ts +24 -0
  22. package/cjs/lib/model/estimate-control/estimate-item.model.d.ts.map +1 -0
  23. package/cjs/lib/model/estimate-control/estimate-item.model.js +121 -0
  24. package/cjs/lib/model/estimate-control/estimate-item.model.js.map +1 -0
  25. package/cjs/lib/model/estimate-control/estimate.dto.d.ts +37 -0
  26. package/cjs/lib/model/estimate-control/estimate.dto.d.ts.map +1 -0
  27. package/cjs/lib/model/estimate-control/estimate.dto.js +3 -0
  28. package/cjs/lib/model/estimate-control/estimate.dto.js.map +1 -0
  29. package/cjs/lib/model/estimate-control/estimate.model.d.ts +55 -0
  30. package/cjs/lib/model/estimate-control/estimate.model.d.ts.map +1 -0
  31. package/cjs/lib/model/estimate-control/estimate.model.js +265 -0
  32. package/cjs/lib/model/estimate-control/estimate.model.js.map +1 -0
  33. package/cjs/lib/model/estimate-control/estimate.types.d.ts +3 -0
  34. package/cjs/lib/model/estimate-control/estimate.types.d.ts.map +1 -0
  35. package/cjs/lib/model/estimate-control/estimate.types.js +3 -0
  36. package/cjs/lib/model/estimate-control/estimate.types.js.map +1 -0
  37. package/cjs/lib/model/estimate-control/index.d.ts +8 -0
  38. package/cjs/lib/model/estimate-control/index.d.ts.map +1 -0
  39. package/cjs/lib/model/estimate-control/index.js +11 -0
  40. package/cjs/lib/model/estimate-control/index.js.map +1 -0
  41. package/cjs/lib/model/index.d.ts +2 -0
  42. package/cjs/lib/model/index.d.ts.map +1 -1
  43. package/cjs/lib/model/index.js +2 -0
  44. package/cjs/lib/model/index.js.map +1 -1
  45. package/cjs/lib/model/report-invocation.d.ts +40 -0
  46. package/cjs/lib/model/report-invocation.d.ts.map +1 -0
  47. package/cjs/lib/model/report-invocation.js +3 -0
  48. package/cjs/lib/model/report-invocation.js.map +1 -0
  49. package/cjs/lib/utils/get-value-by-path.d.ts +5 -15
  50. package/cjs/lib/utils/get-value-by-path.d.ts.map +1 -1
  51. package/cjs/lib/utils/get-value-by-path.js +49 -60
  52. package/cjs/lib/utils/get-value-by-path.js.map +1 -1
  53. package/cjs/lib/utils/index.d.ts +8 -0
  54. package/cjs/lib/utils/index.d.ts.map +1 -0
  55. package/cjs/lib/utils/index.js +11 -0
  56. package/cjs/lib/utils/index.js.map +1 -0
  57. package/cjs/lib/utils/is-array-string.d.ts +3 -0
  58. package/cjs/lib/utils/is-array-string.d.ts.map +1 -1
  59. package/cjs/lib/utils/is-array-string.js +3 -0
  60. package/cjs/lib/utils/is-array-string.js.map +1 -1
  61. package/cjs/lib/utils/is-numeric.d.ts +2 -0
  62. package/cjs/lib/utils/is-numeric.d.ts.map +1 -0
  63. package/cjs/lib/utils/is-numeric.js +10 -0
  64. package/cjs/lib/utils/is-numeric.js.map +1 -0
  65. package/cjs/lib/utils/is-plain-object.d.ts +3 -0
  66. package/cjs/lib/utils/is-plain-object.d.ts.map +1 -1
  67. package/cjs/lib/utils/is-plain-object.js +3 -0
  68. package/cjs/lib/utils/is-plain-object.js.map +1 -1
  69. package/cjs/lib/utils/try-json.d.ts +2 -2
  70. package/cjs/lib/utils/try-json.js +2 -2
  71. package/cjs/lib/utils/try-json.js.map +1 -1
  72. package/cjs/public-api.d.ts +1 -0
  73. package/cjs/public-api.d.ts.map +1 -1
  74. package/cjs/public-api.js +1 -0
  75. package/cjs/public-api.js.map +1 -1
  76. package/fesm2022/ironcode-vas-lib.mjs +548 -56
  77. package/fesm2022/ironcode-vas-lib.mjs.map +1 -1
  78. package/lib/control-value/vas-camera-control-value.model.d.ts +4 -2
  79. package/lib/entity/vas-field.dto.d.ts +3 -4
  80. package/lib/model/estimate-control/estimate-control-value.dto.d.ts +13 -0
  81. package/lib/model/estimate-control/estimate-control-value.model.d.ts +10 -0
  82. package/lib/model/estimate-control/estimate-item.dto.d.ts +15 -0
  83. package/lib/model/estimate-control/estimate-item.model.d.ts +23 -0
  84. package/lib/model/estimate-control/estimate.dto.d.ts +36 -0
  85. package/lib/model/estimate-control/estimate.model.d.ts +54 -0
  86. package/lib/model/estimate-control/estimate.types.d.ts +2 -0
  87. package/lib/model/estimate-control/index.d.ts +7 -0
  88. package/lib/model/index.d.ts +2 -0
  89. package/lib/model/report-invocation.d.ts +39 -0
  90. package/lib/utils/get-value-by-path.d.ts +5 -15
  91. package/lib/utils/index.d.ts +7 -0
  92. package/lib/utils/is-array-string.d.ts +4 -0
  93. package/lib/utils/is-numeric.d.ts +1 -0
  94. package/lib/utils/is-plain-object.d.ts +3 -0
  95. package/lib/utils/omit.d.ts +7 -0
  96. package/lib/utils/try-json.d.ts +2 -2
  97. package/package.json +3 -2
  98. 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
@@ -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;
3282
- }
3283
- case 1: {
3284
- return (object)[path[0]];
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 (!(scope === undefined || scope === null) && index < length) {
3315
+ scope = scope[p[index++]];
3316
+ }
3317
+ if (!(index && index == length) || scope === null) {
3318
+ return undefined;
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,8 +3795,8 @@ 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
+ * Try to parse the value as JSON if it looks like a JSON encoded string, return
3799
+ * the original value if it's not a JSON string or parsing fails.
3340
3800
  */
3341
3801
  const tryJson = (value) => {
3342
3802
  if (typeof value === 'string' && ['{', '['].includes(value.substring(0, 1))) {
@@ -3350,6 +3810,9 @@ const tryJson = (value) => {
3350
3810
  return value;
3351
3811
  };
3352
3812
 
3813
+ /**
3814
+ * Return true if the argument is a plain object {}, i.e. not an array, null.
3815
+ */
3353
3816
  function isPlainObject(value) {
3354
3817
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3355
3818
  }
@@ -3477,7 +3940,7 @@ class VasJobModel extends VasRestrictedAccountObjectModel {
3477
3940
  .keys(dto[groupName])
3478
3941
  .forEach(controlName => {
3479
3942
  const cn = controlName.replace('fields.', '');
3480
- fields[cn] = dto[groupName][cn];
3943
+ fields[cn] = dto[groupName][controlName];
3481
3944
  });
3482
3945
  });
3483
3946
  }
@@ -4280,9 +4743,38 @@ class VasVehicleModel extends VasAccountObjectModel {
4280
4743
  }
4281
4744
  }
4282
4745
 
4746
+ /**
4747
+ * Return true IFF the array contains only strings
4748
+ */
4749
+ const isArrayString = (value) => {
4750
+ return Array.isArray(value) && value.every(i => typeof i === 'string');
4751
+ };
4752
+
4753
+ /**
4754
+ * Return an object omitting properties specified by key(s)
4755
+ *
4756
+ * @param obj object to be processed
4757
+ * @param omitKey key or array of keys to be omitted
4758
+ */
4759
+ const omit = (obj, omitKey) => {
4760
+ return Object.keys(obj).reduce((result, key) => {
4761
+ if (Array.isArray(omitKey)) {
4762
+ if (!omitKey.includes(key)) {
4763
+ result[key] = obj[key];
4764
+ }
4765
+ }
4766
+ else {
4767
+ if (omitKey !== key) {
4768
+ result[key] = obj[key];
4769
+ }
4770
+ }
4771
+ return result;
4772
+ }, {});
4773
+ };
4774
+
4283
4775
  /**
4284
4776
  * Generated bundle index. Do not edit.
4285
4777
  */
4286
4778
 
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 };
4779
+ 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, VasJobCopyModel, VasJobDataModel, VasJobEmailModel, VasJobModel, 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
4780
  //# sourceMappingURL=ironcode-vas-lib.mjs.map