@metron-labs/quote-react 0.1.61 → 0.1.63
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/dist/app/stages/ScheduleResultsStage.d.ts +1 -1
- package/dist/app/stages/ScheduleResultsStage.js +52 -21
- package/dist/components/estimators/ConcreteAggregateEstimator.js +12 -74
- package/dist/components/estimators/EstimatorResults.js +7 -7
- package/dist/components/estimators/PaintEstimator.d.ts +1 -1
- package/dist/components/estimators/PaintEstimator.js +7 -2
- package/dist/embed/quote.js +12 -12
- package/dist/lib/estimators/fixedProducts.js +2 -0
- package/dist/util/pricing.js +46 -0
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@ const FIXED_PRODUCTS = {
|
|
|
18
18
|
architrave: { "architrave-ogee": "134 primed mdf ogee", "architrave-pencil-round": "1896", "architrave-square-edge": "118 primed mdf square", "architrave-torus": "1531" },
|
|
19
19
|
// Board keys come from the board type; see fixedProductSearch.
|
|
20
20
|
plasterboard: { standard: "1647", moisture: "11 moisture plasterboard", fire: "14 fire plasterboard", sound: "1769", screws: "131160", tape: "1766", adhesive: "108439" },
|
|
21
|
+
paint: { "paint-emulsion": "157217" },
|
|
22
|
+
"concrete-aggregate": { cement: "1762 cement", "sharp-sand": "563", gravel: "571", mot: "569" },
|
|
21
23
|
insulation: { "insulation-floor": "152187", "insulation-ground": "442", "insulation-wall": "147", "insulation-roof": "442", "insulation-loft": "152180" },
|
|
22
24
|
timber: { studs: "1213", joists: "208", battens: "1729" },
|
|
23
25
|
fencing: {
|
package/dist/util/pricing.js
CHANGED
|
@@ -102,6 +102,24 @@ const packItemsForRequestedUnit = (item, requestedUnit) => {
|
|
|
102
102
|
}
|
|
103
103
|
return null;
|
|
104
104
|
};
|
|
105
|
+
// A tin, bag or tub priced per item usually names its size, e.g. "Vinyl Matt Emulsion - 5L" or
|
|
106
|
+
// "Sharp Sand Bulk Bag (800kg)"; a request in litres or kilograms is turned into whole packs from
|
|
107
|
+
// that. Only used when the catalogue gives no volume or weight itself.
|
|
108
|
+
const packSizeFromDescription = (item, measure) => {
|
|
109
|
+
const pattern = measure === "volume" ? /(\d+(?:[.,]\d+)?)\s*(ml|l|ltr|ltrs|litres?|liters?)\b/i : /(\d+(?:[.,]\d+)?)\s*(kg|kgs|tonnes?)\b/i;
|
|
110
|
+
const match = item.description.match(pattern);
|
|
111
|
+
if (!match)
|
|
112
|
+
return null;
|
|
113
|
+
const amount = Number.parseFloat(match[1].replace(",", "."));
|
|
114
|
+
if (!Number.isFinite(amount) || amount <= 0)
|
|
115
|
+
return null;
|
|
116
|
+
const unit = match[2].toLowerCase();
|
|
117
|
+
if (measure === "volume")
|
|
118
|
+
return { ml: unit === "ml" ? amount : amount * 1000 };
|
|
119
|
+
return { g: unit.startsWith("tonne") ? amount * 1000000 : amount * 1000 };
|
|
120
|
+
};
|
|
121
|
+
const catalogueVolumeTypes = ["volume_from_description", "same_unit_volume"];
|
|
122
|
+
const catalogueWeightTypes = ["weight_from_description", "mass_from_pack", "same_unit_mass"];
|
|
105
123
|
// Product exports often omit structured coverage but include it verbatim, e.g. "13.89m2/Roll".
|
|
106
124
|
// This is intentionally narrow: it must name the same sell unit as the catalogue row.
|
|
107
125
|
const coverageFromDescription = (item, requestedUnit, sellUnit) => {
|
|
@@ -142,6 +160,34 @@ export function getPricingBreakdown(item, options) {
|
|
|
142
160
|
});
|
|
143
161
|
if (!Number.isFinite(requestedAmount) || requestedAmount <= 0)
|
|
144
162
|
return review("Quantity needs confirmation.");
|
|
163
|
+
const requestVolumeMl = volumeInMl(requestedAmount, requestedUnit);
|
|
164
|
+
const requestWeightG = weightInG(requestedAmount, requestedUnit);
|
|
165
|
+
const packFromDescription = sameUnit(requestedUnit, sellUnit)
|
|
166
|
+
? null
|
|
167
|
+
: requestVolumeMl != null && !catalogueVolumeTypes.includes(item.conversionType ?? "")
|
|
168
|
+
? packSizeFromDescription(item, "volume")
|
|
169
|
+
: requestWeightG != null && !catalogueWeightTypes.includes(item.conversionType ?? "")
|
|
170
|
+
? packSizeFromDescription(item, "weight")
|
|
171
|
+
: null;
|
|
172
|
+
const requested = packFromDescription?.ml != null ? requestVolumeMl : requestWeightG;
|
|
173
|
+
const packAmount = packFromDescription?.ml ?? packFromDescription?.g ?? null;
|
|
174
|
+
if (requested != null && packAmount != null) {
|
|
175
|
+
const priceUnit = canonicalUnit(item.uoi) || sellUnit;
|
|
176
|
+
const packLabel = packFromDescription?.ml != null ? `${packAmount / 1000} l` : `${packAmount / 1000} kg`;
|
|
177
|
+
const required = hasOverride ? overriddenSellUnits : Math.ceil((requested * (1 + wasteFactor)) / packAmount - 1e-9);
|
|
178
|
+
return {
|
|
179
|
+
requestedAmount,
|
|
180
|
+
requestedUnit,
|
|
181
|
+
salesUnitSize: 1,
|
|
182
|
+
sellUnit: priceUnit,
|
|
183
|
+
calculatedSellUnits: required,
|
|
184
|
+
requiredSellUnits: required,
|
|
185
|
+
unitPrice: item.price,
|
|
186
|
+
lineTotal: item.price != null ? item.price * required : null,
|
|
187
|
+
calculation: `${requestedAmount} ${requestedUnit}${wasteFactor > 0 ? ` x ${1 + wasteFactor}` : ""} / ${packLabel} per ${priceUnit} (from product description) = ${required} ${priceUnit}`,
|
|
188
|
+
costingMode: "derived",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
145
191
|
// "sales_unit" means one listed-price unit, whatever it holds: 1 sales_unit of a box of
|
|
146
192
|
// 1,000 screws priced "per Each" is one box at the listed price. The estimators ask for it
|
|
147
193
|
// when they have already counted packs (see estimateRequestUnit).
|