@roomwise/calc-engine 0.5.0 → 0.7.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 +5 -3
- package/dist/index.cjs +159 -22
- package/dist/index.d.cts +40 -6
- package/dist/index.d.ts +40 -6
- package/dist/index.js +150 -23
- 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, and are skipped in `PARTIAL`. Options are priced by unit (`PIECE`, `ROOM_SQM`, `ROOM`, `PROJECT`). An option with `priceCoefficientKeys`
|
|
50
|
+
- Engineering and additional: package items marked `includedInBase` become `INCLUDED` lines with a zero amount, and are skipped in `PARTIAL`. 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
|
|
|
@@ -197,7 +197,7 @@ Every schema is a zod schema with an inferred type of the same name without the
|
|
|
197
197
|
| `totalArea` variable | `totalAreaSqm` | `roomsFloorArea` |
|
|
198
198
|
| Area warnings | `ROOMS_AREA_EXCEEDS_TOTAL`, `ROOMS_AREA_BELOW_TOTAL` | none |
|
|
199
199
|
|
|
200
|
-
The base cost has no per-area rate table. It is built from rules whose formulas use the volumes of the rooms (`roomsFloorArea`, `roomsWallArea`, `roomsPerimeter`, `wetRoomsCount`) together with `finishLevel`, `scope` and coefficients.
|
|
200
|
+
The base cost has no per-area rate table. It is built from rules whose formulas use the volumes of the rooms (`roomsFloorArea`, `roomsWallArea`, `roomsPuttyWallArea`, `roomsPerimeter`, `wetRoomsCount`) together with `finishLevel`, `scope` and coefficients.
|
|
201
201
|
|
|
202
202
|
### Line components
|
|
203
203
|
|
|
@@ -241,18 +241,20 @@ roomType == 'KITCHEN' ? ceil(floorArea / 2) : 0
|
|
|
241
241
|
| object | `totalArea`, `balconyArea` | number; `totalArea` is `roomsFloorArea` in `PARTIAL` |
|
|
242
242
|
| object | `scope` | `FULL`, `PARTIAL` |
|
|
243
243
|
| object | `roomsFloorArea`, `roomsWallArea`, `roomsPerimeter` | number: sums over all rooms of `floorArea`, `wallArea` and `perimeter` |
|
|
244
|
+
| object | `roomsPuttyWallArea` | number: sum of `wallArea` over rooms whose wall selections need putty (rooms without wall selections count) |
|
|
244
245
|
| object | `wetRoomsCount` | number (bathrooms, kitchens and kitchen-living rooms) |
|
|
245
246
|
| object | `balconyTiles` | boolean |
|
|
246
247
|
| object | `finishLevel` | `ROUGH`, `PRE_FINISH`, `LIVED_IN` |
|
|
247
248
|
| object | `roomsCount`, `bathroomsCount` | number (living rooms, bedrooms and kitchen-living rooms count as rooms) |
|
|
248
249
|
| `SURFACE` rules only | `surfaceArea` | number: the area of the surface being prepared |
|
|
250
|
+
| `SURFACE` rules only | `puttyRequired` | boolean: `true` when no category is selected on the surface or at least one selected category has `requiresPutty` (default `true`), taken from the selections, not from line status; booleans do not coerce, write `puttyRequired ? a : 0` |
|
|
249
251
|
| cost and labor formulas of `CATEGORY` and `MATERIAL_TYPE` rules | `quantity` | the result of the quantity formula |
|
|
250
252
|
|
|
251
253
|
Availability by rule level:
|
|
252
254
|
|
|
253
255
|
- `OBJECT`: object variables only. `quantity` is not available.
|
|
254
256
|
- `CATEGORY` and `MATERIAL_TYPE`: room, product and object variables. `quantity` is available in cost and labor formulas, not in the quantity formula. `laborFormula` sees the same variables as `costFormula`.
|
|
255
|
-
- `SURFACE`: room and object variables plus `surfaceArea`. `quantity` and the product variables are not available.
|
|
257
|
+
- `SURFACE`: room and object variables plus `surfaceArea` and `puttyRequired`. `quantity` and the product variables are not available.
|
|
256
258
|
|
|
257
259
|
Coefficients are read as `coef.<key>`; keys come from the pricing data. The cost and labor formulas receive `quantity` unrounded, while the line shows it rounded to 3 decimals.
|
|
258
260
|
|
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,9 @@ function toCents(dollars) {
|
|
|
52
52
|
}
|
|
53
53
|
//#endregion
|
|
54
54
|
//#region src/variables/room.ts
|
|
55
|
+
function surfaceAreaOf(variables, surface) {
|
|
56
|
+
return surface === "WALLS" ? variables.wallArea : variables.floorArea;
|
|
57
|
+
}
|
|
55
58
|
function roomVariables(room) {
|
|
56
59
|
const perimeter = 2 * (room.lengthM + room.widthM);
|
|
57
60
|
return {
|
|
@@ -66,6 +69,63 @@ function roomVariables(room) {
|
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
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
|
|
69
129
|
//#region src/variables/object.ts
|
|
70
130
|
const COUNTED_AS_ROOMS = [
|
|
71
131
|
"LIVING_ROOM",
|
|
@@ -80,7 +140,7 @@ const WET_ROOMS = [
|
|
|
80
140
|
function totalAreaOf(scope, totalAreaSqm, roomsFloorArea) {
|
|
81
141
|
return scope === "PARTIAL" || totalAreaSqm === null ? roomsFloorArea : totalAreaSqm;
|
|
82
142
|
}
|
|
83
|
-
function objectVariables(configuration) {
|
|
143
|
+
function objectVariables(configuration, categories) {
|
|
84
144
|
const rooms = configuration.rooms.map(roomVariables);
|
|
85
145
|
const sum = (pick) => rooms.reduce((total, room) => total + pick(room), 0);
|
|
86
146
|
const roomsFloorArea = sum((room) => room.floorArea);
|
|
@@ -94,6 +154,7 @@ function objectVariables(configuration) {
|
|
|
94
154
|
bathroomsCount: configuration.rooms.filter((room) => room.roomTypeCode === "BATHROOM").length,
|
|
95
155
|
roomsFloorArea,
|
|
96
156
|
roomsWallArea: sum((room) => room.wallArea),
|
|
157
|
+
roomsPuttyWallArea: configuration.rooms.reduce((total, room) => total + roomVariables(room).wallArea * puttyShareOn(room, "WALLS", categories), 0),
|
|
97
158
|
roomsPerimeter: sum((room) => room.perimeter),
|
|
98
159
|
wetRoomsCount: rooms.filter((room) => WET_ROOMS.includes(room.roomType)).length
|
|
99
160
|
};
|
|
@@ -726,23 +787,25 @@ function applySurfacePrep(data, room, lines, objectScope) {
|
|
|
726
787
|
...roomVariables(room),
|
|
727
788
|
...objectScope
|
|
728
789
|
};
|
|
729
|
-
const areas = {
|
|
730
|
-
FLOOR: variables.floorArea,
|
|
731
|
-
CEILING: variables.floorArea,
|
|
732
|
-
WALLS: variables.wallArea
|
|
733
|
-
};
|
|
734
790
|
const surfaces = /* @__PURE__ */ new Map();
|
|
735
791
|
lines.forEach((line, index) => {
|
|
736
|
-
const
|
|
737
|
-
if (line.status === "OK" &&
|
|
792
|
+
const category = data.categories.find((item) => item.id === line.categoryId);
|
|
793
|
+
if (line.status === "OK" && category !== void 0 && category.surface !== "NONE") {
|
|
794
|
+
const surface = category.surface;
|
|
795
|
+
surfaces.set(surface, [...surfaces.get(surface) ?? [], index]);
|
|
796
|
+
}
|
|
738
797
|
});
|
|
739
798
|
for (const [surface, indexes] of surfaces) {
|
|
740
799
|
const rule = surfaceRuleOf(data, surface);
|
|
741
800
|
if (rule === void 0) continue;
|
|
801
|
+
const surfaceArea = surfaceAreaOf(variables, surface);
|
|
802
|
+
const puttyShare = puttyShareOn(room, surface, data.categories);
|
|
742
803
|
const outcome = runNumericFormula(rule.costFormula, {
|
|
743
804
|
variables: {
|
|
744
805
|
...variables,
|
|
745
|
-
surfaceArea
|
|
806
|
+
surfaceArea,
|
|
807
|
+
puttyRequired: puttyShare > 0,
|
|
808
|
+
puttyArea: surfaceArea * puttyShare
|
|
746
809
|
},
|
|
747
810
|
coefficients: data.coefficients
|
|
748
811
|
});
|
|
@@ -775,7 +838,9 @@ function selectRule(data, categoryId, materialTypeId) {
|
|
|
775
838
|
const rules = data.rules.filter(isMaterialRule);
|
|
776
839
|
return rules.find((rule) => rule.level === "MATERIAL_TYPE" && rule.categoryId === categoryId && rule.materialTypeId === materialTypeId) ?? rules.find((rule) => rule.level === "CATEGORY" && rule.categoryId === categoryId);
|
|
777
840
|
}
|
|
778
|
-
function materialLine(configuration, data, room,
|
|
841
|
+
function materialLine(configuration, data, room, selection, objectScope) {
|
|
842
|
+
const { categoryId } = selection;
|
|
843
|
+
const productId = selection.productId;
|
|
779
844
|
const init = {
|
|
780
845
|
id: lineId("MATERIALS", room.clientId, categoryId),
|
|
781
846
|
section: "MATERIALS",
|
|
@@ -783,6 +848,8 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
783
848
|
categoryId,
|
|
784
849
|
productId
|
|
785
850
|
};
|
|
851
|
+
const shareError = areaShareError(room, selection, data.categories);
|
|
852
|
+
if (shareError !== null) return failedLine(init, "ERROR", shareError);
|
|
786
853
|
const category = data.categories.find((item) => item.id === categoryId);
|
|
787
854
|
const product = data.products.find((item) => item.id === productId && item.categoryId === categoryId);
|
|
788
855
|
if (product === void 0 || category === void 0 || !product.available) return failedLine(init, "UNAVAILABLE", {
|
|
@@ -804,8 +871,12 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
804
871
|
materialTypeId: product.materialTypeId
|
|
805
872
|
}
|
|
806
873
|
});
|
|
874
|
+
const base = roomVariables(room);
|
|
875
|
+
const share = areaShareOf(selection);
|
|
807
876
|
const variables = {
|
|
808
|
-
...
|
|
877
|
+
...base,
|
|
878
|
+
wallArea: category.surface === "WALLS" ? base.wallArea * share : base.wallArea,
|
|
879
|
+
floorArea: category.surface === "FLOOR" ? base.floorArea * share : base.floorArea,
|
|
809
880
|
...productVariables(product, category),
|
|
810
881
|
...objectScope
|
|
811
882
|
};
|
|
@@ -875,7 +946,7 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
875
946
|
}
|
|
876
947
|
function materialLines(configuration, data, objectScope) {
|
|
877
948
|
return configuration.rooms.flatMap((room) => {
|
|
878
|
-
const lines = room.selections.flatMap((
|
|
949
|
+
const lines = room.selections.flatMap((selection) => selection.productId === null ? [] : [materialLine(configuration, data, room, selection, objectScope)]);
|
|
879
950
|
return configuration.scope === "PARTIAL" ? applySurfacePrep(data, room, lines, objectScope) : lines;
|
|
880
951
|
});
|
|
881
952
|
}
|
|
@@ -973,7 +1044,8 @@ const RangeSchema = zod.z.object({
|
|
|
973
1044
|
//#region src/schemas/configuration.ts
|
|
974
1045
|
const RoomSelectionSchema = zod.z.object({
|
|
975
1046
|
categoryId: zod.z.string(),
|
|
976
|
-
productId: zod.z.string().nullable()
|
|
1047
|
+
productId: zod.z.string().nullable(),
|
|
1048
|
+
areaShare: zod.z.int().min(1).max(100).optional()
|
|
977
1049
|
});
|
|
978
1050
|
const RoomConfigurationSchema = zod.z.object({
|
|
979
1051
|
clientId: zod.z.string(),
|
|
@@ -1077,7 +1149,8 @@ const PricingRoomTypeSchema = zod.z.object({
|
|
|
1077
1149
|
const PricingCategorySchema = zod.z.object({
|
|
1078
1150
|
id: zod.z.string(),
|
|
1079
1151
|
wastePercent: zod.z.number().min(0),
|
|
1080
|
-
surface: SurfaceKindSchema
|
|
1152
|
+
surface: SurfaceKindSchema,
|
|
1153
|
+
requiresPutty: zod.z.boolean().default(true)
|
|
1081
1154
|
});
|
|
1082
1155
|
const PricingMaterialTypeSchema = zod.z.object({
|
|
1083
1156
|
id: zod.z.string(),
|
|
@@ -1099,11 +1172,11 @@ const PricingPackageItemSchema = zod.z.object({
|
|
|
1099
1172
|
priceCents: CentsSchema.nullable(),
|
|
1100
1173
|
unit: OptionUnitSchema.nullable()
|
|
1101
1174
|
});
|
|
1102
|
-
const
|
|
1175
|
+
const PricingOptionObjectSchema = zod.z.object({
|
|
1103
1176
|
id: zod.z.string(),
|
|
1104
1177
|
code: OptionCodeSchema.nullable(),
|
|
1105
1178
|
kind: OptionKindSchema,
|
|
1106
|
-
priceCents: CentsSchema,
|
|
1179
|
+
priceCents: CentsSchema.nullable(),
|
|
1107
1180
|
unit: OptionUnitSchema,
|
|
1108
1181
|
roomTypeCodes: zod.z.array(zod.z.string()),
|
|
1109
1182
|
priceCoefficientKeys: zod.z.array(zod.z.string()),
|
|
@@ -1112,6 +1185,26 @@ const PricingOptionSchema = zod.z.object({
|
|
|
1112
1185
|
maxQuantity: zod.z.int().nullable(),
|
|
1113
1186
|
available: zod.z.boolean()
|
|
1114
1187
|
});
|
|
1188
|
+
function findOptionPriceSourceViolation(priceCents, priceCoefficientKeys) {
|
|
1189
|
+
const hasKeys = priceCoefficientKeys.length > 0;
|
|
1190
|
+
if (priceCents !== null && hasKeys) return {
|
|
1191
|
+
path: "priceCents",
|
|
1192
|
+
message: "priceCents must be null when priceCoefficientKeys is set"
|
|
1193
|
+
};
|
|
1194
|
+
if (priceCents === null && !hasKeys) return {
|
|
1195
|
+
path: "priceCoefficientKeys",
|
|
1196
|
+
message: "priceCoefficientKeys is required when priceCents is null"
|
|
1197
|
+
};
|
|
1198
|
+
return null;
|
|
1199
|
+
}
|
|
1200
|
+
const PricingOptionSchema = PricingOptionObjectSchema.superRefine((option, context) => {
|
|
1201
|
+
const violation = findOptionPriceSourceViolation(option.priceCents, option.priceCoefficientKeys);
|
|
1202
|
+
if (violation !== null) context.addIssue({
|
|
1203
|
+
code: "custom",
|
|
1204
|
+
path: [violation.path],
|
|
1205
|
+
message: violation.message
|
|
1206
|
+
});
|
|
1207
|
+
});
|
|
1115
1208
|
const PricingDataSchema = zod.z.object({
|
|
1116
1209
|
version: zod.z.string().regex(/^[0-9a-f]{16}$/),
|
|
1117
1210
|
exchangeRate: ExchangeRateSchema.nullable(),
|
|
@@ -1191,6 +1284,8 @@ const RuleSampleSchema = zod.z.object({
|
|
|
1191
1284
|
wastePercent: zod.z.number().min(0)
|
|
1192
1285
|
}).optional(),
|
|
1193
1286
|
surfaceArea: zod.z.number().min(0).optional(),
|
|
1287
|
+
puttyRequired: zod.z.boolean().optional(),
|
|
1288
|
+
puttyArea: zod.z.number().min(0).optional(),
|
|
1194
1289
|
object: zod.z.object({
|
|
1195
1290
|
scope: ScopeSchema,
|
|
1196
1291
|
totalAreaSqm: zod.z.number().positive().nullable(),
|
|
@@ -1201,6 +1296,7 @@ const RuleSampleSchema = zod.z.object({
|
|
|
1201
1296
|
bathroomsCount: zod.z.int().min(0),
|
|
1202
1297
|
roomsFloorArea: zod.z.number().min(0),
|
|
1203
1298
|
roomsWallArea: zod.z.number().min(0),
|
|
1299
|
+
roomsPuttyWallArea: zod.z.number().min(0).optional(),
|
|
1204
1300
|
roomsPerimeter: zod.z.number().min(0),
|
|
1205
1301
|
wetRoomsCount: zod.z.int().min(0)
|
|
1206
1302
|
})
|
|
@@ -1268,10 +1364,32 @@ function roomsAreaWarnings(configuration, limits) {
|
|
|
1268
1364
|
params: { missingSqm }
|
|
1269
1365
|
}] : [];
|
|
1270
1366
|
}
|
|
1271
|
-
|
|
1367
|
+
const SHARE_SURFACES = ["WALLS", "FLOOR"];
|
|
1368
|
+
function areaShareWarnings(configuration, categories) {
|
|
1369
|
+
return configuration.rooms.flatMap((room) => {
|
|
1370
|
+
const variables = roomVariables(room);
|
|
1371
|
+
return SHARE_SURFACES.flatMap((surface) => {
|
|
1372
|
+
const shares = surfaceShares(room, surface, categories);
|
|
1373
|
+
if (!shares.shared || !shares.complete || shares.totalPercent >= 100) return [];
|
|
1374
|
+
const percent = 100 - shares.totalPercent;
|
|
1375
|
+
const area = surfaceAreaOf(variables, surface);
|
|
1376
|
+
return [{
|
|
1377
|
+
code: "AREA_SHARE_UNALLOCATED",
|
|
1378
|
+
params: {
|
|
1379
|
+
roomClientId: room.clientId,
|
|
1380
|
+
surface,
|
|
1381
|
+
percent,
|
|
1382
|
+
sqm: roundArea(area * percent / 100)
|
|
1383
|
+
}
|
|
1384
|
+
}];
|
|
1385
|
+
});
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
function estimateWarnings(lines, configuration, rate, data) {
|
|
1272
1389
|
return [
|
|
1273
1390
|
...collectWarnings(lines),
|
|
1274
|
-
...roomsAreaWarnings(configuration, limits),
|
|
1391
|
+
...roomsAreaWarnings(configuration, data.limits),
|
|
1392
|
+
...areaShareWarnings(configuration, data.categories),
|
|
1275
1393
|
...configuration.currency === "UAH" && rate === null ? [{ code: "EXCHANGE_RATE_UNAVAILABLE" }] : []
|
|
1276
1394
|
];
|
|
1277
1395
|
}
|
|
@@ -1285,7 +1403,7 @@ function withKopecks(line, rate) {
|
|
|
1285
1403
|
}
|
|
1286
1404
|
function estimate(configuration, data) {
|
|
1287
1405
|
const rate = data.exchangeRate?.rate ?? null;
|
|
1288
|
-
const variables = objectVariables(configuration);
|
|
1406
|
+
const variables = objectVariables(configuration, data.categories);
|
|
1289
1407
|
const lines = [
|
|
1290
1408
|
...objectRuleLines(configuration, data, "BASE", variables),
|
|
1291
1409
|
...objectRuleLines(configuration, data, "OBJECT", variables),
|
|
@@ -1299,7 +1417,7 @@ function estimate(configuration, data) {
|
|
|
1299
1417
|
exchangeRate: data.exchangeRate,
|
|
1300
1418
|
lines,
|
|
1301
1419
|
totals: computeTotals(lines, rate !== null),
|
|
1302
|
-
warnings: estimateWarnings(lines, configuration, rate, data
|
|
1420
|
+
warnings: estimateWarnings(lines, configuration, rate, data),
|
|
1303
1421
|
incomplete: isIncomplete(lines)
|
|
1304
1422
|
};
|
|
1305
1423
|
}
|
|
@@ -1381,6 +1499,7 @@ function objectScope({ object }) {
|
|
|
1381
1499
|
bathroomsCount: object.bathroomsCount,
|
|
1382
1500
|
roomsFloorArea: object.roomsFloorArea,
|
|
1383
1501
|
roomsWallArea: object.roomsWallArea,
|
|
1502
|
+
roomsPuttyWallArea: object.roomsPuttyWallArea ?? object.roomsWallArea,
|
|
1384
1503
|
roomsPerimeter: object.roomsPerimeter,
|
|
1385
1504
|
wetRoomsCount: object.wetRoomsCount
|
|
1386
1505
|
};
|
|
@@ -1393,10 +1512,13 @@ function scopeOf(level, sample) {
|
|
|
1393
1512
|
code: "SAMPLE_INCOMPLETE",
|
|
1394
1513
|
params: { missing: room === void 0 ? "room" : "surfaceArea" }
|
|
1395
1514
|
} };
|
|
1515
|
+
const puttyRequired = sample.puttyRequired ?? true;
|
|
1396
1516
|
return { scope: {
|
|
1397
1517
|
...roomVariables(room),
|
|
1398
1518
|
...objectScope(sample),
|
|
1399
|
-
surfaceArea
|
|
1519
|
+
surfaceArea,
|
|
1520
|
+
puttyRequired,
|
|
1521
|
+
puttyArea: sample.puttyArea ?? (puttyRequired ? surfaceArea : 0)
|
|
1400
1522
|
} };
|
|
1401
1523
|
}
|
|
1402
1524
|
if (room === void 0 || product === void 0) return { error: {
|
|
@@ -1538,12 +1660,15 @@ const FORMULA_SCOPE_VARIABLES = {
|
|
|
1538
1660
|
enumeration("scope", ScopeSchema.options),
|
|
1539
1661
|
number("roomsFloorArea"),
|
|
1540
1662
|
number("roomsWallArea"),
|
|
1663
|
+
number("roomsPuttyWallArea"),
|
|
1541
1664
|
number("roomsPerimeter"),
|
|
1542
1665
|
number("wetRoomsCount")
|
|
1543
1666
|
]
|
|
1544
1667
|
};
|
|
1545
1668
|
const FORMULA_QUANTITY_VARIABLE = number("quantity");
|
|
1546
1669
|
const FORMULA_SURFACE_AREA_VARIABLE = number("surfaceArea");
|
|
1670
|
+
const FORMULA_PUTTY_AREA_VARIABLE = number("puttyArea");
|
|
1671
|
+
const FORMULA_PUTTY_REQUIRED_VARIABLE = boolean("puttyRequired");
|
|
1547
1672
|
const FORMULA_VARIABLES = {
|
|
1548
1673
|
OBJECT: FORMULA_SCOPE_VARIABLES.object,
|
|
1549
1674
|
CATEGORY: [
|
|
@@ -1559,7 +1684,9 @@ const FORMULA_VARIABLES = {
|
|
|
1559
1684
|
SURFACE: [
|
|
1560
1685
|
...FORMULA_SCOPE_VARIABLES.room,
|
|
1561
1686
|
...FORMULA_SCOPE_VARIABLES.object,
|
|
1562
|
-
FORMULA_SURFACE_AREA_VARIABLE
|
|
1687
|
+
FORMULA_SURFACE_AREA_VARIABLE,
|
|
1688
|
+
FORMULA_PUTTY_REQUIRED_VARIABLE,
|
|
1689
|
+
FORMULA_PUTTY_AREA_VARIABLE
|
|
1563
1690
|
]
|
|
1564
1691
|
};
|
|
1565
1692
|
const ALL_VARIABLE_NAMES = new Set([...FORMULA_VARIABLES.CATEGORY, ...FORMULA_VARIABLES.SURFACE].map((variable) => variable.name));
|
|
@@ -1787,6 +1914,8 @@ exports.ExchangeRateSchema = ExchangeRateSchema;
|
|
|
1787
1914
|
exports.FORMULA_FUNCTIONS = FORMULA_FUNCTIONS;
|
|
1788
1915
|
exports.FORMULA_FUNCTION_TABLE = FORMULA_FUNCTION_TABLE;
|
|
1789
1916
|
exports.FORMULA_LIMITS = FORMULA_LIMITS;
|
|
1917
|
+
exports.FORMULA_PUTTY_AREA_VARIABLE = FORMULA_PUTTY_AREA_VARIABLE;
|
|
1918
|
+
exports.FORMULA_PUTTY_REQUIRED_VARIABLE = FORMULA_PUTTY_REQUIRED_VARIABLE;
|
|
1790
1919
|
exports.FORMULA_QUANTITY_VARIABLE = FORMULA_QUANTITY_VARIABLE;
|
|
1791
1920
|
exports.FORMULA_SCOPE_VARIABLES = FORMULA_SCOPE_VARIABLES;
|
|
1792
1921
|
exports.FORMULA_SURFACE_AREA_VARIABLE = FORMULA_SURFACE_AREA_VARIABLE;
|
|
@@ -1822,18 +1951,26 @@ exports.ScopeSchema = ScopeSchema;
|
|
|
1822
1951
|
exports.SectionSchema = SectionSchema;
|
|
1823
1952
|
exports.SectionTotalSchema = SectionTotalSchema;
|
|
1824
1953
|
exports.SurfaceKindSchema = SurfaceKindSchema;
|
|
1954
|
+
exports.areaShareError = areaShareError;
|
|
1955
|
+
exports.areaShareOf = areaShareOf;
|
|
1825
1956
|
exports.checkLimits = checkLimits;
|
|
1826
1957
|
exports.convertCentsToKopecks = convertCentsToKopecks;
|
|
1827
1958
|
exports.estimate = estimate;
|
|
1828
1959
|
exports.evaluate = evaluate;
|
|
1960
|
+
exports.findOptionPriceSourceViolation = findOptionPriceSourceViolation;
|
|
1961
|
+
exports.isShareSurface = isShareSurface;
|
|
1829
1962
|
exports.levenshtein = levenshtein;
|
|
1830
1963
|
exports.objectVariables = objectVariables;
|
|
1831
1964
|
exports.parse = parse;
|
|
1832
1965
|
exports.previewRule = previewRule;
|
|
1833
1966
|
exports.productVariables = productVariables;
|
|
1834
1967
|
exports.productVariablesOf = productVariablesOf;
|
|
1968
|
+
exports.puttyRequiredOn = puttyRequiredOn;
|
|
1969
|
+
exports.puttyShareOn = puttyShareOn;
|
|
1835
1970
|
exports.roomVariables = roomVariables;
|
|
1836
1971
|
exports.suggestNearest = suggestNearest;
|
|
1972
|
+
exports.surfaceAreaOf = surfaceAreaOf;
|
|
1973
|
+
exports.surfaceShares = surfaceShares;
|
|
1837
1974
|
exports.toCents = toCents;
|
|
1838
1975
|
exports.totalAreaOf = totalAreaOf;
|
|
1839
1976
|
exports.validateFormula = validateFormula;
|
package/dist/index.d.cts
CHANGED
|
@@ -29,10 +29,10 @@ export declare const RateSourceSchema: z.ZodEnum<{
|
|
|
29
29
|
}>;
|
|
30
30
|
export type RateSource = z.infer<typeof RateSourceSchema>;
|
|
31
31
|
export declare const SurfaceKindSchema: z.ZodEnum<{
|
|
32
|
+
NONE: "NONE";
|
|
32
33
|
FLOOR: "FLOOR";
|
|
33
34
|
WALLS: "WALLS";
|
|
34
35
|
CEILING: "CEILING";
|
|
35
|
-
NONE: "NONE";
|
|
36
36
|
}>;
|
|
37
37
|
export type SurfaceKind = z.infer<typeof SurfaceKindSchema>;
|
|
38
38
|
export declare const ProductUnitSchema: z.ZodEnum<{
|
|
@@ -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<{
|
|
@@ -382,6 +385,8 @@ export declare const RuleSampleSchema: z.ZodObject<{
|
|
|
382
385
|
wastePercent: z.ZodNumber;
|
|
383
386
|
}, z.core.$strip>>;
|
|
384
387
|
surfaceArea: z.ZodOptional<z.ZodNumber>;
|
|
388
|
+
puttyRequired: z.ZodOptional<z.ZodBoolean>;
|
|
389
|
+
puttyArea: z.ZodOptional<z.ZodNumber>;
|
|
385
390
|
object: z.ZodObject<{
|
|
386
391
|
scope: z.ZodEnum<{
|
|
387
392
|
FULL: "FULL";
|
|
@@ -399,6 +404,7 @@ export declare const RuleSampleSchema: z.ZodObject<{
|
|
|
399
404
|
bathroomsCount: z.ZodInt;
|
|
400
405
|
roomsFloorArea: z.ZodNumber;
|
|
401
406
|
roomsWallArea: z.ZodNumber;
|
|
407
|
+
roomsPuttyWallArea: z.ZodOptional<z.ZodNumber>;
|
|
402
408
|
roomsPerimeter: z.ZodNumber;
|
|
403
409
|
wetRoomsCount: z.ZodInt;
|
|
404
410
|
}, z.core.$strip>;
|
|
@@ -482,11 +488,12 @@ export declare const PricingCategorySchema: z.ZodObject<{
|
|
|
482
488
|
id: z.ZodString;
|
|
483
489
|
wastePercent: z.ZodNumber;
|
|
484
490
|
surface: z.ZodEnum<{
|
|
491
|
+
NONE: "NONE";
|
|
485
492
|
FLOOR: "FLOOR";
|
|
486
493
|
WALLS: "WALLS";
|
|
487
494
|
CEILING: "CEILING";
|
|
488
|
-
NONE: "NONE";
|
|
489
495
|
}>;
|
|
496
|
+
requiresPutty: z.ZodDefault<z.ZodBoolean>;
|
|
490
497
|
}, z.core.$strip>;
|
|
491
498
|
export type PricingCategory = z.infer<typeof PricingCategorySchema>;
|
|
492
499
|
export declare const PricingMaterialTypeSchema: z.ZodObject<{
|
|
@@ -521,6 +528,10 @@ export declare const PricingPackageItemSchema: z.ZodObject<{
|
|
|
521
528
|
}>>;
|
|
522
529
|
}, z.core.$strip>;
|
|
523
530
|
export type PricingPackageItem = z.infer<typeof PricingPackageItemSchema>;
|
|
531
|
+
export declare function findOptionPriceSourceViolation(priceCents: number | null, priceCoefficientKeys: readonly string[]): {
|
|
532
|
+
path: 'priceCents' | 'priceCoefficientKeys';
|
|
533
|
+
message: string;
|
|
534
|
+
} | null;
|
|
524
535
|
export declare const PricingOptionSchema: z.ZodObject<{
|
|
525
536
|
id: z.ZodString;
|
|
526
537
|
code: z.ZodNullable<z.ZodEnum<{
|
|
@@ -530,7 +541,7 @@ export declare const PricingOptionSchema: z.ZodObject<{
|
|
|
530
541
|
ENGINEERING: "ENGINEERING";
|
|
531
542
|
ADDITIONAL: "ADDITIONAL";
|
|
532
543
|
}>;
|
|
533
|
-
priceCents: z.ZodInt
|
|
544
|
+
priceCents: z.ZodNullable<z.ZodInt>;
|
|
534
545
|
unit: z.ZodEnum<{
|
|
535
546
|
PIECE: "PIECE";
|
|
536
547
|
ROOM_SQM: "ROOM_SQM";
|
|
@@ -611,11 +622,12 @@ export declare const PricingDataSchema: z.ZodObject<{
|
|
|
611
622
|
id: z.ZodString;
|
|
612
623
|
wastePercent: z.ZodNumber;
|
|
613
624
|
surface: z.ZodEnum<{
|
|
625
|
+
NONE: "NONE";
|
|
614
626
|
FLOOR: "FLOOR";
|
|
615
627
|
WALLS: "WALLS";
|
|
616
628
|
CEILING: "CEILING";
|
|
617
|
-
NONE: "NONE";
|
|
618
629
|
}>;
|
|
630
|
+
requiresPutty: z.ZodDefault<z.ZodBoolean>;
|
|
619
631
|
}, z.core.$strip>>;
|
|
620
632
|
materialTypes: z.ZodArray<z.ZodObject<{
|
|
621
633
|
id: z.ZodString;
|
|
@@ -655,7 +667,7 @@ export declare const PricingDataSchema: z.ZodObject<{
|
|
|
655
667
|
ENGINEERING: "ENGINEERING";
|
|
656
668
|
ADDITIONAL: "ADDITIONAL";
|
|
657
669
|
}>;
|
|
658
|
-
priceCents: z.ZodInt
|
|
670
|
+
priceCents: z.ZodNullable<z.ZodInt>;
|
|
659
671
|
unit: z.ZodEnum<{
|
|
660
672
|
PIECE: "PIECE";
|
|
661
673
|
ROOM_SQM: "ROOM_SQM";
|
|
@@ -812,6 +824,8 @@ export interface FormulaVariable {
|
|
|
812
824
|
export declare const FORMULA_SCOPE_VARIABLES: Record<FormulaScope, readonly FormulaVariable[]>;
|
|
813
825
|
export declare const FORMULA_QUANTITY_VARIABLE: FormulaVariable;
|
|
814
826
|
export declare const FORMULA_SURFACE_AREA_VARIABLE: FormulaVariable;
|
|
827
|
+
export declare const FORMULA_PUTTY_AREA_VARIABLE: FormulaVariable;
|
|
828
|
+
export declare const FORMULA_PUTTY_REQUIRED_VARIABLE: FormulaVariable;
|
|
815
829
|
export declare const FORMULA_VARIABLES: Record<RuleLevel, readonly FormulaVariable[]>;
|
|
816
830
|
export interface ValidateFormulaInput {
|
|
817
831
|
readonly level: RuleLevel;
|
|
@@ -843,11 +857,12 @@ export interface ObjectVariables {
|
|
|
843
857
|
readonly bathroomsCount: number;
|
|
844
858
|
readonly roomsFloorArea: number;
|
|
845
859
|
readonly roomsWallArea: number;
|
|
860
|
+
readonly roomsPuttyWallArea: number;
|
|
846
861
|
readonly roomsPerimeter: number;
|
|
847
862
|
readonly wetRoomsCount: number;
|
|
848
863
|
}
|
|
849
864
|
export declare function totalAreaOf(scope: Scope, totalAreaSqm: number | null, roomsFloorArea: number): number;
|
|
850
|
-
export declare function objectVariables(configuration: Configuration): ObjectVariables;
|
|
865
|
+
export declare function objectVariables(configuration: Configuration, categories: readonly PricingCategory[]): ObjectVariables;
|
|
851
866
|
//#endregion
|
|
852
867
|
//#region src/variables/product.d.ts
|
|
853
868
|
export interface ProductVariables {
|
|
@@ -858,6 +873,9 @@ export interface ProductVariables {
|
|
|
858
873
|
export declare function productVariablesOf(product: Pick<PricingProduct, 'priceCents' | 'unit'>, wastePercent: number): ProductVariables;
|
|
859
874
|
export declare function productVariables(product: PricingProduct, category: PricingCategory): ProductVariables;
|
|
860
875
|
//#endregion
|
|
876
|
+
//#region src/variables/putty.d.ts
|
|
877
|
+
export declare function puttyRequiredOn(room: RoomConfiguration, surface: PricingCategory['surface'], categories: readonly PricingCategory[]): boolean;
|
|
878
|
+
//#endregion
|
|
861
879
|
//#region src/variables/room.d.ts
|
|
862
880
|
export interface RoomVariables {
|
|
863
881
|
readonly length: number;
|
|
@@ -869,6 +887,22 @@ export interface RoomVariables {
|
|
|
869
887
|
readonly heatedFloor: boolean;
|
|
870
888
|
readonly roomType: RoomTypeCode;
|
|
871
889
|
}
|
|
890
|
+
export type MeasuredSurface = 'FLOOR' | 'WALLS' | 'CEILING';
|
|
891
|
+
export declare function surfaceAreaOf(variables: Pick<RoomVariables, 'floorArea' | 'wallArea'>, surface: MeasuredSurface): number;
|
|
872
892
|
export type RoomDimensions = Pick<RoomConfiguration, 'roomTypeCode' | 'lengthM' | 'widthM' | 'heightM' | 'heatedFloor'>;
|
|
873
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;
|
|
874
908
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -29,10 +29,10 @@ export declare const RateSourceSchema: z.ZodEnum<{
|
|
|
29
29
|
}>;
|
|
30
30
|
export type RateSource = z.infer<typeof RateSourceSchema>;
|
|
31
31
|
export declare const SurfaceKindSchema: z.ZodEnum<{
|
|
32
|
+
NONE: "NONE";
|
|
32
33
|
FLOOR: "FLOOR";
|
|
33
34
|
WALLS: "WALLS";
|
|
34
35
|
CEILING: "CEILING";
|
|
35
|
-
NONE: "NONE";
|
|
36
36
|
}>;
|
|
37
37
|
export type SurfaceKind = z.infer<typeof SurfaceKindSchema>;
|
|
38
38
|
export declare const ProductUnitSchema: z.ZodEnum<{
|
|
@@ -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<{
|
|
@@ -382,6 +385,8 @@ export declare const RuleSampleSchema: z.ZodObject<{
|
|
|
382
385
|
wastePercent: z.ZodNumber;
|
|
383
386
|
}, z.core.$strip>>;
|
|
384
387
|
surfaceArea: z.ZodOptional<z.ZodNumber>;
|
|
388
|
+
puttyRequired: z.ZodOptional<z.ZodBoolean>;
|
|
389
|
+
puttyArea: z.ZodOptional<z.ZodNumber>;
|
|
385
390
|
object: z.ZodObject<{
|
|
386
391
|
scope: z.ZodEnum<{
|
|
387
392
|
FULL: "FULL";
|
|
@@ -399,6 +404,7 @@ export declare const RuleSampleSchema: z.ZodObject<{
|
|
|
399
404
|
bathroomsCount: z.ZodInt;
|
|
400
405
|
roomsFloorArea: z.ZodNumber;
|
|
401
406
|
roomsWallArea: z.ZodNumber;
|
|
407
|
+
roomsPuttyWallArea: z.ZodOptional<z.ZodNumber>;
|
|
402
408
|
roomsPerimeter: z.ZodNumber;
|
|
403
409
|
wetRoomsCount: z.ZodInt;
|
|
404
410
|
}, z.core.$strip>;
|
|
@@ -482,11 +488,12 @@ export declare const PricingCategorySchema: z.ZodObject<{
|
|
|
482
488
|
id: z.ZodString;
|
|
483
489
|
wastePercent: z.ZodNumber;
|
|
484
490
|
surface: z.ZodEnum<{
|
|
491
|
+
NONE: "NONE";
|
|
485
492
|
FLOOR: "FLOOR";
|
|
486
493
|
WALLS: "WALLS";
|
|
487
494
|
CEILING: "CEILING";
|
|
488
|
-
NONE: "NONE";
|
|
489
495
|
}>;
|
|
496
|
+
requiresPutty: z.ZodDefault<z.ZodBoolean>;
|
|
490
497
|
}, z.core.$strip>;
|
|
491
498
|
export type PricingCategory = z.infer<typeof PricingCategorySchema>;
|
|
492
499
|
export declare const PricingMaterialTypeSchema: z.ZodObject<{
|
|
@@ -521,6 +528,10 @@ export declare const PricingPackageItemSchema: z.ZodObject<{
|
|
|
521
528
|
}>>;
|
|
522
529
|
}, z.core.$strip>;
|
|
523
530
|
export type PricingPackageItem = z.infer<typeof PricingPackageItemSchema>;
|
|
531
|
+
export declare function findOptionPriceSourceViolation(priceCents: number | null, priceCoefficientKeys: readonly string[]): {
|
|
532
|
+
path: 'priceCents' | 'priceCoefficientKeys';
|
|
533
|
+
message: string;
|
|
534
|
+
} | null;
|
|
524
535
|
export declare const PricingOptionSchema: z.ZodObject<{
|
|
525
536
|
id: z.ZodString;
|
|
526
537
|
code: z.ZodNullable<z.ZodEnum<{
|
|
@@ -530,7 +541,7 @@ export declare const PricingOptionSchema: z.ZodObject<{
|
|
|
530
541
|
ENGINEERING: "ENGINEERING";
|
|
531
542
|
ADDITIONAL: "ADDITIONAL";
|
|
532
543
|
}>;
|
|
533
|
-
priceCents: z.ZodInt
|
|
544
|
+
priceCents: z.ZodNullable<z.ZodInt>;
|
|
534
545
|
unit: z.ZodEnum<{
|
|
535
546
|
PIECE: "PIECE";
|
|
536
547
|
ROOM_SQM: "ROOM_SQM";
|
|
@@ -611,11 +622,12 @@ export declare const PricingDataSchema: z.ZodObject<{
|
|
|
611
622
|
id: z.ZodString;
|
|
612
623
|
wastePercent: z.ZodNumber;
|
|
613
624
|
surface: z.ZodEnum<{
|
|
625
|
+
NONE: "NONE";
|
|
614
626
|
FLOOR: "FLOOR";
|
|
615
627
|
WALLS: "WALLS";
|
|
616
628
|
CEILING: "CEILING";
|
|
617
|
-
NONE: "NONE";
|
|
618
629
|
}>;
|
|
630
|
+
requiresPutty: z.ZodDefault<z.ZodBoolean>;
|
|
619
631
|
}, z.core.$strip>>;
|
|
620
632
|
materialTypes: z.ZodArray<z.ZodObject<{
|
|
621
633
|
id: z.ZodString;
|
|
@@ -655,7 +667,7 @@ export declare const PricingDataSchema: z.ZodObject<{
|
|
|
655
667
|
ENGINEERING: "ENGINEERING";
|
|
656
668
|
ADDITIONAL: "ADDITIONAL";
|
|
657
669
|
}>;
|
|
658
|
-
priceCents: z.ZodInt
|
|
670
|
+
priceCents: z.ZodNullable<z.ZodInt>;
|
|
659
671
|
unit: z.ZodEnum<{
|
|
660
672
|
PIECE: "PIECE";
|
|
661
673
|
ROOM_SQM: "ROOM_SQM";
|
|
@@ -812,6 +824,8 @@ export interface FormulaVariable {
|
|
|
812
824
|
export declare const FORMULA_SCOPE_VARIABLES: Record<FormulaScope, readonly FormulaVariable[]>;
|
|
813
825
|
export declare const FORMULA_QUANTITY_VARIABLE: FormulaVariable;
|
|
814
826
|
export declare const FORMULA_SURFACE_AREA_VARIABLE: FormulaVariable;
|
|
827
|
+
export declare const FORMULA_PUTTY_AREA_VARIABLE: FormulaVariable;
|
|
828
|
+
export declare const FORMULA_PUTTY_REQUIRED_VARIABLE: FormulaVariable;
|
|
815
829
|
export declare const FORMULA_VARIABLES: Record<RuleLevel, readonly FormulaVariable[]>;
|
|
816
830
|
export interface ValidateFormulaInput {
|
|
817
831
|
readonly level: RuleLevel;
|
|
@@ -843,11 +857,12 @@ export interface ObjectVariables {
|
|
|
843
857
|
readonly bathroomsCount: number;
|
|
844
858
|
readonly roomsFloorArea: number;
|
|
845
859
|
readonly roomsWallArea: number;
|
|
860
|
+
readonly roomsPuttyWallArea: number;
|
|
846
861
|
readonly roomsPerimeter: number;
|
|
847
862
|
readonly wetRoomsCount: number;
|
|
848
863
|
}
|
|
849
864
|
export declare function totalAreaOf(scope: Scope, totalAreaSqm: number | null, roomsFloorArea: number): number;
|
|
850
|
-
export declare function objectVariables(configuration: Configuration): ObjectVariables;
|
|
865
|
+
export declare function objectVariables(configuration: Configuration, categories: readonly PricingCategory[]): ObjectVariables;
|
|
851
866
|
//#endregion
|
|
852
867
|
//#region src/variables/product.d.ts
|
|
853
868
|
export interface ProductVariables {
|
|
@@ -858,6 +873,9 @@ export interface ProductVariables {
|
|
|
858
873
|
export declare function productVariablesOf(product: Pick<PricingProduct, 'priceCents' | 'unit'>, wastePercent: number): ProductVariables;
|
|
859
874
|
export declare function productVariables(product: PricingProduct, category: PricingCategory): ProductVariables;
|
|
860
875
|
//#endregion
|
|
876
|
+
//#region src/variables/putty.d.ts
|
|
877
|
+
export declare function puttyRequiredOn(room: RoomConfiguration, surface: PricingCategory['surface'], categories: readonly PricingCategory[]): boolean;
|
|
878
|
+
//#endregion
|
|
861
879
|
//#region src/variables/room.d.ts
|
|
862
880
|
export interface RoomVariables {
|
|
863
881
|
readonly length: number;
|
|
@@ -869,6 +887,22 @@ export interface RoomVariables {
|
|
|
869
887
|
readonly heatedFloor: boolean;
|
|
870
888
|
readonly roomType: RoomTypeCode;
|
|
871
889
|
}
|
|
890
|
+
export type MeasuredSurface = 'FLOOR' | 'WALLS' | 'CEILING';
|
|
891
|
+
export declare function surfaceAreaOf(variables: Pick<RoomVariables, 'floorArea' | 'wallArea'>, surface: MeasuredSurface): number;
|
|
872
892
|
export type RoomDimensions = Pick<RoomConfiguration, 'roomTypeCode' | 'lengthM' | 'widthM' | 'heightM' | 'heatedFloor'>;
|
|
873
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;
|
|
874
908
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,9 @@ function toCents(dollars) {
|
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/variables/room.ts
|
|
30
|
+
function surfaceAreaOf(variables, surface) {
|
|
31
|
+
return surface === "WALLS" ? variables.wallArea : variables.floorArea;
|
|
32
|
+
}
|
|
30
33
|
function roomVariables(room) {
|
|
31
34
|
const perimeter = 2 * (room.lengthM + room.widthM);
|
|
32
35
|
return {
|
|
@@ -41,6 +44,63 @@ function roomVariables(room) {
|
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
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
|
|
44
104
|
//#region src/variables/object.ts
|
|
45
105
|
const COUNTED_AS_ROOMS = [
|
|
46
106
|
"LIVING_ROOM",
|
|
@@ -55,7 +115,7 @@ const WET_ROOMS = [
|
|
|
55
115
|
function totalAreaOf(scope, totalAreaSqm, roomsFloorArea) {
|
|
56
116
|
return scope === "PARTIAL" || totalAreaSqm === null ? roomsFloorArea : totalAreaSqm;
|
|
57
117
|
}
|
|
58
|
-
function objectVariables(configuration) {
|
|
118
|
+
function objectVariables(configuration, categories) {
|
|
59
119
|
const rooms = configuration.rooms.map(roomVariables);
|
|
60
120
|
const sum = (pick) => rooms.reduce((total, room) => total + pick(room), 0);
|
|
61
121
|
const roomsFloorArea = sum((room) => room.floorArea);
|
|
@@ -69,6 +129,7 @@ function objectVariables(configuration) {
|
|
|
69
129
|
bathroomsCount: configuration.rooms.filter((room) => room.roomTypeCode === "BATHROOM").length,
|
|
70
130
|
roomsFloorArea,
|
|
71
131
|
roomsWallArea: sum((room) => room.wallArea),
|
|
132
|
+
roomsPuttyWallArea: configuration.rooms.reduce((total, room) => total + roomVariables(room).wallArea * puttyShareOn(room, "WALLS", categories), 0),
|
|
72
133
|
roomsPerimeter: sum((room) => room.perimeter),
|
|
73
134
|
wetRoomsCount: rooms.filter((room) => WET_ROOMS.includes(room.roomType)).length
|
|
74
135
|
};
|
|
@@ -701,23 +762,25 @@ function applySurfacePrep(data, room, lines, objectScope) {
|
|
|
701
762
|
...roomVariables(room),
|
|
702
763
|
...objectScope
|
|
703
764
|
};
|
|
704
|
-
const areas = {
|
|
705
|
-
FLOOR: variables.floorArea,
|
|
706
|
-
CEILING: variables.floorArea,
|
|
707
|
-
WALLS: variables.wallArea
|
|
708
|
-
};
|
|
709
765
|
const surfaces = /* @__PURE__ */ new Map();
|
|
710
766
|
lines.forEach((line, index) => {
|
|
711
|
-
const
|
|
712
|
-
if (line.status === "OK" &&
|
|
767
|
+
const category = data.categories.find((item) => item.id === line.categoryId);
|
|
768
|
+
if (line.status === "OK" && category !== void 0 && category.surface !== "NONE") {
|
|
769
|
+
const surface = category.surface;
|
|
770
|
+
surfaces.set(surface, [...surfaces.get(surface) ?? [], index]);
|
|
771
|
+
}
|
|
713
772
|
});
|
|
714
773
|
for (const [surface, indexes] of surfaces) {
|
|
715
774
|
const rule = surfaceRuleOf(data, surface);
|
|
716
775
|
if (rule === void 0) continue;
|
|
776
|
+
const surfaceArea = surfaceAreaOf(variables, surface);
|
|
777
|
+
const puttyShare = puttyShareOn(room, surface, data.categories);
|
|
717
778
|
const outcome = runNumericFormula(rule.costFormula, {
|
|
718
779
|
variables: {
|
|
719
780
|
...variables,
|
|
720
|
-
surfaceArea
|
|
781
|
+
surfaceArea,
|
|
782
|
+
puttyRequired: puttyShare > 0,
|
|
783
|
+
puttyArea: surfaceArea * puttyShare
|
|
721
784
|
},
|
|
722
785
|
coefficients: data.coefficients
|
|
723
786
|
});
|
|
@@ -750,7 +813,9 @@ function selectRule(data, categoryId, materialTypeId) {
|
|
|
750
813
|
const rules = data.rules.filter(isMaterialRule);
|
|
751
814
|
return rules.find((rule) => rule.level === "MATERIAL_TYPE" && rule.categoryId === categoryId && rule.materialTypeId === materialTypeId) ?? rules.find((rule) => rule.level === "CATEGORY" && rule.categoryId === categoryId);
|
|
752
815
|
}
|
|
753
|
-
function materialLine(configuration, data, room,
|
|
816
|
+
function materialLine(configuration, data, room, selection, objectScope) {
|
|
817
|
+
const { categoryId } = selection;
|
|
818
|
+
const productId = selection.productId;
|
|
754
819
|
const init = {
|
|
755
820
|
id: lineId("MATERIALS", room.clientId, categoryId),
|
|
756
821
|
section: "MATERIALS",
|
|
@@ -758,6 +823,8 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
758
823
|
categoryId,
|
|
759
824
|
productId
|
|
760
825
|
};
|
|
826
|
+
const shareError = areaShareError(room, selection, data.categories);
|
|
827
|
+
if (shareError !== null) return failedLine(init, "ERROR", shareError);
|
|
761
828
|
const category = data.categories.find((item) => item.id === categoryId);
|
|
762
829
|
const product = data.products.find((item) => item.id === productId && item.categoryId === categoryId);
|
|
763
830
|
if (product === void 0 || category === void 0 || !product.available) return failedLine(init, "UNAVAILABLE", {
|
|
@@ -779,8 +846,12 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
779
846
|
materialTypeId: product.materialTypeId
|
|
780
847
|
}
|
|
781
848
|
});
|
|
849
|
+
const base = roomVariables(room);
|
|
850
|
+
const share = areaShareOf(selection);
|
|
782
851
|
const variables = {
|
|
783
|
-
...
|
|
852
|
+
...base,
|
|
853
|
+
wallArea: category.surface === "WALLS" ? base.wallArea * share : base.wallArea,
|
|
854
|
+
floorArea: category.surface === "FLOOR" ? base.floorArea * share : base.floorArea,
|
|
784
855
|
...productVariables(product, category),
|
|
785
856
|
...objectScope
|
|
786
857
|
};
|
|
@@ -850,7 +921,7 @@ function materialLine(configuration, data, room, categoryId, productId, objectSc
|
|
|
850
921
|
}
|
|
851
922
|
function materialLines(configuration, data, objectScope) {
|
|
852
923
|
return configuration.rooms.flatMap((room) => {
|
|
853
|
-
const lines = room.selections.flatMap((
|
|
924
|
+
const lines = room.selections.flatMap((selection) => selection.productId === null ? [] : [materialLine(configuration, data, room, selection, objectScope)]);
|
|
854
925
|
return configuration.scope === "PARTIAL" ? applySurfacePrep(data, room, lines, objectScope) : lines;
|
|
855
926
|
});
|
|
856
927
|
}
|
|
@@ -948,7 +1019,8 @@ const RangeSchema = z.object({
|
|
|
948
1019
|
//#region src/schemas/configuration.ts
|
|
949
1020
|
const RoomSelectionSchema = z.object({
|
|
950
1021
|
categoryId: z.string(),
|
|
951
|
-
productId: z.string().nullable()
|
|
1022
|
+
productId: z.string().nullable(),
|
|
1023
|
+
areaShare: z.int().min(1).max(100).optional()
|
|
952
1024
|
});
|
|
953
1025
|
const RoomConfigurationSchema = z.object({
|
|
954
1026
|
clientId: z.string(),
|
|
@@ -1052,7 +1124,8 @@ const PricingRoomTypeSchema = z.object({
|
|
|
1052
1124
|
const PricingCategorySchema = z.object({
|
|
1053
1125
|
id: z.string(),
|
|
1054
1126
|
wastePercent: z.number().min(0),
|
|
1055
|
-
surface: SurfaceKindSchema
|
|
1127
|
+
surface: SurfaceKindSchema,
|
|
1128
|
+
requiresPutty: z.boolean().default(true)
|
|
1056
1129
|
});
|
|
1057
1130
|
const PricingMaterialTypeSchema = z.object({
|
|
1058
1131
|
id: z.string(),
|
|
@@ -1074,11 +1147,11 @@ const PricingPackageItemSchema = z.object({
|
|
|
1074
1147
|
priceCents: CentsSchema.nullable(),
|
|
1075
1148
|
unit: OptionUnitSchema.nullable()
|
|
1076
1149
|
});
|
|
1077
|
-
const
|
|
1150
|
+
const PricingOptionObjectSchema = z.object({
|
|
1078
1151
|
id: z.string(),
|
|
1079
1152
|
code: OptionCodeSchema.nullable(),
|
|
1080
1153
|
kind: OptionKindSchema,
|
|
1081
|
-
priceCents: CentsSchema,
|
|
1154
|
+
priceCents: CentsSchema.nullable(),
|
|
1082
1155
|
unit: OptionUnitSchema,
|
|
1083
1156
|
roomTypeCodes: z.array(z.string()),
|
|
1084
1157
|
priceCoefficientKeys: z.array(z.string()),
|
|
@@ -1087,6 +1160,26 @@ const PricingOptionSchema = z.object({
|
|
|
1087
1160
|
maxQuantity: z.int().nullable(),
|
|
1088
1161
|
available: z.boolean()
|
|
1089
1162
|
});
|
|
1163
|
+
function findOptionPriceSourceViolation(priceCents, priceCoefficientKeys) {
|
|
1164
|
+
const hasKeys = priceCoefficientKeys.length > 0;
|
|
1165
|
+
if (priceCents !== null && hasKeys) return {
|
|
1166
|
+
path: "priceCents",
|
|
1167
|
+
message: "priceCents must be null when priceCoefficientKeys is set"
|
|
1168
|
+
};
|
|
1169
|
+
if (priceCents === null && !hasKeys) return {
|
|
1170
|
+
path: "priceCoefficientKeys",
|
|
1171
|
+
message: "priceCoefficientKeys is required when priceCents is null"
|
|
1172
|
+
};
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
const PricingOptionSchema = PricingOptionObjectSchema.superRefine((option, context) => {
|
|
1176
|
+
const violation = findOptionPriceSourceViolation(option.priceCents, option.priceCoefficientKeys);
|
|
1177
|
+
if (violation !== null) context.addIssue({
|
|
1178
|
+
code: "custom",
|
|
1179
|
+
path: [violation.path],
|
|
1180
|
+
message: violation.message
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1090
1183
|
const PricingDataSchema = z.object({
|
|
1091
1184
|
version: z.string().regex(/^[0-9a-f]{16}$/),
|
|
1092
1185
|
exchangeRate: ExchangeRateSchema.nullable(),
|
|
@@ -1166,6 +1259,8 @@ const RuleSampleSchema = z.object({
|
|
|
1166
1259
|
wastePercent: z.number().min(0)
|
|
1167
1260
|
}).optional(),
|
|
1168
1261
|
surfaceArea: z.number().min(0).optional(),
|
|
1262
|
+
puttyRequired: z.boolean().optional(),
|
|
1263
|
+
puttyArea: z.number().min(0).optional(),
|
|
1169
1264
|
object: z.object({
|
|
1170
1265
|
scope: ScopeSchema,
|
|
1171
1266
|
totalAreaSqm: z.number().positive().nullable(),
|
|
@@ -1176,6 +1271,7 @@ const RuleSampleSchema = z.object({
|
|
|
1176
1271
|
bathroomsCount: z.int().min(0),
|
|
1177
1272
|
roomsFloorArea: z.number().min(0),
|
|
1178
1273
|
roomsWallArea: z.number().min(0),
|
|
1274
|
+
roomsPuttyWallArea: z.number().min(0).optional(),
|
|
1179
1275
|
roomsPerimeter: z.number().min(0),
|
|
1180
1276
|
wetRoomsCount: z.int().min(0)
|
|
1181
1277
|
})
|
|
@@ -1243,10 +1339,32 @@ function roomsAreaWarnings(configuration, limits) {
|
|
|
1243
1339
|
params: { missingSqm }
|
|
1244
1340
|
}] : [];
|
|
1245
1341
|
}
|
|
1246
|
-
|
|
1342
|
+
const SHARE_SURFACES = ["WALLS", "FLOOR"];
|
|
1343
|
+
function areaShareWarnings(configuration, categories) {
|
|
1344
|
+
return configuration.rooms.flatMap((room) => {
|
|
1345
|
+
const variables = roomVariables(room);
|
|
1346
|
+
return SHARE_SURFACES.flatMap((surface) => {
|
|
1347
|
+
const shares = surfaceShares(room, surface, categories);
|
|
1348
|
+
if (!shares.shared || !shares.complete || shares.totalPercent >= 100) return [];
|
|
1349
|
+
const percent = 100 - shares.totalPercent;
|
|
1350
|
+
const area = surfaceAreaOf(variables, surface);
|
|
1351
|
+
return [{
|
|
1352
|
+
code: "AREA_SHARE_UNALLOCATED",
|
|
1353
|
+
params: {
|
|
1354
|
+
roomClientId: room.clientId,
|
|
1355
|
+
surface,
|
|
1356
|
+
percent,
|
|
1357
|
+
sqm: roundArea(area * percent / 100)
|
|
1358
|
+
}
|
|
1359
|
+
}];
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
function estimateWarnings(lines, configuration, rate, data) {
|
|
1247
1364
|
return [
|
|
1248
1365
|
...collectWarnings(lines),
|
|
1249
|
-
...roomsAreaWarnings(configuration, limits),
|
|
1366
|
+
...roomsAreaWarnings(configuration, data.limits),
|
|
1367
|
+
...areaShareWarnings(configuration, data.categories),
|
|
1250
1368
|
...configuration.currency === "UAH" && rate === null ? [{ code: "EXCHANGE_RATE_UNAVAILABLE" }] : []
|
|
1251
1369
|
];
|
|
1252
1370
|
}
|
|
@@ -1260,7 +1378,7 @@ function withKopecks(line, rate) {
|
|
|
1260
1378
|
}
|
|
1261
1379
|
function estimate(configuration, data) {
|
|
1262
1380
|
const rate = data.exchangeRate?.rate ?? null;
|
|
1263
|
-
const variables = objectVariables(configuration);
|
|
1381
|
+
const variables = objectVariables(configuration, data.categories);
|
|
1264
1382
|
const lines = [
|
|
1265
1383
|
...objectRuleLines(configuration, data, "BASE", variables),
|
|
1266
1384
|
...objectRuleLines(configuration, data, "OBJECT", variables),
|
|
@@ -1274,7 +1392,7 @@ function estimate(configuration, data) {
|
|
|
1274
1392
|
exchangeRate: data.exchangeRate,
|
|
1275
1393
|
lines,
|
|
1276
1394
|
totals: computeTotals(lines, rate !== null),
|
|
1277
|
-
warnings: estimateWarnings(lines, configuration, rate, data
|
|
1395
|
+
warnings: estimateWarnings(lines, configuration, rate, data),
|
|
1278
1396
|
incomplete: isIncomplete(lines)
|
|
1279
1397
|
};
|
|
1280
1398
|
}
|
|
@@ -1356,6 +1474,7 @@ function objectScope({ object }) {
|
|
|
1356
1474
|
bathroomsCount: object.bathroomsCount,
|
|
1357
1475
|
roomsFloorArea: object.roomsFloorArea,
|
|
1358
1476
|
roomsWallArea: object.roomsWallArea,
|
|
1477
|
+
roomsPuttyWallArea: object.roomsPuttyWallArea ?? object.roomsWallArea,
|
|
1359
1478
|
roomsPerimeter: object.roomsPerimeter,
|
|
1360
1479
|
wetRoomsCount: object.wetRoomsCount
|
|
1361
1480
|
};
|
|
@@ -1368,10 +1487,13 @@ function scopeOf(level, sample) {
|
|
|
1368
1487
|
code: "SAMPLE_INCOMPLETE",
|
|
1369
1488
|
params: { missing: room === void 0 ? "room" : "surfaceArea" }
|
|
1370
1489
|
} };
|
|
1490
|
+
const puttyRequired = sample.puttyRequired ?? true;
|
|
1371
1491
|
return { scope: {
|
|
1372
1492
|
...roomVariables(room),
|
|
1373
1493
|
...objectScope(sample),
|
|
1374
|
-
surfaceArea
|
|
1494
|
+
surfaceArea,
|
|
1495
|
+
puttyRequired,
|
|
1496
|
+
puttyArea: sample.puttyArea ?? (puttyRequired ? surfaceArea : 0)
|
|
1375
1497
|
} };
|
|
1376
1498
|
}
|
|
1377
1499
|
if (room === void 0 || product === void 0) return { error: {
|
|
@@ -1513,12 +1635,15 @@ const FORMULA_SCOPE_VARIABLES = {
|
|
|
1513
1635
|
enumeration("scope", ScopeSchema.options),
|
|
1514
1636
|
number("roomsFloorArea"),
|
|
1515
1637
|
number("roomsWallArea"),
|
|
1638
|
+
number("roomsPuttyWallArea"),
|
|
1516
1639
|
number("roomsPerimeter"),
|
|
1517
1640
|
number("wetRoomsCount")
|
|
1518
1641
|
]
|
|
1519
1642
|
};
|
|
1520
1643
|
const FORMULA_QUANTITY_VARIABLE = number("quantity");
|
|
1521
1644
|
const FORMULA_SURFACE_AREA_VARIABLE = number("surfaceArea");
|
|
1645
|
+
const FORMULA_PUTTY_AREA_VARIABLE = number("puttyArea");
|
|
1646
|
+
const FORMULA_PUTTY_REQUIRED_VARIABLE = boolean("puttyRequired");
|
|
1522
1647
|
const FORMULA_VARIABLES = {
|
|
1523
1648
|
OBJECT: FORMULA_SCOPE_VARIABLES.object,
|
|
1524
1649
|
CATEGORY: [
|
|
@@ -1534,7 +1659,9 @@ const FORMULA_VARIABLES = {
|
|
|
1534
1659
|
SURFACE: [
|
|
1535
1660
|
...FORMULA_SCOPE_VARIABLES.room,
|
|
1536
1661
|
...FORMULA_SCOPE_VARIABLES.object,
|
|
1537
|
-
FORMULA_SURFACE_AREA_VARIABLE
|
|
1662
|
+
FORMULA_SURFACE_AREA_VARIABLE,
|
|
1663
|
+
FORMULA_PUTTY_REQUIRED_VARIABLE,
|
|
1664
|
+
FORMULA_PUTTY_AREA_VARIABLE
|
|
1538
1665
|
]
|
|
1539
1666
|
};
|
|
1540
1667
|
const ALL_VARIABLE_NAMES = new Set([...FORMULA_VARIABLES.CATEGORY, ...FORMULA_VARIABLES.SURFACE].map((variable) => variable.name));
|
|
@@ -1749,4 +1876,4 @@ function validateFormula(input) {
|
|
|
1749
1876
|
};
|
|
1750
1877
|
}
|
|
1751
1878
|
//#endregion
|
|
1752
|
-
export { COEFFICIENT_NAMESPACE, CentsSchema, ConfigurationSchema, CurrencySchema, EstimateLineSchema, EstimateMessageSchema, EstimateSchema, EstimateTotalsSchema, EstimateWarningSchema, ExchangeRateSchema, FORMULA_FUNCTIONS, FORMULA_FUNCTION_TABLE, FORMULA_LIMITS, 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, levenshtein, objectVariables, parse, previewRule, productVariables, productVariablesOf, roomVariables, suggestNearest, toCents, totalAreaOf, validateFormula };
|
|
1879
|
+
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 };
|