@roomwise/calc-engine 0.6.0 → 0.8.0
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.
- package/README.md +2 -2
- package/dist/index.cjs +121 -24
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +115 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Lines are produced in this order: base cost, object rules (by `sortOrder`), mate
|
|
|
47
47
|
- Base cost: `OBJECT` rules with `baseComponent: true`, one `BASE` line each. Only in `FULL`; a line that is `OK` with a zero amount is left out.
|
|
48
48
|
- Object rules: the other `OBJECT` level rules, one line each.
|
|
49
49
|
- Materials: for every room selection with a product, the `MATERIAL_TYPE` rule for the category and the product's material type is used if one exists, otherwise the `CATEGORY` rule. Without either, the line is `NOT_CALCULATED` (`RULE_NOT_FOUND`). Unknown or unavailable products give `UNAVAILABLE`. Each line carries [components](#line-components).
|
|
50
|
-
- Engineering and additional: package items marked `includedInBase` become `INCLUDED` lines with a zero amount
|
|
50
|
+
- Engineering and additional: package items marked `includedInBase` become `INCLUDED` lines with a zero amount. In `PARTIAL` no package items are priced at all; the section holds only options. Options are priced by unit (`PIECE`, `ROOM_SQM`, `ROOM`, `PROJECT`). An option has exactly one price source: `priceCents` with no `priceCoefficientKeys`, or `priceCents: null` with keys, in which case the unit price is the sum of those coefficients in dollars (`PricingDataSchema` rejects both and neither; read the price from the line's `unitPriceCents`). A missing coefficient gives an `ERROR` line (`UNKNOWN_COEFFICIENT`). An option whose `scopes` do not include the configuration scope is `UNAVAILABLE` with `OPTION_SCOPE_NOT_ALLOWED`.
|
|
51
51
|
|
|
52
52
|
Result fields:
|
|
53
53
|
|
|
@@ -193,7 +193,7 @@ Every schema is a zod schema with an inferred type of the same name without the
|
|
|
193
193
|
| `totalAreaSqm` | required | `null` |
|
|
194
194
|
| `BASE` section | `OBJECT` rules with `baseComponent: true`, one line each | none |
|
|
195
195
|
| Material lines | `MATERIAL` component only | `MATERIAL`, `LABOR` and `PREP` components |
|
|
196
|
-
|
|
|
196
|
+
| Engineering package items | priced, `INCLUDED` with `includedInBase` | none |
|
|
197
197
|
| `totalArea` variable | `totalAreaSqm` | `roomsFloorArea` |
|
|
198
198
|
| Area warnings | `ROOMS_AREA_EXCEEDS_TOTAL`, `ROOMS_AREA_BELOW_TOTAL` | none |
|
|
199
199
|
|
package/dist/index.cjs
CHANGED
|
@@ -51,13 +51,10 @@ function toCents(dollars) {
|
|
|
51
51
|
return cents;
|
|
52
52
|
}
|
|
53
53
|
//#endregion
|
|
54
|
-
//#region src/variables/putty.ts
|
|
55
|
-
function puttyRequiredOn(room, surface, categories) {
|
|
56
|
-
const selected = categories.filter((category) => category.surface === surface && room.selections.some((selection) => selection.categoryId === category.id));
|
|
57
|
-
return selected.length === 0 || selected.some((category) => category.requiresPutty);
|
|
58
|
-
}
|
|
59
|
-
//#endregion
|
|
60
54
|
//#region src/variables/room.ts
|
|
55
|
+
function surfaceAreaOf(variables, surface) {
|
|
56
|
+
return surface === "WALLS" ? variables.wallArea : variables.floorArea;
|
|
57
|
+
}
|
|
61
58
|
function roomVariables(room) {
|
|
62
59
|
const perimeter = 2 * (room.lengthM + room.widthM);
|
|
63
60
|
return {
|
|
@@ -72,6 +69,63 @@ function roomVariables(room) {
|
|
|
72
69
|
};
|
|
73
70
|
}
|
|
74
71
|
//#endregion
|
|
72
|
+
//#region src/variables/putty.ts
|
|
73
|
+
function puttyRequiredOn(room, surface, categories) {
|
|
74
|
+
const selected = categories.filter((category) => category.surface === surface && room.selections.some((selection) => selection.categoryId === category.id));
|
|
75
|
+
return selected.length === 0 || selected.some((category) => category.requiresPutty);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/variables/share.ts
|
|
79
|
+
function isShareSurface(surface) {
|
|
80
|
+
return surface === "WALLS" || surface === "FLOOR";
|
|
81
|
+
}
|
|
82
|
+
function surfaceShares(room, surface, categories) {
|
|
83
|
+
const selections = room.selections.filter((selection) => selection.productId !== null && categories.some((category) => category.id === selection.categoryId && category.surface === surface));
|
|
84
|
+
return {
|
|
85
|
+
selections,
|
|
86
|
+
shared: selections.some((selection) => selection.areaShare !== void 0),
|
|
87
|
+
complete: selections.every((selection) => selection.areaShare !== void 0),
|
|
88
|
+
totalPercent: selections.reduce((sum, selection) => sum + (selection.areaShare ?? 0), 0)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function areaShareOf(selection) {
|
|
92
|
+
return selection.areaShare === void 0 ? 1 : selection.areaShare / 100;
|
|
93
|
+
}
|
|
94
|
+
function areaShareError(room, selection, categories) {
|
|
95
|
+
const category = categories.find((item) => item.id === selection.categoryId);
|
|
96
|
+
if (category === void 0) return null;
|
|
97
|
+
if (!isShareSurface(category.surface)) return selection.areaShare === void 0 ? null : {
|
|
98
|
+
code: "AREA_SHARE_NOT_ALLOWED",
|
|
99
|
+
params: {
|
|
100
|
+
categoryId: category.id,
|
|
101
|
+
surface: category.surface
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const shares = surfaceShares(room, category.surface, categories);
|
|
105
|
+
const params = {
|
|
106
|
+
roomClientId: room.clientId,
|
|
107
|
+
surface: category.surface
|
|
108
|
+
};
|
|
109
|
+
if (shares.shared && !shares.complete) return {
|
|
110
|
+
code: "AREA_SHARE_MISSING",
|
|
111
|
+
params
|
|
112
|
+
};
|
|
113
|
+
if (shares.totalPercent > 100) return {
|
|
114
|
+
code: "AREA_SHARE_EXCEEDS_SURFACE",
|
|
115
|
+
params: {
|
|
116
|
+
...params,
|
|
117
|
+
percent: shares.totalPercent
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function puttyShareOn(room, surface, categories) {
|
|
123
|
+
const shares = surface === "WALLS" ? surfaceShares(room, surface, categories) : null;
|
|
124
|
+
if (shares === null || !shares.shared) return puttyRequiredOn(room, surface, categories) ? 1 : 0;
|
|
125
|
+
const needing = shares.selections.filter((selection) => categories.find((category) => category.id === selection.categoryId)?.requiresPutty);
|
|
126
|
+
return Math.min(1, needing.reduce((sum, selection) => sum + areaShareOf(selection), 0));
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
75
129
|
//#region src/variables/object.ts
|
|
76
130
|
const COUNTED_AS_ROOMS = [
|
|
77
131
|
"LIVING_ROOM",
|
|
@@ -100,7 +154,7 @@ function objectVariables(configuration, categories) {
|
|
|
100
154
|
bathroomsCount: configuration.rooms.filter((room) => room.roomTypeCode === "BATHROOM").length,
|
|
101
155
|
roomsFloorArea,
|
|
102
156
|
roomsWallArea: sum((room) => room.wallArea),
|
|
103
|
-
roomsPuttyWallArea: configuration.rooms.reduce((total, room) =>
|
|
157
|
+
roomsPuttyWallArea: configuration.rooms.reduce((total, room) => total + roomVariables(room).wallArea * puttyShareOn(room, "WALLS", categories), 0),
|
|
104
158
|
roomsPerimeter: sum((room) => room.perimeter),
|
|
105
159
|
wetRoomsCount: rooms.filter((room) => WET_ROOMS.includes(room.roomType)).length
|
|
106
160
|
};
|
|
@@ -268,7 +322,9 @@ function packageLine(configuration, item) {
|
|
|
268
322
|
}, item.priceCents / 100 * quantity);
|
|
269
323
|
}
|
|
270
324
|
function engineeringLines(configuration, data) {
|
|
271
|
-
|
|
325
|
+
const options = optionLines(configuration, data, "ENGINEERING");
|
|
326
|
+
if (configuration.scope === "PARTIAL") return options;
|
|
327
|
+
return [...data.packageItems.map((item) => packageLine(configuration, item)), ...options];
|
|
272
328
|
}
|
|
273
329
|
//#endregion
|
|
274
330
|
//#region src/formula/language.ts
|
|
@@ -733,11 +789,6 @@ function applySurfacePrep(data, room, lines, objectScope) {
|
|
|
733
789
|
...roomVariables(room),
|
|
734
790
|
...objectScope
|
|
735
791
|
};
|
|
736
|
-
const areas = {
|
|
737
|
-
FLOOR: variables.floorArea,
|
|
738
|
-
CEILING: variables.floorArea,
|
|
739
|
-
WALLS: variables.wallArea
|
|
740
|
-
};
|
|
741
792
|
const surfaces = /* @__PURE__ */ new Map();
|
|
742
793
|
lines.forEach((line, index) => {
|
|
743
794
|
const category = data.categories.find((item) => item.id === line.categoryId);
|
|
@@ -749,11 +800,14 @@ function applySurfacePrep(data, room, lines, objectScope) {
|
|
|
749
800
|
for (const [surface, indexes] of surfaces) {
|
|
750
801
|
const rule = surfaceRuleOf(data, surface);
|
|
751
802
|
if (rule === void 0) continue;
|
|
803
|
+
const surfaceArea = surfaceAreaOf(variables, surface);
|
|
804
|
+
const puttyShare = puttyShareOn(room, surface, data.categories);
|
|
752
805
|
const outcome = runNumericFormula(rule.costFormula, {
|
|
753
806
|
variables: {
|
|
754
807
|
...variables,
|
|
755
|
-
surfaceArea
|
|
756
|
-
puttyRequired:
|
|
808
|
+
surfaceArea,
|
|
809
|
+
puttyRequired: puttyShare > 0,
|
|
810
|
+
puttyArea: surfaceArea * puttyShare
|
|
757
811
|
},
|
|
758
812
|
coefficients: data.coefficients
|
|
759
813
|
});
|
|
@@ -786,7 +840,9 @@ function selectRule(data, categoryId, materialTypeId) {
|
|
|
786
840
|
const rules = data.rules.filter(isMaterialRule);
|
|
787
841
|
return rules.find((rule) => rule.level === "MATERIAL_TYPE" && rule.categoryId === categoryId && rule.materialTypeId === materialTypeId) ?? rules.find((rule) => rule.level === "CATEGORY" && rule.categoryId === categoryId);
|
|
788
842
|
}
|
|
789
|
-
function materialLine(configuration, data, room,
|
|
843
|
+
function materialLine(configuration, data, room, selection, objectScope) {
|
|
844
|
+
const { categoryId } = selection;
|
|
845
|
+
const productId = selection.productId;
|
|
790
846
|
const init = {
|
|
791
847
|
id: lineId("MATERIALS", room.clientId, categoryId),
|
|
792
848
|
section: "MATERIALS",
|
|
@@ -794,6 +850,8 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
794
850
|
categoryId,
|
|
795
851
|
productId
|
|
796
852
|
};
|
|
853
|
+
const shareError = areaShareError(room, selection, data.categories);
|
|
854
|
+
if (shareError !== null) return failedLine(init, "ERROR", shareError);
|
|
797
855
|
const category = data.categories.find((item) => item.id === categoryId);
|
|
798
856
|
const product = data.products.find((item) => item.id === productId && item.categoryId === categoryId);
|
|
799
857
|
if (product === void 0 || category === void 0 || !product.available) return failedLine(init, "UNAVAILABLE", {
|
|
@@ -815,8 +873,12 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
815
873
|
materialTypeId: product.materialTypeId
|
|
816
874
|
}
|
|
817
875
|
});
|
|
876
|
+
const base = roomVariables(room);
|
|
877
|
+
const share = areaShareOf(selection);
|
|
818
878
|
const variables = {
|
|
819
|
-
...
|
|
879
|
+
...base,
|
|
880
|
+
wallArea: category.surface === "WALLS" ? base.wallArea * share : base.wallArea,
|
|
881
|
+
floorArea: category.surface === "FLOOR" ? base.floorArea * share : base.floorArea,
|
|
820
882
|
...productVariables(product, category),
|
|
821
883
|
...objectScope
|
|
822
884
|
};
|
|
@@ -886,7 +948,7 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
886
948
|
}
|
|
887
949
|
function materialLines(configuration, data, objectScope) {
|
|
888
950
|
return configuration.rooms.flatMap((room) => {
|
|
889
|
-
const lines = room.selections.flatMap((
|
|
951
|
+
const lines = room.selections.flatMap((selection) => selection.productId === null ? [] : [materialLine(configuration, data, room, selection, objectScope)]);
|
|
890
952
|
return configuration.scope === "PARTIAL" ? applySurfacePrep(data, room, lines, objectScope) : lines;
|
|
891
953
|
});
|
|
892
954
|
}
|
|
@@ -984,7 +1046,8 @@ const RangeSchema = zod.z.object({
|
|
|
984
1046
|
//#region src/schemas/configuration.ts
|
|
985
1047
|
const RoomSelectionSchema = zod.z.object({
|
|
986
1048
|
categoryId: zod.z.string(),
|
|
987
|
-
productId: zod.z.string().nullable()
|
|
1049
|
+
productId: zod.z.string().nullable(),
|
|
1050
|
+
areaShare: zod.z.int().min(1).max(100).optional()
|
|
988
1051
|
});
|
|
989
1052
|
const RoomConfigurationSchema = zod.z.object({
|
|
990
1053
|
clientId: zod.z.string(),
|
|
@@ -1224,6 +1287,7 @@ const RuleSampleSchema = zod.z.object({
|
|
|
1224
1287
|
}).optional(),
|
|
1225
1288
|
surfaceArea: zod.z.number().min(0).optional(),
|
|
1226
1289
|
puttyRequired: zod.z.boolean().optional(),
|
|
1290
|
+
puttyArea: zod.z.number().min(0).optional(),
|
|
1227
1291
|
object: zod.z.object({
|
|
1228
1292
|
scope: ScopeSchema,
|
|
1229
1293
|
totalAreaSqm: zod.z.number().positive().nullable(),
|
|
@@ -1302,10 +1366,32 @@ function roomsAreaWarnings(configuration, limits) {
|
|
|
1302
1366
|
params: { missingSqm }
|
|
1303
1367
|
}] : [];
|
|
1304
1368
|
}
|
|
1305
|
-
|
|
1369
|
+
const SHARE_SURFACES = ["WALLS", "FLOOR"];
|
|
1370
|
+
function areaShareWarnings(configuration, categories) {
|
|
1371
|
+
return configuration.rooms.flatMap((room) => {
|
|
1372
|
+
const variables = roomVariables(room);
|
|
1373
|
+
return SHARE_SURFACES.flatMap((surface) => {
|
|
1374
|
+
const shares = surfaceShares(room, surface, categories);
|
|
1375
|
+
if (!shares.shared || !shares.complete || shares.totalPercent >= 100) return [];
|
|
1376
|
+
const percent = 100 - shares.totalPercent;
|
|
1377
|
+
const area = surfaceAreaOf(variables, surface);
|
|
1378
|
+
return [{
|
|
1379
|
+
code: "AREA_SHARE_UNALLOCATED",
|
|
1380
|
+
params: {
|
|
1381
|
+
roomClientId: room.clientId,
|
|
1382
|
+
surface,
|
|
1383
|
+
percent,
|
|
1384
|
+
sqm: roundArea(area * percent / 100)
|
|
1385
|
+
}
|
|
1386
|
+
}];
|
|
1387
|
+
});
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
function estimateWarnings(lines, configuration, rate, data) {
|
|
1306
1391
|
return [
|
|
1307
1392
|
...collectWarnings(lines),
|
|
1308
|
-
...roomsAreaWarnings(configuration, limits),
|
|
1393
|
+
...roomsAreaWarnings(configuration, data.limits),
|
|
1394
|
+
...areaShareWarnings(configuration, data.categories),
|
|
1309
1395
|
...configuration.currency === "UAH" && rate === null ? [{ code: "EXCHANGE_RATE_UNAVAILABLE" }] : []
|
|
1310
1396
|
];
|
|
1311
1397
|
}
|
|
@@ -1333,7 +1419,7 @@ function estimate(configuration, data) {
|
|
|
1333
1419
|
exchangeRate: data.exchangeRate,
|
|
1334
1420
|
lines,
|
|
1335
1421
|
totals: computeTotals(lines, rate !== null),
|
|
1336
|
-
warnings: estimateWarnings(lines, configuration, rate, data
|
|
1422
|
+
warnings: estimateWarnings(lines, configuration, rate, data),
|
|
1337
1423
|
incomplete: isIncomplete(lines)
|
|
1338
1424
|
};
|
|
1339
1425
|
}
|
|
@@ -1428,11 +1514,13 @@ function scopeOf(level, sample) {
|
|
|
1428
1514
|
code: "SAMPLE_INCOMPLETE",
|
|
1429
1515
|
params: { missing: room === void 0 ? "room" : "surfaceArea" }
|
|
1430
1516
|
} };
|
|
1517
|
+
const puttyRequired = sample.puttyRequired ?? true;
|
|
1431
1518
|
return { scope: {
|
|
1432
1519
|
...roomVariables(room),
|
|
1433
1520
|
...objectScope(sample),
|
|
1434
1521
|
surfaceArea,
|
|
1435
|
-
puttyRequired
|
|
1522
|
+
puttyRequired,
|
|
1523
|
+
puttyArea: sample.puttyArea ?? (puttyRequired ? surfaceArea : 0)
|
|
1436
1524
|
} };
|
|
1437
1525
|
}
|
|
1438
1526
|
if (room === void 0 || product === void 0) return { error: {
|
|
@@ -1581,6 +1669,7 @@ const FORMULA_SCOPE_VARIABLES = {
|
|
|
1581
1669
|
};
|
|
1582
1670
|
const FORMULA_QUANTITY_VARIABLE = number("quantity");
|
|
1583
1671
|
const FORMULA_SURFACE_AREA_VARIABLE = number("surfaceArea");
|
|
1672
|
+
const FORMULA_PUTTY_AREA_VARIABLE = number("puttyArea");
|
|
1584
1673
|
const FORMULA_PUTTY_REQUIRED_VARIABLE = boolean("puttyRequired");
|
|
1585
1674
|
const FORMULA_VARIABLES = {
|
|
1586
1675
|
OBJECT: FORMULA_SCOPE_VARIABLES.object,
|
|
@@ -1598,7 +1687,8 @@ const FORMULA_VARIABLES = {
|
|
|
1598
1687
|
...FORMULA_SCOPE_VARIABLES.room,
|
|
1599
1688
|
...FORMULA_SCOPE_VARIABLES.object,
|
|
1600
1689
|
FORMULA_SURFACE_AREA_VARIABLE,
|
|
1601
|
-
FORMULA_PUTTY_REQUIRED_VARIABLE
|
|
1690
|
+
FORMULA_PUTTY_REQUIRED_VARIABLE,
|
|
1691
|
+
FORMULA_PUTTY_AREA_VARIABLE
|
|
1602
1692
|
]
|
|
1603
1693
|
};
|
|
1604
1694
|
const ALL_VARIABLE_NAMES = new Set([...FORMULA_VARIABLES.CATEGORY, ...FORMULA_VARIABLES.SURFACE].map((variable) => variable.name));
|
|
@@ -1826,6 +1916,7 @@ exports.ExchangeRateSchema = ExchangeRateSchema;
|
|
|
1826
1916
|
exports.FORMULA_FUNCTIONS = FORMULA_FUNCTIONS;
|
|
1827
1917
|
exports.FORMULA_FUNCTION_TABLE = FORMULA_FUNCTION_TABLE;
|
|
1828
1918
|
exports.FORMULA_LIMITS = FORMULA_LIMITS;
|
|
1919
|
+
exports.FORMULA_PUTTY_AREA_VARIABLE = FORMULA_PUTTY_AREA_VARIABLE;
|
|
1829
1920
|
exports.FORMULA_PUTTY_REQUIRED_VARIABLE = FORMULA_PUTTY_REQUIRED_VARIABLE;
|
|
1830
1921
|
exports.FORMULA_QUANTITY_VARIABLE = FORMULA_QUANTITY_VARIABLE;
|
|
1831
1922
|
exports.FORMULA_SCOPE_VARIABLES = FORMULA_SCOPE_VARIABLES;
|
|
@@ -1862,11 +1953,14 @@ exports.ScopeSchema = ScopeSchema;
|
|
|
1862
1953
|
exports.SectionSchema = SectionSchema;
|
|
1863
1954
|
exports.SectionTotalSchema = SectionTotalSchema;
|
|
1864
1955
|
exports.SurfaceKindSchema = SurfaceKindSchema;
|
|
1956
|
+
exports.areaShareError = areaShareError;
|
|
1957
|
+
exports.areaShareOf = areaShareOf;
|
|
1865
1958
|
exports.checkLimits = checkLimits;
|
|
1866
1959
|
exports.convertCentsToKopecks = convertCentsToKopecks;
|
|
1867
1960
|
exports.estimate = estimate;
|
|
1868
1961
|
exports.evaluate = evaluate;
|
|
1869
1962
|
exports.findOptionPriceSourceViolation = findOptionPriceSourceViolation;
|
|
1963
|
+
exports.isShareSurface = isShareSurface;
|
|
1870
1964
|
exports.levenshtein = levenshtein;
|
|
1871
1965
|
exports.objectVariables = objectVariables;
|
|
1872
1966
|
exports.parse = parse;
|
|
@@ -1874,8 +1968,11 @@ exports.previewRule = previewRule;
|
|
|
1874
1968
|
exports.productVariables = productVariables;
|
|
1875
1969
|
exports.productVariablesOf = productVariablesOf;
|
|
1876
1970
|
exports.puttyRequiredOn = puttyRequiredOn;
|
|
1971
|
+
exports.puttyShareOn = puttyShareOn;
|
|
1877
1972
|
exports.roomVariables = roomVariables;
|
|
1878
1973
|
exports.suggestNearest = suggestNearest;
|
|
1974
|
+
exports.surfaceAreaOf = surfaceAreaOf;
|
|
1975
|
+
exports.surfaceShares = surfaceShares;
|
|
1879
1976
|
exports.toCents = toCents;
|
|
1880
1977
|
exports.totalAreaOf = totalAreaOf;
|
|
1881
1978
|
exports.validateFormula = validateFormula;
|
package/dist/index.d.cts
CHANGED
|
@@ -106,6 +106,7 @@ export type Range = z.infer<typeof RangeSchema>;
|
|
|
106
106
|
export declare const RoomSelectionSchema: z.ZodObject<{
|
|
107
107
|
categoryId: z.ZodString;
|
|
108
108
|
productId: z.ZodNullable<z.ZodString>;
|
|
109
|
+
areaShare: z.ZodOptional<z.ZodInt>;
|
|
109
110
|
}, z.core.$strip>;
|
|
110
111
|
export type RoomSelection = z.infer<typeof RoomSelectionSchema>;
|
|
111
112
|
export declare const RoomConfigurationSchema: z.ZodObject<{
|
|
@@ -127,6 +128,7 @@ export declare const RoomConfigurationSchema: z.ZodObject<{
|
|
|
127
128
|
selections: z.ZodArray<z.ZodObject<{
|
|
128
129
|
categoryId: z.ZodString;
|
|
129
130
|
productId: z.ZodNullable<z.ZodString>;
|
|
131
|
+
areaShare: z.ZodOptional<z.ZodInt>;
|
|
130
132
|
}, z.core.$strip>>;
|
|
131
133
|
}, z.core.$strip>;
|
|
132
134
|
export type RoomConfiguration = z.infer<typeof RoomConfigurationSchema>;
|
|
@@ -168,6 +170,7 @@ export declare const ConfigurationSchema: z.ZodObject<{
|
|
|
168
170
|
selections: z.ZodArray<z.ZodObject<{
|
|
169
171
|
categoryId: z.ZodString;
|
|
170
172
|
productId: z.ZodNullable<z.ZodString>;
|
|
173
|
+
areaShare: z.ZodOptional<z.ZodInt>;
|
|
171
174
|
}, z.core.$strip>>;
|
|
172
175
|
}, z.core.$strip>>;
|
|
173
176
|
options: z.ZodArray<z.ZodObject<{
|
|
@@ -383,6 +386,7 @@ export declare const RuleSampleSchema: z.ZodObject<{
|
|
|
383
386
|
}, z.core.$strip>>;
|
|
384
387
|
surfaceArea: z.ZodOptional<z.ZodNumber>;
|
|
385
388
|
puttyRequired: z.ZodOptional<z.ZodBoolean>;
|
|
389
|
+
puttyArea: z.ZodOptional<z.ZodNumber>;
|
|
386
390
|
object: z.ZodObject<{
|
|
387
391
|
scope: z.ZodEnum<{
|
|
388
392
|
FULL: "FULL";
|
|
@@ -820,6 +824,7 @@ export interface FormulaVariable {
|
|
|
820
824
|
export declare const FORMULA_SCOPE_VARIABLES: Record<FormulaScope, readonly FormulaVariable[]>;
|
|
821
825
|
export declare const FORMULA_QUANTITY_VARIABLE: FormulaVariable;
|
|
822
826
|
export declare const FORMULA_SURFACE_AREA_VARIABLE: FormulaVariable;
|
|
827
|
+
export declare const FORMULA_PUTTY_AREA_VARIABLE: FormulaVariable;
|
|
823
828
|
export declare const FORMULA_PUTTY_REQUIRED_VARIABLE: FormulaVariable;
|
|
824
829
|
export declare const FORMULA_VARIABLES: Record<RuleLevel, readonly FormulaVariable[]>;
|
|
825
830
|
export interface ValidateFormulaInput {
|
|
@@ -882,6 +887,22 @@ export interface RoomVariables {
|
|
|
882
887
|
readonly heatedFloor: boolean;
|
|
883
888
|
readonly roomType: RoomTypeCode;
|
|
884
889
|
}
|
|
890
|
+
export type MeasuredSurface = 'FLOOR' | 'WALLS' | 'CEILING';
|
|
891
|
+
export declare function surfaceAreaOf(variables: Pick<RoomVariables, 'floorArea' | 'wallArea'>, surface: MeasuredSurface): number;
|
|
885
892
|
export type RoomDimensions = Pick<RoomConfiguration, 'roomTypeCode' | 'lengthM' | 'widthM' | 'heightM' | 'heatedFloor'>;
|
|
886
893
|
export declare function roomVariables(room: RoomDimensions): RoomVariables;
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region src/variables/share.d.ts
|
|
896
|
+
export type ShareSurface = 'WALLS' | 'FLOOR';
|
|
897
|
+
export interface SurfaceShares {
|
|
898
|
+
readonly selections: readonly RoomSelection[];
|
|
899
|
+
readonly shared: boolean;
|
|
900
|
+
readonly complete: boolean;
|
|
901
|
+
readonly totalPercent: number;
|
|
902
|
+
}
|
|
903
|
+
export declare function isShareSurface(surface: PricingCategory['surface']): surface is ShareSurface;
|
|
904
|
+
export declare function surfaceShares(room: RoomConfiguration, surface: ShareSurface, categories: readonly PricingCategory[]): SurfaceShares;
|
|
905
|
+
export declare function areaShareOf(selection: RoomSelection): number;
|
|
906
|
+
export declare function areaShareError(room: RoomConfiguration, selection: RoomSelection, categories: readonly PricingCategory[]): EstimateMessage | null;
|
|
907
|
+
export declare function puttyShareOn(room: RoomConfiguration, surface: MeasuredSurface, categories: readonly PricingCategory[]): number;
|
|
887
908
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -106,6 +106,7 @@ export type Range = z.infer<typeof RangeSchema>;
|
|
|
106
106
|
export declare const RoomSelectionSchema: z.ZodObject<{
|
|
107
107
|
categoryId: z.ZodString;
|
|
108
108
|
productId: z.ZodNullable<z.ZodString>;
|
|
109
|
+
areaShare: z.ZodOptional<z.ZodInt>;
|
|
109
110
|
}, z.core.$strip>;
|
|
110
111
|
export type RoomSelection = z.infer<typeof RoomSelectionSchema>;
|
|
111
112
|
export declare const RoomConfigurationSchema: z.ZodObject<{
|
|
@@ -127,6 +128,7 @@ export declare const RoomConfigurationSchema: z.ZodObject<{
|
|
|
127
128
|
selections: z.ZodArray<z.ZodObject<{
|
|
128
129
|
categoryId: z.ZodString;
|
|
129
130
|
productId: z.ZodNullable<z.ZodString>;
|
|
131
|
+
areaShare: z.ZodOptional<z.ZodInt>;
|
|
130
132
|
}, z.core.$strip>>;
|
|
131
133
|
}, z.core.$strip>;
|
|
132
134
|
export type RoomConfiguration = z.infer<typeof RoomConfigurationSchema>;
|
|
@@ -168,6 +170,7 @@ export declare const ConfigurationSchema: z.ZodObject<{
|
|
|
168
170
|
selections: z.ZodArray<z.ZodObject<{
|
|
169
171
|
categoryId: z.ZodString;
|
|
170
172
|
productId: z.ZodNullable<z.ZodString>;
|
|
173
|
+
areaShare: z.ZodOptional<z.ZodInt>;
|
|
171
174
|
}, z.core.$strip>>;
|
|
172
175
|
}, z.core.$strip>>;
|
|
173
176
|
options: z.ZodArray<z.ZodObject<{
|
|
@@ -383,6 +386,7 @@ export declare const RuleSampleSchema: z.ZodObject<{
|
|
|
383
386
|
}, z.core.$strip>>;
|
|
384
387
|
surfaceArea: z.ZodOptional<z.ZodNumber>;
|
|
385
388
|
puttyRequired: z.ZodOptional<z.ZodBoolean>;
|
|
389
|
+
puttyArea: z.ZodOptional<z.ZodNumber>;
|
|
386
390
|
object: z.ZodObject<{
|
|
387
391
|
scope: z.ZodEnum<{
|
|
388
392
|
FULL: "FULL";
|
|
@@ -820,6 +824,7 @@ export interface FormulaVariable {
|
|
|
820
824
|
export declare const FORMULA_SCOPE_VARIABLES: Record<FormulaScope, readonly FormulaVariable[]>;
|
|
821
825
|
export declare const FORMULA_QUANTITY_VARIABLE: FormulaVariable;
|
|
822
826
|
export declare const FORMULA_SURFACE_AREA_VARIABLE: FormulaVariable;
|
|
827
|
+
export declare const FORMULA_PUTTY_AREA_VARIABLE: FormulaVariable;
|
|
823
828
|
export declare const FORMULA_PUTTY_REQUIRED_VARIABLE: FormulaVariable;
|
|
824
829
|
export declare const FORMULA_VARIABLES: Record<RuleLevel, readonly FormulaVariable[]>;
|
|
825
830
|
export interface ValidateFormulaInput {
|
|
@@ -882,6 +887,22 @@ export interface RoomVariables {
|
|
|
882
887
|
readonly heatedFloor: boolean;
|
|
883
888
|
readonly roomType: RoomTypeCode;
|
|
884
889
|
}
|
|
890
|
+
export type MeasuredSurface = 'FLOOR' | 'WALLS' | 'CEILING';
|
|
891
|
+
export declare function surfaceAreaOf(variables: Pick<RoomVariables, 'floorArea' | 'wallArea'>, surface: MeasuredSurface): number;
|
|
885
892
|
export type RoomDimensions = Pick<RoomConfiguration, 'roomTypeCode' | 'lengthM' | 'widthM' | 'heightM' | 'heatedFloor'>;
|
|
886
893
|
export declare function roomVariables(room: RoomDimensions): RoomVariables;
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region src/variables/share.d.ts
|
|
896
|
+
export type ShareSurface = 'WALLS' | 'FLOOR';
|
|
897
|
+
export interface SurfaceShares {
|
|
898
|
+
readonly selections: readonly RoomSelection[];
|
|
899
|
+
readonly shared: boolean;
|
|
900
|
+
readonly complete: boolean;
|
|
901
|
+
readonly totalPercent: number;
|
|
902
|
+
}
|
|
903
|
+
export declare function isShareSurface(surface: PricingCategory['surface']): surface is ShareSurface;
|
|
904
|
+
export declare function surfaceShares(room: RoomConfiguration, surface: ShareSurface, categories: readonly PricingCategory[]): SurfaceShares;
|
|
905
|
+
export declare function areaShareOf(selection: RoomSelection): number;
|
|
906
|
+
export declare function areaShareError(room: RoomConfiguration, selection: RoomSelection, categories: readonly PricingCategory[]): EstimateMessage | null;
|
|
907
|
+
export declare function puttyShareOn(room: RoomConfiguration, surface: MeasuredSurface, categories: readonly PricingCategory[]): number;
|
|
887
908
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -26,13 +26,10 @@ function toCents(dollars) {
|
|
|
26
26
|
return cents;
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region src/variables/putty.ts
|
|
30
|
-
function puttyRequiredOn(room, surface, categories) {
|
|
31
|
-
const selected = categories.filter((category) => category.surface === surface && room.selections.some((selection) => selection.categoryId === category.id));
|
|
32
|
-
return selected.length === 0 || selected.some((category) => category.requiresPutty);
|
|
33
|
-
}
|
|
34
|
-
//#endregion
|
|
35
29
|
//#region src/variables/room.ts
|
|
30
|
+
function surfaceAreaOf(variables, surface) {
|
|
31
|
+
return surface === "WALLS" ? variables.wallArea : variables.floorArea;
|
|
32
|
+
}
|
|
36
33
|
function roomVariables(room) {
|
|
37
34
|
const perimeter = 2 * (room.lengthM + room.widthM);
|
|
38
35
|
return {
|
|
@@ -47,6 +44,63 @@ function roomVariables(room) {
|
|
|
47
44
|
};
|
|
48
45
|
}
|
|
49
46
|
//#endregion
|
|
47
|
+
//#region src/variables/putty.ts
|
|
48
|
+
function puttyRequiredOn(room, surface, categories) {
|
|
49
|
+
const selected = categories.filter((category) => category.surface === surface && room.selections.some((selection) => selection.categoryId === category.id));
|
|
50
|
+
return selected.length === 0 || selected.some((category) => category.requiresPutty);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/variables/share.ts
|
|
54
|
+
function isShareSurface(surface) {
|
|
55
|
+
return surface === "WALLS" || surface === "FLOOR";
|
|
56
|
+
}
|
|
57
|
+
function surfaceShares(room, surface, categories) {
|
|
58
|
+
const selections = room.selections.filter((selection) => selection.productId !== null && categories.some((category) => category.id === selection.categoryId && category.surface === surface));
|
|
59
|
+
return {
|
|
60
|
+
selections,
|
|
61
|
+
shared: selections.some((selection) => selection.areaShare !== void 0),
|
|
62
|
+
complete: selections.every((selection) => selection.areaShare !== void 0),
|
|
63
|
+
totalPercent: selections.reduce((sum, selection) => sum + (selection.areaShare ?? 0), 0)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function areaShareOf(selection) {
|
|
67
|
+
return selection.areaShare === void 0 ? 1 : selection.areaShare / 100;
|
|
68
|
+
}
|
|
69
|
+
function areaShareError(room, selection, categories) {
|
|
70
|
+
const category = categories.find((item) => item.id === selection.categoryId);
|
|
71
|
+
if (category === void 0) return null;
|
|
72
|
+
if (!isShareSurface(category.surface)) return selection.areaShare === void 0 ? null : {
|
|
73
|
+
code: "AREA_SHARE_NOT_ALLOWED",
|
|
74
|
+
params: {
|
|
75
|
+
categoryId: category.id,
|
|
76
|
+
surface: category.surface
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const shares = surfaceShares(room, category.surface, categories);
|
|
80
|
+
const params = {
|
|
81
|
+
roomClientId: room.clientId,
|
|
82
|
+
surface: category.surface
|
|
83
|
+
};
|
|
84
|
+
if (shares.shared && !shares.complete) return {
|
|
85
|
+
code: "AREA_SHARE_MISSING",
|
|
86
|
+
params
|
|
87
|
+
};
|
|
88
|
+
if (shares.totalPercent > 100) return {
|
|
89
|
+
code: "AREA_SHARE_EXCEEDS_SURFACE",
|
|
90
|
+
params: {
|
|
91
|
+
...params,
|
|
92
|
+
percent: shares.totalPercent
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function puttyShareOn(room, surface, categories) {
|
|
98
|
+
const shares = surface === "WALLS" ? surfaceShares(room, surface, categories) : null;
|
|
99
|
+
if (shares === null || !shares.shared) return puttyRequiredOn(room, surface, categories) ? 1 : 0;
|
|
100
|
+
const needing = shares.selections.filter((selection) => categories.find((category) => category.id === selection.categoryId)?.requiresPutty);
|
|
101
|
+
return Math.min(1, needing.reduce((sum, selection) => sum + areaShareOf(selection), 0));
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
50
104
|
//#region src/variables/object.ts
|
|
51
105
|
const COUNTED_AS_ROOMS = [
|
|
52
106
|
"LIVING_ROOM",
|
|
@@ -75,7 +129,7 @@ function objectVariables(configuration, categories) {
|
|
|
75
129
|
bathroomsCount: configuration.rooms.filter((room) => room.roomTypeCode === "BATHROOM").length,
|
|
76
130
|
roomsFloorArea,
|
|
77
131
|
roomsWallArea: sum((room) => room.wallArea),
|
|
78
|
-
roomsPuttyWallArea: configuration.rooms.reduce((total, room) =>
|
|
132
|
+
roomsPuttyWallArea: configuration.rooms.reduce((total, room) => total + roomVariables(room).wallArea * puttyShareOn(room, "WALLS", categories), 0),
|
|
79
133
|
roomsPerimeter: sum((room) => room.perimeter),
|
|
80
134
|
wetRoomsCount: rooms.filter((room) => WET_ROOMS.includes(room.roomType)).length
|
|
81
135
|
};
|
|
@@ -243,7 +297,9 @@ function packageLine(configuration, item) {
|
|
|
243
297
|
}, item.priceCents / 100 * quantity);
|
|
244
298
|
}
|
|
245
299
|
function engineeringLines(configuration, data) {
|
|
246
|
-
|
|
300
|
+
const options = optionLines(configuration, data, "ENGINEERING");
|
|
301
|
+
if (configuration.scope === "PARTIAL") return options;
|
|
302
|
+
return [...data.packageItems.map((item) => packageLine(configuration, item)), ...options];
|
|
247
303
|
}
|
|
248
304
|
//#endregion
|
|
249
305
|
//#region src/formula/language.ts
|
|
@@ -708,11 +764,6 @@ function applySurfacePrep(data, room, lines, objectScope) {
|
|
|
708
764
|
...roomVariables(room),
|
|
709
765
|
...objectScope
|
|
710
766
|
};
|
|
711
|
-
const areas = {
|
|
712
|
-
FLOOR: variables.floorArea,
|
|
713
|
-
CEILING: variables.floorArea,
|
|
714
|
-
WALLS: variables.wallArea
|
|
715
|
-
};
|
|
716
767
|
const surfaces = /* @__PURE__ */ new Map();
|
|
717
768
|
lines.forEach((line, index) => {
|
|
718
769
|
const category = data.categories.find((item) => item.id === line.categoryId);
|
|
@@ -724,11 +775,14 @@ function applySurfacePrep(data, room, lines, objectScope) {
|
|
|
724
775
|
for (const [surface, indexes] of surfaces) {
|
|
725
776
|
const rule = surfaceRuleOf(data, surface);
|
|
726
777
|
if (rule === void 0) continue;
|
|
778
|
+
const surfaceArea = surfaceAreaOf(variables, surface);
|
|
779
|
+
const puttyShare = puttyShareOn(room, surface, data.categories);
|
|
727
780
|
const outcome = runNumericFormula(rule.costFormula, {
|
|
728
781
|
variables: {
|
|
729
782
|
...variables,
|
|
730
|
-
surfaceArea
|
|
731
|
-
puttyRequired:
|
|
783
|
+
surfaceArea,
|
|
784
|
+
puttyRequired: puttyShare > 0,
|
|
785
|
+
puttyArea: surfaceArea * puttyShare
|
|
732
786
|
},
|
|
733
787
|
coefficients: data.coefficients
|
|
734
788
|
});
|
|
@@ -761,7 +815,9 @@ function selectRule(data, categoryId, materialTypeId) {
|
|
|
761
815
|
const rules = data.rules.filter(isMaterialRule);
|
|
762
816
|
return rules.find((rule) => rule.level === "MATERIAL_TYPE" && rule.categoryId === categoryId && rule.materialTypeId === materialTypeId) ?? rules.find((rule) => rule.level === "CATEGORY" && rule.categoryId === categoryId);
|
|
763
817
|
}
|
|
764
|
-
function materialLine(configuration, data, room,
|
|
818
|
+
function materialLine(configuration, data, room, selection, objectScope) {
|
|
819
|
+
const { categoryId } = selection;
|
|
820
|
+
const productId = selection.productId;
|
|
765
821
|
const init = {
|
|
766
822
|
id: lineId("MATERIALS", room.clientId, categoryId),
|
|
767
823
|
section: "MATERIALS",
|
|
@@ -769,6 +825,8 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
769
825
|
categoryId,
|
|
770
826
|
productId
|
|
771
827
|
};
|
|
828
|
+
const shareError = areaShareError(room, selection, data.categories);
|
|
829
|
+
if (shareError !== null) return failedLine(init, "ERROR", shareError);
|
|
772
830
|
const category = data.categories.find((item) => item.id === categoryId);
|
|
773
831
|
const product = data.products.find((item) => item.id === productId && item.categoryId === categoryId);
|
|
774
832
|
if (product === void 0 || category === void 0 || !product.available) return failedLine(init, "UNAVAILABLE", {
|
|
@@ -790,8 +848,12 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
790
848
|
materialTypeId: product.materialTypeId
|
|
791
849
|
}
|
|
792
850
|
});
|
|
851
|
+
const base = roomVariables(room);
|
|
852
|
+
const share = areaShareOf(selection);
|
|
793
853
|
const variables = {
|
|
794
|
-
...
|
|
854
|
+
...base,
|
|
855
|
+
wallArea: category.surface === "WALLS" ? base.wallArea * share : base.wallArea,
|
|
856
|
+
floorArea: category.surface === "FLOOR" ? base.floorArea * share : base.floorArea,
|
|
795
857
|
...productVariables(product, category),
|
|
796
858
|
...objectScope
|
|
797
859
|
};
|
|
@@ -861,7 +923,7 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
861
923
|
}
|
|
862
924
|
function materialLines(configuration, data, objectScope) {
|
|
863
925
|
return configuration.rooms.flatMap((room) => {
|
|
864
|
-
const lines = room.selections.flatMap((
|
|
926
|
+
const lines = room.selections.flatMap((selection) => selection.productId === null ? [] : [materialLine(configuration, data, room, selection, objectScope)]);
|
|
865
927
|
return configuration.scope === "PARTIAL" ? applySurfacePrep(data, room, lines, objectScope) : lines;
|
|
866
928
|
});
|
|
867
929
|
}
|
|
@@ -959,7 +1021,8 @@ const RangeSchema = z.object({
|
|
|
959
1021
|
//#region src/schemas/configuration.ts
|
|
960
1022
|
const RoomSelectionSchema = z.object({
|
|
961
1023
|
categoryId: z.string(),
|
|
962
|
-
productId: z.string().nullable()
|
|
1024
|
+
productId: z.string().nullable(),
|
|
1025
|
+
areaShare: z.int().min(1).max(100).optional()
|
|
963
1026
|
});
|
|
964
1027
|
const RoomConfigurationSchema = z.object({
|
|
965
1028
|
clientId: z.string(),
|
|
@@ -1199,6 +1262,7 @@ const RuleSampleSchema = z.object({
|
|
|
1199
1262
|
}).optional(),
|
|
1200
1263
|
surfaceArea: z.number().min(0).optional(),
|
|
1201
1264
|
puttyRequired: z.boolean().optional(),
|
|
1265
|
+
puttyArea: z.number().min(0).optional(),
|
|
1202
1266
|
object: z.object({
|
|
1203
1267
|
scope: ScopeSchema,
|
|
1204
1268
|
totalAreaSqm: z.number().positive().nullable(),
|
|
@@ -1277,10 +1341,32 @@ function roomsAreaWarnings(configuration, limits) {
|
|
|
1277
1341
|
params: { missingSqm }
|
|
1278
1342
|
}] : [];
|
|
1279
1343
|
}
|
|
1280
|
-
|
|
1344
|
+
const SHARE_SURFACES = ["WALLS", "FLOOR"];
|
|
1345
|
+
function areaShareWarnings(configuration, categories) {
|
|
1346
|
+
return configuration.rooms.flatMap((room) => {
|
|
1347
|
+
const variables = roomVariables(room);
|
|
1348
|
+
return SHARE_SURFACES.flatMap((surface) => {
|
|
1349
|
+
const shares = surfaceShares(room, surface, categories);
|
|
1350
|
+
if (!shares.shared || !shares.complete || shares.totalPercent >= 100) return [];
|
|
1351
|
+
const percent = 100 - shares.totalPercent;
|
|
1352
|
+
const area = surfaceAreaOf(variables, surface);
|
|
1353
|
+
return [{
|
|
1354
|
+
code: "AREA_SHARE_UNALLOCATED",
|
|
1355
|
+
params: {
|
|
1356
|
+
roomClientId: room.clientId,
|
|
1357
|
+
surface,
|
|
1358
|
+
percent,
|
|
1359
|
+
sqm: roundArea(area * percent / 100)
|
|
1360
|
+
}
|
|
1361
|
+
}];
|
|
1362
|
+
});
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
function estimateWarnings(lines, configuration, rate, data) {
|
|
1281
1366
|
return [
|
|
1282
1367
|
...collectWarnings(lines),
|
|
1283
|
-
...roomsAreaWarnings(configuration, limits),
|
|
1368
|
+
...roomsAreaWarnings(configuration, data.limits),
|
|
1369
|
+
...areaShareWarnings(configuration, data.categories),
|
|
1284
1370
|
...configuration.currency === "UAH" && rate === null ? [{ code: "EXCHANGE_RATE_UNAVAILABLE" }] : []
|
|
1285
1371
|
];
|
|
1286
1372
|
}
|
|
@@ -1308,7 +1394,7 @@ function estimate(configuration, data) {
|
|
|
1308
1394
|
exchangeRate: data.exchangeRate,
|
|
1309
1395
|
lines,
|
|
1310
1396
|
totals: computeTotals(lines, rate !== null),
|
|
1311
|
-
warnings: estimateWarnings(lines, configuration, rate, data
|
|
1397
|
+
warnings: estimateWarnings(lines, configuration, rate, data),
|
|
1312
1398
|
incomplete: isIncomplete(lines)
|
|
1313
1399
|
};
|
|
1314
1400
|
}
|
|
@@ -1403,11 +1489,13 @@ function scopeOf(level, sample) {
|
|
|
1403
1489
|
code: "SAMPLE_INCOMPLETE",
|
|
1404
1490
|
params: { missing: room === void 0 ? "room" : "surfaceArea" }
|
|
1405
1491
|
} };
|
|
1492
|
+
const puttyRequired = sample.puttyRequired ?? true;
|
|
1406
1493
|
return { scope: {
|
|
1407
1494
|
...roomVariables(room),
|
|
1408
1495
|
...objectScope(sample),
|
|
1409
1496
|
surfaceArea,
|
|
1410
|
-
puttyRequired
|
|
1497
|
+
puttyRequired,
|
|
1498
|
+
puttyArea: sample.puttyArea ?? (puttyRequired ? surfaceArea : 0)
|
|
1411
1499
|
} };
|
|
1412
1500
|
}
|
|
1413
1501
|
if (room === void 0 || product === void 0) return { error: {
|
|
@@ -1556,6 +1644,7 @@ const FORMULA_SCOPE_VARIABLES = {
|
|
|
1556
1644
|
};
|
|
1557
1645
|
const FORMULA_QUANTITY_VARIABLE = number("quantity");
|
|
1558
1646
|
const FORMULA_SURFACE_AREA_VARIABLE = number("surfaceArea");
|
|
1647
|
+
const FORMULA_PUTTY_AREA_VARIABLE = number("puttyArea");
|
|
1559
1648
|
const FORMULA_PUTTY_REQUIRED_VARIABLE = boolean("puttyRequired");
|
|
1560
1649
|
const FORMULA_VARIABLES = {
|
|
1561
1650
|
OBJECT: FORMULA_SCOPE_VARIABLES.object,
|
|
@@ -1573,7 +1662,8 @@ const FORMULA_VARIABLES = {
|
|
|
1573
1662
|
...FORMULA_SCOPE_VARIABLES.room,
|
|
1574
1663
|
...FORMULA_SCOPE_VARIABLES.object,
|
|
1575
1664
|
FORMULA_SURFACE_AREA_VARIABLE,
|
|
1576
|
-
FORMULA_PUTTY_REQUIRED_VARIABLE
|
|
1665
|
+
FORMULA_PUTTY_REQUIRED_VARIABLE,
|
|
1666
|
+
FORMULA_PUTTY_AREA_VARIABLE
|
|
1577
1667
|
]
|
|
1578
1668
|
};
|
|
1579
1669
|
const ALL_VARIABLE_NAMES = new Set([...FORMULA_VARIABLES.CATEGORY, ...FORMULA_VARIABLES.SURFACE].map((variable) => variable.name));
|
|
@@ -1788,4 +1878,4 @@ function validateFormula(input) {
|
|
|
1788
1878
|
};
|
|
1789
1879
|
}
|
|
1790
1880
|
//#endregion
|
|
1791
|
-
export { COEFFICIENT_NAMESPACE, CentsSchema, ConfigurationSchema, CurrencySchema, EstimateLineSchema, EstimateMessageSchema, EstimateSchema, EstimateTotalsSchema, EstimateWarningSchema, ExchangeRateSchema, FORMULA_FUNCTIONS, FORMULA_FUNCTION_TABLE, FORMULA_LIMITS, FORMULA_PUTTY_REQUIRED_VARIABLE, FORMULA_QUANTITY_VARIABLE, FORMULA_SCOPE_VARIABLES, FORMULA_SURFACE_AREA_VARIABLE, FORMULA_VARIABLES, FinishLevelSchema, LineComponentKindSchema, LineComponentSchema, LineStatusSchema, LocalizedTextSchema, OptionCodeSchema, OptionKindSchema, OptionSelectionSchema, OptionUnitSchema, PricingCategorySchema, PricingDataSchema, PricingLimitsSchema, PricingMaterialTypeSchema, PricingOptionSchema, PricingPackageItemSchema, PricingProductSchema, PricingRoomTypeSchema, PricingRuleSchema, ProductUnitSchema, RangeSchema, RateSourceSchema, RoomConfigurationSchema, RoomSelectionSchema, RoomTypeCodeSchema, RuleLevelSchema, RulePreviewSchema, RuleSampleSchema, ScopeSchema, SectionSchema, SectionTotalSchema, SurfaceKindSchema, checkLimits, convertCentsToKopecks, estimate, evaluate, findOptionPriceSourceViolation, levenshtein, objectVariables, parse, previewRule, productVariables, productVariablesOf, puttyRequiredOn, roomVariables, suggestNearest, toCents, totalAreaOf, validateFormula };
|
|
1881
|
+
export { COEFFICIENT_NAMESPACE, CentsSchema, ConfigurationSchema, CurrencySchema, EstimateLineSchema, EstimateMessageSchema, EstimateSchema, EstimateTotalsSchema, EstimateWarningSchema, ExchangeRateSchema, FORMULA_FUNCTIONS, FORMULA_FUNCTION_TABLE, FORMULA_LIMITS, FORMULA_PUTTY_AREA_VARIABLE, FORMULA_PUTTY_REQUIRED_VARIABLE, FORMULA_QUANTITY_VARIABLE, FORMULA_SCOPE_VARIABLES, FORMULA_SURFACE_AREA_VARIABLE, FORMULA_VARIABLES, FinishLevelSchema, LineComponentKindSchema, LineComponentSchema, LineStatusSchema, LocalizedTextSchema, OptionCodeSchema, OptionKindSchema, OptionSelectionSchema, OptionUnitSchema, PricingCategorySchema, PricingDataSchema, PricingLimitsSchema, PricingMaterialTypeSchema, PricingOptionSchema, PricingPackageItemSchema, PricingProductSchema, PricingRoomTypeSchema, PricingRuleSchema, ProductUnitSchema, RangeSchema, RateSourceSchema, RoomConfigurationSchema, RoomSelectionSchema, RoomTypeCodeSchema, RuleLevelSchema, RulePreviewSchema, RuleSampleSchema, ScopeSchema, SectionSchema, SectionTotalSchema, SurfaceKindSchema, areaShareError, areaShareOf, checkLimits, convertCentsToKopecks, estimate, evaluate, findOptionPriceSourceViolation, isShareSurface, levenshtein, objectVariables, parse, previewRule, productVariables, productVariablesOf, puttyRequiredOn, puttyShareOn, roomVariables, suggestNearest, surfaceAreaOf, surfaceShares, toCents, totalAreaOf, validateFormula };
|