thm-p3-configurator 0.0.441 → 0.0.443

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/dist/src/shared/__api__/__tests__/queryKeyFactory.test.js +26 -0
  2. package/dist/src/shared/__api__/queries.js +9 -4
  3. package/dist/src/shared/__api__/queryKeyFactory.js +10 -5
  4. package/dist/src/shared/__components__/ProductCard/index.js +4 -1
  5. package/dist/src/shared/__components__/SingleOrderCartCard.js +8 -2
  6. package/dist/src/shared/__components__/SingleOrderProductCard.js +43 -16
  7. package/dist/src/shared/__containers__/AppointmentForm.js +6 -3
  8. package/dist/src/shared/__containers__/ExitIntentModal.js +6 -3
  9. package/dist/src/shared/__containers__/ProductCartSide.js +5 -2
  10. package/dist/src/shared/__containers__/ProductsCartOverview.js +5 -5
  11. package/dist/src/shared/__containers__/ProductsOverview.js +3 -1
  12. package/dist/src/shared/__containers__/QuotationForm.js +6 -3
  13. package/dist/src/shared/__containers__/internal/InternalAppointmentForm.js +6 -3
  14. package/dist/src/shared/__containers__/internal/InternalQuotationForm.js +6 -3
  15. package/dist/src/shared/__containers__/internal/InternalSingleOrderCart.js +13 -6
  16. package/dist/src/shared/__containers__/internal/InternalSingleOrderProductOverview.js +3 -3
  17. package/dist/src/shared/__helpers__/__tests__/product.test.js +11 -0
  18. package/dist/src/shared/__helpers__/__tests__/singleOrderArticleRules.test.js +82 -0
  19. package/dist/src/shared/__helpers__/product.js +30 -14
  20. package/dist/src/shared/__helpers__/singleOrderArticleRules.js +54 -0
  21. package/dist/src/shared/__helpers__/singleOrderArticles.js +4 -1
  22. package/dist/src/shared/__style__/components/card/_card-horizontal.scss +6 -0
  23. package/package.json +1 -1
@@ -82,6 +82,32 @@ describe('queryKeyFactory', () => {
82
82
  extraPriceAmount: null
83
83
  })).toBe('licensePlate=12AB34&formula=THC');
84
84
  });
85
+
86
+ // Hyper adds the board-computer update when selectBoardComputerUpdate is
87
+ // absent, so an opt-out only survives when the false is serialized
88
+ it('keeps selectBoardComputerUpdate for both true and false', () => {
89
+ expect((0, _queryKeyFactory.serializeOrderParams)({
90
+ licensePlate: '12AB34',
91
+ formula: 'THC',
92
+ selectBoardComputerUpdate: false
93
+ })).toBe('licensePlate=12AB34&formula=THC&selectBoardComputerUpdate=false');
94
+ expect((0, _queryKeyFactory.serializeOrderParams)({
95
+ licensePlate: '12AB34',
96
+ formula: 'THC',
97
+ selectBoardComputerUpdate: true
98
+ })).toBe('licensePlate=12AB34&formula=THC&selectBoardComputerUpdate=true');
99
+ });
100
+ it('gives opting in and opting out of the board computer distinct query keys', () => {
101
+ expect((0, _queryKeyFactory.getOrderQueryKey)({
102
+ licensePlate: '12AB34',
103
+ formula: 'THC',
104
+ selectBoardComputerUpdate: true
105
+ })).not.toEqual((0, _queryKeyFactory.getOrderQueryKey)({
106
+ licensePlate: '12AB34',
107
+ formula: 'THC',
108
+ selectBoardComputerUpdate: false
109
+ }));
110
+ });
85
111
  it('builds order query keys from serialized order params', () => {
86
112
  expect((0, _queryKeyFactory.getOrderQueryKey)({
87
113
  licensePlate: '12AB34',
@@ -41,6 +41,7 @@ var _OrderSessionContext = require("../__context__/OrderSessionContext");
41
41
  var _usePricingFormula = require("../__hooks__/usePricingFormula");
42
42
  var _helpers__ = require("../__helpers__");
43
43
  var _singleOrderArticles = require("../__helpers__/singleOrderArticles");
44
+ var _singleOrderArticleRules = require("../__helpers__/singleOrderArticleRules");
44
45
  var _singleOrderCartArticles = require("../__helpers__/singleOrderCartArticles");
45
46
  var _product = require("../__helpers__/product");
46
47
  var _useSessionContextReady = require("../__hooks__/useSessionContextReady");
@@ -423,7 +424,8 @@ const useSingleOrderCart = () => {
423
424
  return {
424
425
  articles: [],
425
426
  totals: emptyTotals,
426
- validationError: null
427
+ validationError: null,
428
+ hasQuantityErrors: false
427
429
  };
428
430
  }
429
431
 
@@ -458,7 +460,8 @@ const useSingleOrderCart = () => {
458
460
  totalExclVat: Math.round(totalExclVat * 100) / 100,
459
461
  totalInclVat: Math.round(totalInclVat * 100) / 100
460
462
  },
461
- validationError: null
463
+ validationError: null,
464
+ hasQuantityErrors: (0, _singleOrderArticleRules.hasSingleOrderQuantityErrors)(articlesWithPrice)
462
465
  };
463
466
  }
464
467
 
@@ -486,7 +489,8 @@ const useSingleOrderCart = () => {
486
489
  return {
487
490
  articles: [],
488
491
  totals: emptyTotals,
489
- validationError
492
+ validationError,
493
+ hasQuantityErrors: false
490
494
  };
491
495
  }
492
496
  const articleLookup = await fetchCartArticleLookup(cart.map(article => article.articleNumber), singleOrderFormula);
@@ -518,7 +522,8 @@ const useSingleOrderCart = () => {
518
522
  totalExclVat: Math.round(totalExclVat * 100) / 100,
519
523
  totalInclVat: Math.round(totalInclVat * 100) / 100
520
524
  },
521
- validationError
525
+ validationError,
526
+ hasQuantityErrors: (0, _singleOrderArticleRules.hasSingleOrderQuantityErrors)(articlesWithPrice)
522
527
  };
523
528
  }
524
529
  });
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ require("core-js/modules/es.array.includes.js");
3
4
  require("core-js/modules/es.array.reduce.js");
4
5
  require("core-js/modules/es.array.sort.js");
5
6
  require("core-js/modules/es.json.stringify.js");
@@ -15,6 +16,7 @@ Object.defineProperty(exports, "__esModule", {
15
16
  value: true
16
17
  });
17
18
  exports.serializeOrderParams = exports.normalizeOrderParams = exports.getSingleOrderArticlesKey = exports.getOrderQueryKey = exports.getBuildYearsQueryKey = exports.getBrandsQueryKey = void 0;
19
+ require("core-js/modules/es.array.includes.js");
18
20
  require("core-js/modules/es.array.reduce.js");
19
21
  require("core-js/modules/es.array.sort.js");
20
22
  require("core-js/modules/es.json.stringify.js");
@@ -77,11 +79,17 @@ const removeNullishEntries = function removeNullishEntries() {
77
79
  return entryValue != null;
78
80
  }));
79
81
  };
82
+
83
+ // Hyper adds the board-computer update to the cart whenever
84
+ // selectBoardComputerUpdate is absent, so an explicit false has to survive
85
+ // serialization. Every other empty-ish value still drops out of the canonical
86
+ // URL, so variant client URLs keep collapsing to one Hyper URL.
87
+ const ORDER_PARAMS_KEPT_WHEN_FALSE = ['selectBoardComputerUpdate'];
80
88
  const removeLegacyEmptyOrderEntries = function removeLegacyEmptyOrderEntries() {
81
89
  let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
82
90
  return Object.fromEntries(Object.entries(value).filter(_ref2 => {
83
- let [, entryValue] = _ref2;
84
- return Boolean(entryValue);
91
+ let [key, entryValue] = _ref2;
92
+ return Boolean(entryValue) || entryValue === false && ORDER_PARAMS_KEPT_WHEN_FALSE.includes(key);
85
93
  }));
86
94
  };
87
95
  const sortObjectKeys = function sortObjectKeys() {
@@ -98,9 +106,6 @@ const normalizeOrderParams = exports.normalizeOrderParams = function normalizeOr
98
106
  normalizedParams.constructionYear = normalizedParams.buildYear;
99
107
  delete normalizedParams.buildYear;
100
108
  }
101
- if (normalizedParams.selectBoardComputerUpdate === false || normalizedParams.selectBoardComputerUpdate === 'false') {
102
- delete normalizedParams.selectBoardComputerUpdate;
103
- }
104
109
  if (Array.isArray(normalizedParams.cart) && normalizedParams.cart.length === 0) {
105
110
  delete normalizedParams.cart;
106
111
  }
@@ -109,6 +109,7 @@ const ProductCard = _ref => {
109
109
  isRecommended = false,
110
110
  productId,
111
111
  productTitle,
112
+ productOrientation,
112
113
  productDescription,
113
114
  stock = null,
114
115
  productUsps = [],
@@ -186,7 +187,9 @@ const ProductCard = _ref => {
186
187
  }, /*#__PURE__*/_react.default.createElement(_ProductCardPrice.default, {
187
188
  price: productDiscountedPrice < productPrice ? productPrice : productDiscountedPrice,
188
189
  discountedPrice: productDiscountedPrice < productPrice ? productDiscountedPrice : productPrice
189
- }))), /*#__PURE__*/_react.default.createElement("div", {
190
+ }))), productOrientation && /*#__PURE__*/_react.default.createElement("p", {
191
+ className: (0, _helpers__.withStyle)('card-title__orientation')
192
+ }, /*#__PURE__*/_react.default.createElement("strong", null, productOrientation)), /*#__PURE__*/_react.default.createElement("div", {
190
193
  className: (0, _helpers__.withStyle)('card-text row no-gutters flex-wrap align-items-bottom justify-content-start')
191
194
  }, /*#__PURE__*/_react.default.createElement(_ProductCardUsps.default, {
192
195
  usps: productUsps
@@ -8,6 +8,7 @@ exports.default = void 0;
8
8
  require("core-js/modules/es.number.to-fixed.js");
9
9
  var _react = _interopRequireDefault(require("react"));
10
10
  var _helpers__ = require("../__helpers__");
11
+ var _singleOrderArticleRules = require("../__helpers__/singleOrderArticleRules");
11
12
  var _LinkButton = _interopRequireDefault(require("./Buttons/LinkButton"));
12
13
  var _NumericInput = _interopRequireDefault(require("./Form/NumericInput"));
13
14
  function _interopRequireDefault(e) {
@@ -43,8 +44,10 @@ const SingleOrderCartCard = _ref2 => {
43
44
  productBrandNumber,
44
45
  quantity = 0,
45
46
  handleQuantityChange,
46
- removeFromCart
47
+ removeFromCart,
48
+ orderRules
47
49
  } = _ref2;
50
+ const quantityError = (0, _singleOrderArticleRules.getSingleOrderQuantityError)(quantity, orderRules);
48
51
  return /*#__PURE__*/_react.default.createElement("div", {
49
52
  key: "".concat(productId, "-").concat(Date.now()),
50
53
  className: (0, _helpers__.withStyle)('w-100 mb-3')
@@ -69,11 +72,14 @@ const SingleOrderCartCard = _ref2 => {
69
72
  width: "100px",
70
73
  height: "40px",
71
74
  defaultValue: quantity,
75
+ max: 9999,
72
76
  onChange: value => handleQuantityChange(productId, value, productPriceExclVat, productPriceInclVat, productBrandNumber),
73
77
  buttonTransformY: 40
74
78
  }), /*#__PURE__*/_react.default.createElement(_LinkButton.default, {
75
79
  label: "Verwijder",
76
80
  onClick: () => removeFromCart(productId)
77
- }))))));
81
+ })), quantityError && /*#__PURE__*/_react.default.createElement("small", {
82
+ className: (0, _helpers__.withStyle)('text-danger mt-1')
83
+ }, quantityError)))));
78
84
  };
79
85
  var _default = exports.default = SingleOrderCartCard;
@@ -31,6 +31,7 @@ var _queries = require("../__api__/queries");
31
31
  var _NumericInput = _interopRequireDefault(require("../__components__/Form/NumericInput"));
32
32
  var _constants__ = require("../__constants__");
33
33
  var _helpers__ = require("../__helpers__");
34
+ var _singleOrderArticleRules = require("../__helpers__/singleOrderArticleRules");
34
35
  var _singleOrderArticles = require("../__helpers__/singleOrderArticles");
35
36
  var _OutlinedButton = _interopRequireDefault(require("./Buttons/OutlinedButton"));
36
37
  var _PrimaryButton = _interopRequireDefault(require("./Buttons/PrimaryButton"));
@@ -139,13 +140,16 @@ const ProductActions = _ref2 => {
139
140
  productBrandNumber,
140
141
  hasPreferredPart = false,
141
142
  isWebshop = false,
142
- isLoadingPartStockLevel = false
143
+ isLoadingPartStockLevel = false,
144
+ quantityError
143
145
  } = _ref2;
144
146
  return /*#__PURE__*/_react.default.createElement("div", {
145
147
  className: (0, _helpers__.withStyle)('d-flex justify-content-end')
146
148
  }, /*#__PURE__*/_react.default.createElement("div", {
147
149
  className: (0, _helpers__.withStyle)('d-flex flex-column align-items-end')
148
- }, !isWebshop && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, isOutOfStock ? /*#__PURE__*/_react.default.createElement("div", {
150
+ }, quantityError && /*#__PURE__*/_react.default.createElement("div", {
151
+ className: (0, _helpers__.withStyle)('text-danger mb-1')
152
+ }, quantityError), !isWebshop && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, isOutOfStock ? /*#__PURE__*/_react.default.createElement("div", {
149
153
  className: (0, _helpers__.withStyle)('text-danger mb-1')
150
154
  }, "Niet op voorraad") : stockStatus === _constants__.StockStatus.InsufficientStock ? /*#__PURE__*/_react.default.createElement("div", {
151
155
  className: (0, _helpers__.withStyle)('text-warning mb-1')
@@ -165,6 +169,7 @@ const ProductActions = _ref2 => {
165
169
  width: "100px",
166
170
  height: "55px",
167
171
  defaultValue: quantity,
172
+ max: 9999,
168
173
  onChange: value => handleQuantityChange(productId, value, productPrice, productPriceInclVat, productBrandNumber),
169
174
  buttonTransformY: 40
170
175
  }), quantity === 0 ? /*#__PURE__*/_react.default.createElement(_PrimaryButton.default, {
@@ -204,7 +209,22 @@ const ProductCategories = _ref3 => {
204
209
  className: (0, _helpers__.withStyle)(partStockLevels.status === _constants__.StockStatus.OutOfStock ? 'text-danger' : partStockLevels.status === _constants__.StockStatus.InsufficientStock ? 'text-warning' : 'text-success')
205
210
  }, partStockLevels.status === _constants__.StockStatus.OutOfStock ? 'Niet op voorraad' : partStockLevels.status === _constants__.StockStatus.InsufficientStock ? 'Weinig op voorraad' : 'Op voorraad'), partStockLevels.quantity ? /*#__PURE__*/_react.default.createElement("small", null, ": ".concat(partStockLevels.quantity)) : ''));
206
211
  };
207
- const SingleOrderProductCard = _ref4 => {
212
+ const SingleOrderOrderRules = _ref4 => {
213
+ let {
214
+ rules
215
+ } = _ref4;
216
+ const ruleLines = (0, _singleOrderArticleRules.describeSingleOrderArticleRules)(rules);
217
+ if (!ruleLines.length) {
218
+ return null;
219
+ }
220
+ return /*#__PURE__*/_react.default.createElement("div", {
221
+ className: (0, _helpers__.withStyle)('mt-2')
222
+ }, ruleLines.map(ruleLine => /*#__PURE__*/_react.default.createElement("p", {
223
+ key: ruleLine,
224
+ className: (0, _helpers__.withStyle)('fw-bold mb-0')
225
+ }, ruleLine)));
226
+ };
227
+ const SingleOrderProductCard = _ref5 => {
208
228
  let {
209
229
  productId,
210
230
  isSelected,
@@ -232,8 +252,10 @@ const SingleOrderProductCard = _ref4 => {
232
252
  parts = [],
233
253
  branchId,
234
254
  brandName = null,
235
- isWebshop = false
236
- } = _ref4;
255
+ isWebshop = false,
256
+ productDescription,
257
+ orderRules
258
+ } = _ref5;
237
259
  const {
238
260
  productFieldLabels
239
261
  } = (0, _queries.useProductFieldLabels)();
@@ -250,12 +272,12 @@ const SingleOrderProductCard = _ref4 => {
250
272
  return typeof (part === null || part === void 0 ? void 0 : part.preferredPart) === 'string' ? (_part$preferredPart$s = part.preferredPart.split('|')[0]) === null || _part$preferredPart$s === void 0 ? void 0 : _part$preferredPart$s.trim() : null;
251
273
  };
252
274
  const getPreferredPartNumber = part => {
253
- var _ref5, _ref6, _ref7, _part$articleNumber;
254
- return (_ref5 = (_ref6 = (_ref7 = (_part$articleNumber = part === null || part === void 0 ? void 0 : part.articleNumber) !== null && _part$articleNumber !== void 0 ? _part$articleNumber : part === null || part === void 0 ? void 0 : part.partArticleNumber) !== null && _ref7 !== void 0 ? _ref7 : part === null || part === void 0 ? void 0 : part.preferredPartNumber) !== null && _ref6 !== void 0 ? _ref6 : part === null || part === void 0 ? void 0 : part.partNumber) !== null && _ref5 !== void 0 ? _ref5 : getPreferredPartDisplayArticleNumber(part);
275
+ var _ref6, _ref7, _ref8, _part$articleNumber;
276
+ return (_ref6 = (_ref7 = (_ref8 = (_part$articleNumber = part === null || part === void 0 ? void 0 : part.articleNumber) !== null && _part$articleNumber !== void 0 ? _part$articleNumber : part === null || part === void 0 ? void 0 : part.partArticleNumber) !== null && _ref8 !== void 0 ? _ref8 : part === null || part === void 0 ? void 0 : part.preferredPartNumber) !== null && _ref7 !== void 0 ? _ref7 : part === null || part === void 0 ? void 0 : part.partNumber) !== null && _ref6 !== void 0 ? _ref6 : getPreferredPartDisplayArticleNumber(part);
255
277
  };
256
278
  const getPreferredPartBrandNumber = part => {
257
- var _ref8, _ref9, _ref10, _ref11, _part$articleBrandNum;
258
- return (_ref8 = (_ref9 = (_ref10 = (_ref11 = (_part$articleBrandNum = part === null || part === void 0 ? void 0 : part.articleBrandNumber) !== null && _part$articleBrandNum !== void 0 ? _part$articleBrandNum : part === null || part === void 0 ? void 0 : part.partArticleBrandNumber) !== null && _ref11 !== void 0 ? _ref11 : part === null || part === void 0 ? void 0 : part.preferredPartBrandNumber) !== null && _ref10 !== void 0 ? _ref10 : part === null || part === void 0 ? void 0 : part.partBrandNumber) !== null && _ref9 !== void 0 ? _ref9 : part === null || part === void 0 ? void 0 : part.brandNumber) !== null && _ref8 !== void 0 ? _ref8 : part === null || part === void 0 ? void 0 : part.partBrandId;
279
+ var _ref9, _ref10, _ref11, _ref12, _part$articleBrandNum;
280
+ return (_ref9 = (_ref10 = (_ref11 = (_ref12 = (_part$articleBrandNum = part === null || part === void 0 ? void 0 : part.articleBrandNumber) !== null && _part$articleBrandNum !== void 0 ? _part$articleBrandNum : part === null || part === void 0 ? void 0 : part.partArticleBrandNumber) !== null && _ref12 !== void 0 ? _ref12 : part === null || part === void 0 ? void 0 : part.preferredPartBrandNumber) !== null && _ref11 !== void 0 ? _ref11 : part === null || part === void 0 ? void 0 : part.partBrandNumber) !== null && _ref10 !== void 0 ? _ref10 : part === null || part === void 0 ? void 0 : part.brandNumber) !== null && _ref9 !== void 0 ? _ref9 : part === null || part === void 0 ? void 0 : part.partBrandId;
259
281
  };
260
282
  (0, _react.useEffect)(() => {
261
283
  if (showPartStockLevel && preferredPart) {
@@ -289,17 +311,17 @@ const SingleOrderProductCard = _ref4 => {
289
311
  setPartStockLevels(null);
290
312
  }
291
313
  }, [parts]);
292
- const filteredProductSpecifications = productSpecifications.filter(_ref12 => {
314
+ const filteredProductSpecifications = productSpecifications.filter(_ref13 => {
293
315
  let {
294
316
  key,
295
317
  value
296
- } = _ref12;
318
+ } = _ref13;
297
319
  return (productFieldLabels === null || productFieldLabels === void 0 ? void 0 : productFieldLabels[key]) && (value !== 'kg' || key !== 'weight');
298
- }).map(_ref13 => {
320
+ }).map(_ref14 => {
299
321
  let {
300
322
  key,
301
323
  value
302
- } = _ref13;
324
+ } = _ref14;
303
325
  return {
304
326
  key: productFieldLabels[key],
305
327
  value
@@ -320,7 +342,9 @@ const SingleOrderProductCard = _ref4 => {
320
342
  className: (0, _helpers__.withStyle)('card-title mb-3')
321
343
  }, productTitle, /*#__PURE__*/_react.default.createElement("span", {
322
344
  className: (0, _helpers__.withStyle)('ms-2 d-flex flex-column align-items-end')
323
- }, locationPriceExclVat != null && /*#__PURE__*/_react.default.createElement("span", null, "Vestigingprijs excl BTW: \u20AC", locationPriceExclVat.toFixed(2)), consumerPriceExclVat != null && /*#__PURE__*/_react.default.createElement("small", null, "Consumentenprijs excl BTW: \u20AC", consumerPriceExclVat.toFixed(2)))), /*#__PURE__*/_react.default.createElement("div", {
345
+ }, locationPriceExclVat != null && /*#__PURE__*/_react.default.createElement("span", null, "Vestigingprijs excl BTW: \u20AC", locationPriceExclVat.toFixed(2)), consumerPriceExclVat != null && /*#__PURE__*/_react.default.createElement("small", null, "Consumentenprijs excl BTW: \u20AC", consumerPriceExclVat.toFixed(2)))), productDescription && /*#__PURE__*/_react.default.createElement("h6", {
346
+ className: (0, _helpers__.withStyle)('card-subtitle mb-3')
347
+ }, productDescription), /*#__PURE__*/_react.default.createElement("div", {
324
348
  className: (0, _helpers__.withStyle)(isModelArticle ? 'd-flex justify-content-between' : '')
325
349
  }, (productSubgroup || articleNumber || preferredPart) && /*#__PURE__*/_react.default.createElement(ProductCategories, {
326
350
  brandName: brandName,
@@ -334,7 +358,9 @@ const SingleOrderProductCard = _ref4 => {
334
358
  }), (filteredProductSpecifications === null || filteredProductSpecifications === void 0 ? void 0 : filteredProductSpecifications.length) > 0 && /*#__PURE__*/_react.default.createElement(_ProductCardSpecifications.default, {
335
359
  specifications: filteredProductSpecifications,
336
360
  className: isModelArticle ? '' : 'w-100'
337
- })), (productUsps === null || productUsps === void 0 ? void 0 : productUsps.length) > 0 && !productUsps.every(usp => usp.text === null) && /*#__PURE__*/_react.default.createElement(_ProductCardUsps.default, {
361
+ })), /*#__PURE__*/_react.default.createElement(SingleOrderOrderRules, {
362
+ rules: orderRules
363
+ }), (productUsps === null || productUsps === void 0 ? void 0 : productUsps.length) > 0 && !productUsps.every(usp => usp.text === null) && /*#__PURE__*/_react.default.createElement(_ProductCardUsps.default, {
338
364
  usps: productUsps,
339
365
  className: isModelArticle ? 'mr-2' : 'w-100'
340
366
  })), /*#__PURE__*/_react.default.createElement(ProductActions, {
@@ -354,7 +380,8 @@ const SingleOrderProductCard = _ref4 => {
354
380
  productBrandNumber: productBrandNumber,
355
381
  hasPreferredPart: Boolean(preferredPart),
356
382
  isWebshop: isWebshop,
357
- isLoadingPartStockLevel: isLoadingPartStockLevel
383
+ isLoadingPartStockLevel: isLoadingPartStockLevel,
384
+ quantityError: (0, _singleOrderArticleRules.getSingleOrderQuantityError)(quantity, orderRules)
358
385
  })))));
359
386
  };
360
387
  var _default = exports.default = SingleOrderProductCard;
@@ -208,7 +208,11 @@ const AppointmentForm = () => {
208
208
  });
209
209
  const prospectId = (0, _prospectTracking.createProspectId)();
210
210
  prospectIdRef.current = prospectId;
211
- const appointmentRequestPayload = (0, _helpers__.removeNullishProps)({
211
+ const appointmentRequestPayload = _objectSpread({
212
+ // Note: outside removeNullishProps(), which strips a `false`. Hyper
213
+ // adds the board-computer update whenever this flag is absent
214
+ selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection)
215
+ }, (0, _helpers__.removeNullishProps)({
212
216
  prospectId,
213
217
  licensePlate: licensePlate === null || licensePlate === void 0 ? void 0 : licensePlate.replace(/-/g, ''),
214
218
  model,
@@ -227,7 +231,6 @@ const AppointmentForm = () => {
227
231
  })),
228
232
  formula,
229
233
  branchId: selectedBranch,
230
- selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection),
231
234
  skipFilters,
232
235
  discount: (() => {
233
236
  if (!(discountCode !== null && discountCode !== void 0 && discountCode.length)) return null;
@@ -246,7 +249,7 @@ const AppointmentForm = () => {
246
249
  isTowmotiveFormula: isTowmotive,
247
250
  vehicleCodingDiscountPercentage
248
251
  }))
249
- });
252
+ }));
250
253
  await createAppointmentRequest(appointmentRequestPayload);
251
254
  } catch (ex) {
252
255
  (0, _Datalayer.pushToDataLayer)({
@@ -243,7 +243,11 @@ const ExitIntentModal = () => {
243
243
  });
244
244
  const prospectId = (0, _prospectTracking.createProspectId)();
245
245
  prospectIdRef.current = prospectId;
246
- await createQuotation((0, _helpers__.removeNullishProps)({
246
+ await createQuotation(_objectSpread({
247
+ // Note: outside removeNullishProps(), which strips a `false`. Hyper
248
+ // adds the board-computer update whenever this flag is absent
249
+ selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection)
250
+ }, (0, _helpers__.removeNullishProps)({
247
251
  prospectId,
248
252
  isExitIntent: true,
249
253
  licensePlate,
@@ -263,7 +267,6 @@ const ExitIntentModal = () => {
263
267
  lastName: (_customer$lastName = customer === null || customer === void 0 ? void 0 : customer.lastName) !== null && _customer$lastName !== void 0 ? _customer$lastName : undefined
264
268
  })),
265
269
  branchId: selectedBranch,
266
- selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection),
267
270
  skipFilters,
268
271
  cart: (0, _product.buildCartInput)(_objectSpread(_objectSpread({
269
272
  selectedCableset,
@@ -275,7 +278,7 @@ const ExitIntentModal = () => {
275
278
  isTowmotiveFormula: isTowmotive,
276
279
  vehicleCodingDiscountPercentage
277
280
  }))
278
- }));
281
+ })));
279
282
  } catch (ex) {
280
283
  (0, _Datalayer.pushToDataLayer)({
281
284
  event: 'form',
@@ -109,7 +109,8 @@ const ProductCartSide = () => {
109
109
  montageRate,
110
110
  extraPriceAmount,
111
111
  discountCode,
112
- priceCalculationDate
112
+ priceCalculationDate,
113
+ skipFilters
113
114
  } = order;
114
115
  const isTmg = (0, _useIsTmg.useIsTmg)();
115
116
  const isTowmotive = (0, _useIsTowmotive.useIsTowmotive)();
@@ -128,7 +129,9 @@ const ProductCartSide = () => {
128
129
  consumerDiscounts,
129
130
  touchedConsumerDiscounts
130
131
  });
131
- const shouldRequestUnfilteredProducts = (0, _product.shouldRequestUnfilteredProductsForCart)(cartInput) || Boolean(existingDossierId && (allArticleNumbers === null || allArticleNumbers === void 0 ? void 0 : allArticleNumbers.length) > 0);
132
+ // Use the session skipFilters so this query matches the product page's;
133
+ // an unfiltered request can return a different catalogue than the page.
134
+ const shouldRequestUnfilteredProducts = skipFilters || Boolean(existingDossierId && (allArticleNumbers === null || allArticleNumbers === void 0 ? void 0 : allArticleNumbers.length) > 0);
132
135
  const {
133
136
  cart,
134
137
  totals,
@@ -280,7 +280,8 @@ const ProductCartTable = () => {
280
280
  touchedConsumerDiscounts,
281
281
  montageRate,
282
282
  isMontageTariffFixed,
283
- extraPriceAmount
283
+ extraPriceAmount,
284
+ skipFilters
284
285
  }, dispatch] = (0, _OrderSessionContext.useOrderSession)();
285
286
 
286
287
  // Determine display mode
@@ -354,10 +355,9 @@ const ProductCartTable = () => {
354
355
  consumerDiscounts,
355
356
  touchedConsumerDiscounts
356
357
  });
357
- // Also request unfiltered products when editing an existing dossier with
358
- // article numbers that could not be classified into selectedTowbar etc. during
359
- // hydration (e.g. formula-specific articles not in the TM catalogue).
360
- const shouldRequestUnfilteredProducts = (0, _product.shouldRequestUnfilteredProductsForCart)(cartInput) || Boolean(existingDossierId && (allArticleNumbers === null || allArticleNumbers === void 0 ? void 0 : allArticleNumbers.length) > 0);
358
+ // Use the session skipFilters so this query matches the product page's;
359
+ // an unfiltered request can return a different catalogue than the page.
360
+ const shouldRequestUnfilteredProducts = skipFilters || Boolean(existingDossierId && (allArticleNumbers === null || allArticleNumbers === void 0 ? void 0 : allArticleNumbers.length) > 0);
361
361
  const durationAnswerForQuery = durationAnswerIsPrefilled ? undefined : durationAnswer;
362
362
  const {
363
363
  cart,
@@ -456,6 +456,7 @@ const ProductsOverview = () => {
456
456
  })
457
457
  });
458
458
  }
459
+ const towbarOrientation = !APP_CONFIG.internal && (productCategory === _constants__.PRODUCT_CATEGORIES.TOWBAR || productCategory === _constants__.PRODUCT_CATEGORIES.COMBISET) ? (0, _product.getDetachableTowbarOrientation)(product === null || product === void 0 ? void 0 : product.subgroupName) : null;
459
460
  return /*#__PURE__*/_react.default.createElement(_ProductCard.default, {
460
461
  isTmg: isTmg,
461
462
  stock: (_product$consumerPric = product === null || product === void 0 || (_product$consumerPric2 = product.consumerPrices) === null || _product$consumerPric2 === void 0 ? void 0 : _product$consumerPric2.stock) !== null && _product$consumerPric !== void 0 ? _product$consumerPric : product === null || product === void 0 || (_product$locationPric = product.locationPrices) === null || _product$locationPric === void 0 ? void 0 : _product$locationPric.stock,
@@ -463,7 +464,8 @@ const ProductsOverview = () => {
463
464
  hasCheckbox: (productsPerCategory === null || productsPerCategory === void 0 ? void 0 : productsPerCategory.length) > 1,
464
465
  isRecommended: isRecommended,
465
466
  productId: product === null || product === void 0 ? void 0 : product.articleNumber,
466
- productTitle: product === null || product === void 0 ? void 0 : product.subgroupName,
467
+ productTitle: towbarOrientation ? (0, _product.getTowbarInfoTitle)(product === null || product === void 0 ? void 0 : product.subgroupName) : product === null || product === void 0 ? void 0 : product.subgroupName,
468
+ productOrientation: towbarOrientation,
467
469
  productDescription: product === null || product === void 0 ? void 0 : product.productDescription,
468
470
  productUsps: [{
469
471
  type: 'excl',
@@ -210,7 +210,11 @@ const QuotationForm = () => {
210
210
  });
211
211
  const prospectId = (0, _prospectTracking.createProspectId)();
212
212
  prospectIdRef.current = prospectId;
213
- await createQuotation((0, _helpers__.removeNullishProps)({
213
+ await createQuotation(_objectSpread({
214
+ // Note: outside removeNullishProps(), which strips a `false`. Hyper
215
+ // adds the board-computer update whenever this flag is absent
216
+ selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection)
217
+ }, (0, _helpers__.removeNullishProps)({
214
218
  prospectId,
215
219
  isExitIntent: false,
216
220
  licensePlate,
@@ -230,7 +234,6 @@ const QuotationForm = () => {
230
234
  formula,
231
235
  customer: (0, _helpers__.removeNullishProps)(customer),
232
236
  branchId: selectedBranch,
233
- selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection),
234
237
  skipFilters,
235
238
  cart: (0, _product.buildCartInput)(_objectSpread(_objectSpread({
236
239
  selectedCableset,
@@ -242,7 +245,7 @@ const QuotationForm = () => {
242
245
  isTowmotiveFormula: isTowmotive,
243
246
  vehicleCodingDiscountPercentage
244
247
  }))
245
- }));
248
+ })));
246
249
  } catch (ex) {
247
250
  (0, _Datalayer.pushToDataLayer)({
248
251
  event: 'form',
@@ -656,7 +656,11 @@ const InternalAppointmentForm = _ref => {
656
656
  isPlannedAppointment: appointmentType === _constants__.DOSSIER_TYPES.PlannedAppointment
657
657
  }
658
658
  });
659
- const payload = (0, _helpers__.removeNullishProps)(_objectSpread(_objectSpread({
659
+ const payload = _objectSpread({
660
+ // Note: outside removeNullishProps(), which strips a `false`. Hyper
661
+ // adds the board-computer update whenever this flag is absent
662
+ selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection)
663
+ }, (0, _helpers__.removeNullishProps)(_objectSpread(_objectSpread({
660
664
  vehicle,
661
665
  customer: (0, _helpers__.removeNullishProps)(_objectSpread(_objectSpread({}, customer), {}, {
662
666
  firstName: (_customer$firstName = customer === null || customer === void 0 ? void 0 : customer.firstName) !== null && _customer$firstName !== void 0 ? _customer$firstName : undefined,
@@ -704,7 +708,6 @@ const InternalAppointmentForm = _ref => {
704
708
  formula
705
709
  }), {}, {
706
710
  branchId: selectedBranch,
707
- selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection),
708
711
  skipFilters,
709
712
  cart: (0, _product.buildCartInput)(_objectSpread(_objectSpread({
710
713
  selectedCableset,
@@ -719,7 +722,7 @@ const InternalAppointmentForm = _ref => {
719
722
  touchedConsumerDiscounts
720
723
  })),
721
724
  montageRate: montageRate !== null && montageRate !== void 0 ? montageRate : undefined
722
- }));
725
+ })));
723
726
  payload.vehicle = _objectSpread(_objectSpread({}, payload.vehicle), {}, {
724
727
  leaseNumber: activeOwnershipType === _constants__.VEHICLE_OWNERSHIP_TYPE.Leasemaatschappij ? (_vehicle$leaseNumber = vehicle === null || vehicle === void 0 ? void 0 : vehicle.leaseNumber) !== null && _vehicle$leaseNumber !== void 0 ? _vehicle$leaseNumber : '' : '',
725
728
  leaseNumberUnknown: activeOwnershipType === _constants__.VEHICLE_OWNERSHIP_TYPE.Leasemaatschappij ? (_vehicle$leaseNumberU = vehicle === null || vehicle === void 0 ? void 0 : vehicle.leaseNumberUnknown) !== null && _vehicle$leaseNumberU !== void 0 ? _vehicle$leaseNumberU : false : false
@@ -558,7 +558,11 @@ const InternalQuotationForm = _ref => {
558
558
  }
559
559
  });
560
560
  const isAnwbCode = discountCode === null || discountCode === void 0 ? void 0 : discountCode.toLocaleLowerCase().includes('anwb');
561
- const payload = (0, _helpers__.removeNullishProps)(_objectSpread(_objectSpread({
561
+ const payload = _objectSpread({
562
+ // Note: outside removeNullishProps(), which strips a `false`. Hyper
563
+ // adds the board-computer update whenever this flag is absent
564
+ selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection)
565
+ }, (0, _helpers__.removeNullishProps)(_objectSpread(_objectSpread({
562
566
  vehicle,
563
567
  customer: (0, _helpers__.removeNullishProps)(_objectSpread(_objectSpread({}, customer), {}, {
564
568
  firstName: (_customer$firstName = customer === null || customer === void 0 ? void 0 : customer.firstName) !== null && _customer$firstName !== void 0 ? _customer$firstName : undefined,
@@ -601,7 +605,6 @@ const InternalQuotationForm = _ref => {
601
605
  formula
602
606
  }), {}, {
603
607
  branchId: selectedBranch,
604
- selectBoardComputerUpdate: (0, _product.shouldSelectBoardComputerUpdate)(boardComputerSelection),
605
608
  skipFilters,
606
609
  cart: (0, _product.buildCartInput)(_objectSpread(_objectSpread({
607
610
  selectedCableset,
@@ -616,7 +619,7 @@ const InternalQuotationForm = _ref => {
616
619
  touchedConsumerDiscounts
617
620
  })),
618
621
  montageRate: montageRate !== null && montageRate !== void 0 ? montageRate : undefined
619
- }));
622
+ })));
620
623
  payload.vehicle = _objectSpread(_objectSpread({}, payload.vehicle), {}, {
621
624
  leaseNumber: activeOwnershipType === _constants__.VEHICLE_OWNERSHIP_TYPE.Leasemaatschappij ? (_vehicle$leaseNumber = vehicle === null || vehicle === void 0 ? void 0 : vehicle.leaseNumber) !== null && _vehicle$leaseNumber !== void 0 ? _vehicle$leaseNumber : '' : '',
622
625
  leaseNumberUnknown: activeOwnershipType === _constants__.VEHICLE_OWNERSHIP_TYPE.Leasemaatschappij ? (_vehicle$leaseNumberU = vehicle === null || vehicle === void 0 ? void 0 : vehicle.leaseNumberUnknown) !== null && _vehicle$leaseNumberU !== void 0 ? _vehicle$leaseNumberU : false : false
@@ -16,6 +16,7 @@ var _DangerAlert = _interopRequireDefault(require("../../__components__/DangerAl
16
16
  var _ExpandableCard = _interopRequireDefault(require("../../__components__/Cards/ExpandableCard"));
17
17
  var _SingleOrderCartCard = _interopRequireDefault(require("../../__components__/SingleOrderCartCard"));
18
18
  var _helpers__ = require("../../__helpers__");
19
+ var _singleOrderArticleRules = require("../../__helpers__/singleOrderArticleRules");
19
20
  function _interopRequireDefault(e) {
20
21
  return e && e.__esModule ? e : {
21
22
  default: e
@@ -30,7 +31,7 @@ const EmptyState = _ref => {
30
31
  }, message));
31
32
  };
32
33
  const InternalSingleOrderCart = _ref2 => {
33
- var _cart$validationError, _cart$articles, _cart$totals$totalExc, _cart$totals, _cart$totals$totalBtw, _cart$totals2, _cart$totals$totalInc, _cart$totals3;
34
+ var _cart$articles, _cart$totals$totalExc, _cart$totals, _cart$totals$totalBtw, _cart$totals2, _cart$totals$totalInc, _cart$totals3;
34
35
  let {
35
36
  handleQuantityChange,
36
37
  removeFromCart: _removeFromCart,
@@ -42,9 +43,14 @@ const InternalSingleOrderCart = _ref2 => {
42
43
  } = _ref2;
43
44
  const {
44
45
  cart,
45
- isLoadingCart
46
+ isLoadingCart,
47
+ isCartError
46
48
  } = (0, _queries.useSingleOrderCart)();
47
- const cartValidationError = (_cart$validationError = cart === null || cart === void 0 ? void 0 : cart.validationError) !== null && _cart$validationError !== void 0 ? _cart$validationError : null;
49
+ const blockingMessage = cart !== null && cart !== void 0 && cart.validationError ? "".concat(cart.validationError, " Pas de artikelen aan voordat je kunt bevestigen.") : cart !== null && cart !== void 0 && cart.hasQuantityErrors ? 'Een of meer aantallen voldoen niet aan de besteleenheid of het minimum. Pas de aantallen aan voordat je kunt bevestigen.' : null;
50
+ // Every quantity change mints a new query key and the cart holds no previous
51
+ // data, so confirming stays blocked until the answer is back — otherwise the
52
+ // rules can be skipped through that window.
53
+ const isConfirmBlocked = isLoadingCart || isCartError || Boolean(blockingMessage);
48
54
  const confirmButtonClicked = () => {
49
55
  if (showProductOverview) {
50
56
  toggleOverview();
@@ -71,6 +77,7 @@ const InternalSingleOrderCart = _ref2 => {
71
77
  productBrandNumber: article.brandNumber,
72
78
  removeFromCart: () => _removeFromCart(article.articleNumber),
73
79
  quantity: article.quantity,
80
+ orderRules: (0, _singleOrderArticleRules.resolveSingleOrderArticleRules)(article),
74
81
  handleQuantityChange: handleQuantityChange
75
82
  });
76
83
  }) : /*#__PURE__*/_react.default.createElement(EmptyState, {
@@ -91,11 +98,11 @@ const InternalSingleOrderCart = _ref2 => {
91
98
  }, /*#__PURE__*/_react.default.createElement(_LinkButton.default, {
92
99
  label: "Bestelling herstarten",
93
100
  onClick: () => resetOrderSession()
94
- }), cartValidationError && /*#__PURE__*/_react.default.createElement(_DangerAlert.default, {
101
+ }), blockingMessage && /*#__PURE__*/_react.default.createElement(_DangerAlert.default, {
95
102
  className: "mx-2 mb-0",
96
- message: "".concat(cartValidationError, " Pas de artikelen aan voordat je kunt bevestigen.")
103
+ message: blockingMessage
97
104
  }), /*#__PURE__*/_react.default.createElement(_PrimaryButton.default, {
98
- isDisabled: disabledCreateOrder || Boolean(cartValidationError),
105
+ isDisabled: disabledCreateOrder || isConfirmBlocked,
99
106
  label: !showProductOverview ? 'Bevestigen' : 'Bestellen',
100
107
  onClick: confirmButtonClicked
101
108
  })));
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
 
3
- require("core-js/modules/es.symbol.description.js");
4
3
  require("core-js/modules/es.number.to-fixed.js");
5
4
  require("core-js/modules/esnext.iterator.constructor.js");
6
5
  require("core-js/modules/esnext.iterator.filter.js");
@@ -14,7 +13,6 @@ Object.defineProperty(exports, "__esModule", {
14
13
  value: true
15
14
  });
16
15
  exports.default = void 0;
17
- require("core-js/modules/es.symbol.description.js");
18
16
  require("core-js/modules/es.number.to-fixed.js");
19
17
  require("core-js/modules/esnext.iterator.constructor.js");
20
18
  require("core-js/modules/esnext.iterator.find.js");
@@ -30,6 +28,7 @@ var _FormulaContext = require("../../__context__/FormulaContext");
30
28
  var _OrderSessionContext = require("../../__context__/OrderSessionContext");
31
29
  var _helpers__ = require("../../__helpers__");
32
30
  var _product = require("../../__helpers__/product");
31
+ var _singleOrderArticleRules = require("../../__helpers__/singleOrderArticleRules");
33
32
  var _singleOrderArticles = require("../../__helpers__/singleOrderArticles");
34
33
  var _useIsTmg = require("../../__hooks__/useIsTmg");
35
34
  function _interopRequireDefault(e) {
@@ -170,7 +169,8 @@ const InternalSingleOrderProductOverview = _ref => {
170
169
  }],
171
170
  articleNumber: article.articleNumber,
172
171
  handleQuantityChange: handleQuantityChange,
173
- productDescription: article === null || article === void 0 ? void 0 : article.description,
172
+ productDescription: article === null || article === void 0 ? void 0 : article.singleOrderDescription,
173
+ orderRules: (0, _singleOrderArticleRules.resolveSingleOrderArticleRules)(article),
174
174
  productImage: article.image,
175
175
  productSpecifications: (0, _product.extractProductSpecsArray)(article),
176
176
  productTitle: article.name,
@@ -46,4 +46,15 @@ describe('product helpers', () => {
46
46
  expect(result.priceInclVat).toBe(189);
47
47
  expect(result.priceExclVat).toBeCloseTo(156.2, 2);
48
48
  });
49
+ });
50
+ describe('getDetachableTowbarOrientation', () => {
51
+ it('returns horizontaal or verticaal for detachable towbars', () => {
52
+ expect((0, _product.getDetachableTowbarOrientation)('Trekhaken horizontaal afneembaar')).toBe('horizontaal');
53
+ expect((0, _product.getDetachableTowbarOrientation)('Trekhaken verticaal afneembaar')).toBe('verticaal');
54
+ });
55
+ it('does not infer a type for other hitch names', () => {
56
+ expect((0, _product.getDetachableTowbarOrientation)('Afneembare trekhaak')).toBeNull();
57
+ expect((0, _product.getDetachableTowbarOrientation)('Trekhaken vast')).toBeNull();
58
+ expect((0, _product.getDetachableTowbarOrientation)('')).toBeNull();
59
+ });
49
60
  });
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ var _singleOrderArticleRules = require("../singleOrderArticleRules");
4
+ describe('singleOrderArticleRules', () => {
5
+ it('ignores absent and non-positive Hyper values', () => {
6
+ expect((0, _singleOrderArticleRules.resolveSingleOrderArticleRules)({})).toEqual({
7
+ minimumQuantity: null,
8
+ orderUnit: null
9
+ });
10
+ expect((0, _singleOrderArticleRules.resolveSingleOrderArticleRules)({
11
+ singleOrderMinimumAantalTeBestellen: 0,
12
+ singleOrderBesteleenheid: ''
13
+ })).toEqual({
14
+ minimumQuantity: null,
15
+ orderUnit: null
16
+ });
17
+ });
18
+ it('reads the rules off the article', () => {
19
+ expect((0, _singleOrderArticleRules.resolveSingleOrderArticleRules)({
20
+ singleOrderMinimumAantalTeBestellen: '4',
21
+ singleOrderBesteleenheid: 2
22
+ })).toEqual({
23
+ minimumQuantity: 4,
24
+ orderUnit: 2
25
+ });
26
+ });
27
+ it('describes only the filled rules', () => {
28
+ expect((0, _singleOrderArticleRules.describeSingleOrderArticleRules)({
29
+ minimumQuantity: null,
30
+ orderUnit: null
31
+ })).toEqual([]);
32
+ expect((0, _singleOrderArticleRules.describeSingleOrderArticleRules)({
33
+ minimumQuantity: 4,
34
+ orderUnit: null
35
+ })).toEqual(['Minimaal aantal te bestellen: 4']);
36
+ expect((0, _singleOrderArticleRules.describeSingleOrderArticleRules)({
37
+ minimumQuantity: 4,
38
+ orderUnit: 2
39
+ })).toEqual(['Besteleenheid: 2', 'Minimaal aantal te bestellen: 4']);
40
+ });
41
+ it('accepts any quantity for an unconstrained article', () => {
42
+ expect((0, _singleOrderArticleRules.getSingleOrderQuantityError)(3, (0, _singleOrderArticleRules.resolveSingleOrderArticleRules)({}))).toBeNull();
43
+ });
44
+ it('rejects a quantity below the minimum', () => {
45
+ expect((0, _singleOrderArticleRules.getSingleOrderQuantityError)(2, {
46
+ minimumQuantity: 4,
47
+ orderUnit: null
48
+ })).toBe('Minimaal 4 stuks bestellen.');
49
+ });
50
+ it('rejects a quantity that is not a multiple of the order unit', () => {
51
+ expect((0, _singleOrderArticleRules.getSingleOrderQuantityError)(5, {
52
+ minimumQuantity: null,
53
+ orderUnit: 2
54
+ })).toBe('Alleen te bestellen per 2 stuks.');
55
+ expect((0, _singleOrderArticleRules.getSingleOrderQuantityError)(6, {
56
+ minimumQuantity: 4,
57
+ orderUnit: 2
58
+ })).toBeNull();
59
+ });
60
+ it('does not flag an article that is not in the cart', () => {
61
+ expect((0, _singleOrderArticleRules.getSingleOrderQuantityError)(0, {
62
+ minimumQuantity: 4,
63
+ orderUnit: 2
64
+ })).toBeNull();
65
+ });
66
+ it('flags a cart holding an article with an invalid quantity', () => {
67
+ const validLine = {
68
+ articleNumber: 'A',
69
+ name: 'Kabelset',
70
+ quantity: 2
71
+ };
72
+ const invalidLine = {
73
+ articleNumber: 'B',
74
+ name: 'Kogelstang',
75
+ quantity: 1,
76
+ singleOrderMinimumAantalTeBestellen: 4
77
+ };
78
+ expect((0, _singleOrderArticleRules.hasSingleOrderQuantityErrors)([validLine, invalidLine])).toBe(true);
79
+ expect((0, _singleOrderArticleRules.hasSingleOrderQuantityErrors)([validLine])).toBe(false);
80
+ expect((0, _singleOrderArticleRules.hasSingleOrderQuantityErrors)([])).toBe(false);
81
+ });
82
+ });
@@ -9,12 +9,11 @@ require("core-js/modules/esnext.iterator.filter.js");
9
9
  require("core-js/modules/esnext.iterator.flat-map.js");
10
10
  require("core-js/modules/esnext.iterator.for-each.js");
11
11
  require("core-js/modules/esnext.iterator.map.js");
12
- require("core-js/modules/esnext.iterator.some.js");
13
12
  require("core-js/modules/web.dom-collections.iterator.js");
14
13
  Object.defineProperty(exports, "__esModule", {
15
14
  value: true
16
15
  });
17
- exports.validateCartInput = exports.shouldSelectBoardComputerUpdate = exports.shouldRequestUnfilteredProductsForCart = exports.resolveSingleOrderArticlePrices = exports.parseProductSpecifications = exports.getEffectiveBoardComputerSelection = exports.formatHyperDescription = exports.extractProductSpecsArray = exports.checkIfTowbarIsRecommended = exports.buildCartInput = void 0;
16
+ exports.validateCartInput = exports.shouldSelectBoardComputerUpdate = exports.resolveSingleOrderArticlePrices = exports.parseProductSpecifications = exports.getTowbarInfoTitle = exports.getEffectiveBoardComputerSelection = exports.getDetachableTowbarOrientation = exports.formatHyperDescription = exports.extractProductSpecsArray = exports.checkIfTowbarIsRecommended = exports.buildCartInput = void 0;
18
17
  require("core-js/modules/es.array.flat-map.js");
19
18
  require("core-js/modules/es.array.includes.js");
20
19
  require("core-js/modules/es.array.unscopables.flat-map.js");
@@ -24,7 +23,6 @@ require("core-js/modules/esnext.iterator.filter.js");
24
23
  require("core-js/modules/esnext.iterator.flat-map.js");
25
24
  require("core-js/modules/esnext.iterator.for-each.js");
26
25
  require("core-js/modules/esnext.iterator.map.js");
27
- require("core-js/modules/esnext.iterator.some.js");
28
26
  require("core-js/modules/web.dom-collections.iterator.js");
29
27
  var _constants__ = require("../__constants__");
30
28
  function ownKeys(e, r) {
@@ -98,16 +96,6 @@ const validateCartInput = exports.validateCartInput = function validateCartInput
98
96
  }
99
97
  return;
100
98
  };
101
-
102
- /**
103
- * @description Cart overviews resolve selected articles from the order article lists.
104
- * If the user selected an article that only appears with skipFilters enabled,
105
- * those read-only lookups must request the unfiltered product list too.
106
- */
107
- const shouldRequestUnfilteredProductsForCart = exports.shouldRequestUnfilteredProductsForCart = function shouldRequestUnfilteredProductsForCart() {
108
- let cart = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
109
- return Array.isArray(cart) && cart.some(lineItem => (lineItem === null || lineItem === void 0 ? void 0 : lineItem.articleNumber) != null);
110
- };
111
99
  const getEffectiveBoardComputerSelection = exports.getEffectiveBoardComputerSelection = function getEffectiveBoardComputerSelection() {
112
100
  var _ref3;
113
101
  let {
@@ -227,11 +215,39 @@ const checkIfTowbarIsRecommended = _ref8 => {
227
215
  } = _ref8;
228
216
  return category === _constants__.PRODUCT_CATEGORIES.TOWBAR && ((product === null || product === void 0 ? void 0 : product.subgroupName.toLowerCase().includes('afneembaar')) || (product === null || product === void 0 || (_product$subgroupName = product.subgroupName) === null || _product$subgroupName === void 0 ? void 0 : _product$subgroupName.toLowerCase().includes('afneembare')));
229
217
  };
218
+ exports.checkIfTowbarIsRecommended = checkIfTowbarIsRecommended;
219
+ const getTowbarInfoTitle = title => {
220
+ const lower = (title || '').toLowerCase();
221
+ if (lower.includes('afneemba')) {
222
+ return 'Afneembare trekhaak';
223
+ }
224
+ if (lower.includes('wegklapba') || lower.includes('wegdraai')) {
225
+ return 'Wegklapbare trekhaak';
226
+ }
227
+ if (lower.includes('vast')) {
228
+ return 'Vaste trekhaak';
229
+ }
230
+ return title;
231
+ };
232
+ exports.getTowbarInfoTitle = getTowbarInfoTitle;
233
+ const getDetachableTowbarOrientation = title => {
234
+ const lower = (title || '').toLowerCase();
235
+ if (!lower.includes('afneemba')) {
236
+ return null;
237
+ }
238
+ if (lower.includes('horizontaal')) {
239
+ return 'horizontaal';
240
+ }
241
+ if (lower.includes('verticaal')) {
242
+ return 'verticaal';
243
+ }
244
+ return null;
245
+ };
230
246
 
231
247
  /**
232
248
  * @description Formats the description of a product
233
249
  */
234
- exports.checkIfTowbarIsRecommended = checkIfTowbarIsRecommended;
250
+ exports.getDetachableTowbarOrientation = getDetachableTowbarOrientation;
235
251
  const formatHyperDescription = description => {
236
252
  return description === null || description === void 0 ? void 0 : description.ops.map(phrase => phrase.insert).join('');
237
253
  };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ require("core-js/modules/esnext.iterator.constructor.js");
4
+ require("core-js/modules/esnext.iterator.filter.js");
5
+ require("core-js/modules/esnext.iterator.some.js");
6
+ Object.defineProperty(exports, "__esModule", {
7
+ value: true
8
+ });
9
+ exports.resolveSingleOrderArticleRules = exports.hasSingleOrderQuantityErrors = exports.getSingleOrderQuantityError = exports.describeSingleOrderArticleRules = void 0;
10
+ require("core-js/modules/esnext.iterator.constructor.js");
11
+ require("core-js/modules/esnext.iterator.filter.js");
12
+ require("core-js/modules/esnext.iterator.some.js");
13
+ /**
14
+ * Hyper can constrain how an article is ordered in a single order: a minimum
15
+ * quantity and/or an order unit the quantity must be a multiple of. Articles
16
+ * without either field order without restrictions.
17
+ */
18
+
19
+ const toPositiveNumber = value => {
20
+ const number = Number(value);
21
+ return number > 0 ? number : null;
22
+ };
23
+ const resolveSingleOrderArticleRules = article => ({
24
+ minimumQuantity: toPositiveNumber(article.singleOrderMinimumAantalTeBestellen),
25
+ orderUnit: toPositiveNumber(article.singleOrderBesteleenheid)
26
+ });
27
+ exports.resolveSingleOrderArticleRules = resolveSingleOrderArticleRules;
28
+ const describeSingleOrderArticleRules = _ref => {
29
+ let {
30
+ minimumQuantity,
31
+ orderUnit
32
+ } = _ref;
33
+ return [orderUnit && "Besteleenheid: ".concat(orderUnit), minimumQuantity && "Minimaal aantal te bestellen: ".concat(minimumQuantity)].filter(Boolean);
34
+ };
35
+ exports.describeSingleOrderArticleRules = describeSingleOrderArticleRules;
36
+ const getSingleOrderQuantityError = (quantity, _ref2) => {
37
+ let {
38
+ minimumQuantity,
39
+ orderUnit
40
+ } = _ref2;
41
+ if (!quantity) {
42
+ return null;
43
+ }
44
+ if (minimumQuantity && quantity < minimumQuantity) {
45
+ return "Minimaal ".concat(minimumQuantity, " stuks bestellen.");
46
+ }
47
+ if (orderUnit && quantity % orderUnit !== 0) {
48
+ return "Alleen te bestellen per ".concat(orderUnit, " stuks.");
49
+ }
50
+ return null;
51
+ };
52
+ exports.getSingleOrderQuantityError = getSingleOrderQuantityError;
53
+ const hasSingleOrderQuantityErrors = articles => articles.some(article => getSingleOrderQuantityError(article.quantity, resolveSingleOrderArticleRules(article)));
54
+ exports.hasSingleOrderQuantityErrors = hasSingleOrderQuantityErrors;
@@ -442,8 +442,11 @@ const formatGenericArticle = article => {
442
442
  return "".concat(label, ": ").concat(value);
443
443
  }).join(' \n ');
444
444
  return _objectSpread(_objectSpread({}, article), {}, {
445
- name: article.name,
445
+ name: article.singleOrderArtikelNaam || article.name,
446
446
  description: null,
447
+ // Separate from `description`, which formatModelArticle fills with
448
+ // unrelated copy.
449
+ singleOrderDescription: article.singleOrderArtikelOmschrijving || null,
447
450
  subTitle: null,
448
451
  specifications: specification === '' ? null : specification,
449
452
  image: article !== null && article !== void 0 && article.computedImages ? (0, _image.getImageFromHyper)({
@@ -22,6 +22,12 @@ Card Horizontal
22
22
  @extend .d-flex, .justify-content-between;
23
23
  }
24
24
 
25
+ .card-title__orientation {
26
+ font-size: 0.85rem;
27
+ font-weight: 700;
28
+ margin-bottom: 0.5rem;
29
+ }
30
+
25
31
  .card-img__best-choice {
26
32
  z-index: 2;
27
33
  width: 50%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thm-p3-configurator",
3
- "version": "0.0.441",
3
+ "version": "0.0.443",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "author": "EnoRm.",