tripstorekrabi-shared 1.25.0 → 1.25.2

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 (3) hide show
  1. package/booking.js +28 -14
  2. package/package.json +1 -1
  3. package/price.js +106 -73
package/booking.js CHANGED
@@ -177,20 +177,34 @@ export function addBookingFromInvoice(
177
177
  }
178
178
  }
179
179
 
180
- export function getOperatorSignature(agent, operatorName, contact) {
181
- let signature =
182
- '<br><br>Warm regards,<br><br>' +
183
- (agent ? agent + ' - ' : '') + operatorName;
184
- signature += `<br>WhatsApp chat: <a href='https://wa.me/${contact.respondNumber.replace(
185
- /\D/g,
186
- ''
187
- )}'>${contact.respondNumber}</a>`;
188
- signature += `<br>WhatsApp call: <a href='https://wa.me/${contact.whatsappNumber.replace(
189
- /\D/g,
190
- ''
191
- )}'>${contact.whatsappNumber}</a>`;
192
- signature +=
193
- "<br>Telegram chat: <a href='https://t.me/tripstorekrabi_bot'>@tripstorekrabi_bot</a>";
180
+ export function getOperatorSignature(operatorName, contact) {
181
+ let signature = `<br><br>Warm regards,<br><br><strong>${operatorName}</strong><br>`;
182
+
183
+ if (contact && contact.phoneNumber) {
184
+ const cleanPhone = contact.phoneNumber.replace(/[^\d+]/g, '');
185
+ signature += `<br>📞 Phone: <a href="tel:${cleanPhone}">Call our office</a>`;
186
+ }
187
+
188
+ if (contact && contact.email) {
189
+ signature += `<br>✉️ Email: <a href="mailto:${contact.email}">Send email</a>`;
190
+ }
191
+
192
+ if (contact && contact.respondNumber) {
193
+ const cleanRespond = contact.respondNumber.replace(/\D/g, '');
194
+ signature += `<br>💬 WhatsApp: <a href="https://wa.me/${cleanRespond}">Chat on WhatsApp</a>`;
195
+ }
196
+
197
+ if (contact && contact.whatsappNumber) {
198
+ const cleanWhatsapp = contact.whatsappNumber.replace(/\D/g, '');
199
+ signature += `<br>💬 WhatsApp: <a href="https://wa.me/${cleanWhatsapp}">Call on WhatsApp</a>`;
200
+ }
201
+
202
+ signature += `<br>✈️ Telegram: <a href="https://t.me/tripstorekrabi_bot">Chat in Telegram</a>`;
203
+
204
+ signature += `<br>🟢 LINE: <a href="line://ti/p/@cjs7975w">Chat on LINE</a>`;
205
+
206
+ signature += `<br>📘 Facebook: <a href="https://m.me/tripstorekrabi">Message on Facebook</a>`;
207
+
194
208
  return signature;
195
209
  }
196
210
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tripstorekrabi-shared",
3
- "version": "1.25.0",
3
+ "version": "1.25.2",
4
4
  "description": "Shared library for Trip Store Krabi",
5
5
  "author": "Stefan Sommer <tonprince@gmail.com>",
6
6
  "repository": {
package/price.js CHANGED
@@ -96,9 +96,9 @@ export function findVolume(people, context) {
96
96
 
97
97
  if (!volumes || volumes.length === 0) return undefined;
98
98
 
99
- const sortedVolumes = [...volumes].sort((v1, v2) => v1.max - v2.max);
99
+ const sortedVolumes = [...volumes].sort((v1, v2) => (v1.max ?? 0) - (v2.max ?? 0));
100
100
 
101
- return sortedVolumes.find(v => total <= v.max) || sortedVolumes[sortedVolumes.length - 1];
101
+ return sortedVolumes.find(v => total <= (v.max ?? MAX_PAX)) || sortedVolumes[sortedVolumes.length - 1];
102
102
  }
103
103
 
104
104
  export function countMinPeople(pricing, bookingDate = dayjs(), departureDate = null) {
@@ -114,22 +114,27 @@ export function countMaxPeople(pricing, bookingDate = dayjs(), departureDate = n
114
114
  }
115
115
 
116
116
  export function calculateProductSellingPrice(booking, pricing) {
117
- return booking.customerPaymentStatus === "Refunded" ? 0 : calculateProductPrice(booking, pricing, { mode: SELLING_MODE });
117
+ return booking?.customerPaymentStatus === "Refunded" ? 0 : calculateProductPrice(booking, pricing, { mode: SELLING_MODE });
118
118
  }
119
119
 
120
120
  export function calculateProductPartnerNetPrice(booking, pricing) {
121
- return booking.customerPaymentStatus === "Refunded" ? 0 : calculateProductPrice(booking, pricing, { mode: PARTNER_MODE });
121
+ return booking?.customerPaymentStatus === "Refunded" ? 0 : calculateProductPrice(booking, pricing, { mode: PARTNER_MODE });
122
122
  }
123
123
 
124
124
  export function calculateProductOperatorPrice(booking, pricing) {
125
- return booking.operatorPaymentStatus === "No Charge" ? 0 : calculateProductPrice(booking, pricing, { mode: OPERATOR_MODE });
125
+ return booking?.operatorPaymentStatus === "No Charge" ? 0 : calculateProductPrice(booking, pricing, { mode: OPERATOR_MODE });
126
126
  }
127
127
 
128
128
  function calculateSellingPrice(commissionRate = 0, price) {
129
- return Math.floor((price * 100) / (100 - commissionRate));
129
+ if (price == null) return 0;
130
+ const comm = Number(commissionRate) || 0;
131
+ const factor = 100 - comm;
132
+ if (factor <= 0) return price;
133
+ return Math.floor((price * 100) / factor);
130
134
  }
131
135
 
132
136
  export function calculateInflatedSellingPrice(price, config, forceInflation = false) {
137
+ if (price == null) return 0;
133
138
  if (!config || (!config.inflatedPrices && !forceInflation)) return price;
134
139
 
135
140
  const discountRate = Number(config.discount) || 0;
@@ -146,6 +151,8 @@ export function calculateInflatedSellingPrice(price, config, forceInflation = fa
146
151
  }
147
152
 
148
153
  function calculateProductPrice(booking, pricing, options = { mode: SELLING_MODE }) {
154
+ if (!booking) return 0;
155
+
149
156
  const context = resolvePricingContext(pricing, booking.date, booking.departureDate);
150
157
 
151
158
  if (!context || !context.volumes || context.volumes.length === 0) {
@@ -155,12 +162,14 @@ function calculateProductPrice(booking, pricing, options = { mode: SELLING_MODE
155
162
 
156
163
  const volume = findVolume(booking, context);
157
164
  if (!volume) {
158
- console.error(`No volume price found for ${booking.adults + booking.children} people`);
165
+ console.error(`No volume price found for ${(booking.adults ?? 0) + (booking.children ?? 0)} people`);
159
166
  return 0;
160
167
  }
161
168
 
162
169
  let total = 0;
163
170
  const bookingSourceKey = getBookingSourceKey(booking.otaPartnerRef);
171
+ const adults = booking.adults ?? 0;
172
+ const children = booking.children ?? 0;
164
173
 
165
174
  if (context.type === "person") {
166
175
  if (volume[bookingSourceKey]) {
@@ -168,23 +177,23 @@ function calculateProductPrice(booking, pricing, options = { mode: SELLING_MODE
168
177
 
169
178
  if (options.mode === SELLING_MODE) {
170
179
  const commRate = context.commissionRates?.[bookingSourceKey] || 0;
171
- const finalPriceAdult = calculateInflatedSellingPrice(calculateSellingPrice(commRate, customPricing.adult.partnerNetPrice), customPricing);
180
+ const finalPriceAdult = calculateInflatedSellingPrice(calculateSellingPrice(commRate, customPricing.adult?.partnerNetPrice ?? 0), customPricing);
172
181
 
173
182
  let finalPriceChild = 0;
174
- if (context.ageBands?.child?.active && volume.child && booking.children) {
175
- finalPriceChild = calculateInflatedSellingPrice(calculateSellingPrice(commRate, customPricing.child.partnerNetPrice), customPricing);
183
+ if (context.ageBands?.child?.active && volume.child && children) {
184
+ finalPriceChild = calculateInflatedSellingPrice(calculateSellingPrice(commRate, customPricing.child?.partnerNetPrice ?? 0), customPricing);
176
185
  }
177
- total = (booking.adults * finalPriceAdult) + (booking.children ? booking.children * finalPriceChild : 0);
186
+ total = (adults * finalPriceAdult) + (children ? children * finalPriceChild : 0);
178
187
  } else if (options.mode === PARTNER_MODE) {
179
- total = (booking.adults * customPricing.adult.partnerNetPrice) + (context.ageBands?.child?.active && volume.child && booking.children ? booking.children * customPricing.child.partnerNetPrice : 0);
188
+ total = (adults * (customPricing.adult?.partnerNetPrice ?? 0)) + (context.ageBands?.child?.active && volume.child && children ? children * (customPricing.child?.partnerNetPrice ?? 0) : 0);
180
189
  } else if (options.mode === OPERATOR_MODE) {
181
- total = (booking.adults * (customPricing.adult.operatorNetPrice ?? volume.adult.operatorNetPrice)) + (context.ageBands?.child?.active && volume.child && booking.children ? booking.children * (customPricing.child.operatorNetPrice ?? volume.child.operatorNetPrice) : 0);
190
+ total = (adults * (customPricing.adult?.operatorNetPrice ?? volume.adult?.operatorNetPrice ?? 0)) + (context.ageBands?.child?.active && volume.child && children ? children * (customPricing.child?.operatorNetPrice ?? volume.child?.operatorNetPrice ?? 0) : 0);
182
191
  }
183
192
  } else {
184
193
  if (options.mode === SELLING_MODE || options.mode === PARTNER_MODE) {
185
- total = (booking.adults * volume.adult.sellingPrice) + (context.ageBands?.child?.active && volume.child && booking.children ? booking.children * volume.child.sellingPrice : 0);
194
+ total = (adults * (volume.adult?.sellingPrice ?? 0)) + (context.ageBands?.child?.active && volume.child && children ? children * (volume.child?.sellingPrice ?? 0) : 0);
186
195
  } else if (options.mode === OPERATOR_MODE) {
187
- total = (booking.adults * volume.adult.operatorNetPrice) + (context.ageBands?.child?.active && volume.child && booking.children ? booking.children * volume.child.operatorNetPrice : 0);
196
+ total = (adults * (volume.adult?.operatorNetPrice ?? 0)) + (context.ageBands?.child?.active && volume.child && children ? children * (volume.child?.operatorNetPrice ?? 0) : 0);
188
197
  }
189
198
  }
190
199
  } else if (context.type === "group") {
@@ -193,17 +202,17 @@ function calculateProductPrice(booking, pricing, options = { mode: SELLING_MODE
193
202
 
194
203
  if (options.mode === SELLING_MODE) {
195
204
  const commRate = context.commissionRates?.[bookingSourceKey] || 0;
196
- total = calculateInflatedSellingPrice(calculateSellingPrice(commRate, customPricing.partnerNetPrice), customPricing);
205
+ total = calculateInflatedSellingPrice(calculateSellingPrice(commRate, customPricing.partnerNetPrice ?? 0), customPricing);
197
206
  } else if (options.mode === PARTNER_MODE) {
198
- total = customPricing.partnerNetPrice;
207
+ total = customPricing.partnerNetPrice ?? 0;
199
208
  } else if (options.mode === OPERATOR_MODE) {
200
- total = customPricing.operatorNetPrice ?? volume.operatorNetPrice;
209
+ total = customPricing.operatorNetPrice ?? volume.operatorNetPrice ?? 0;
201
210
  }
202
211
  } else {
203
212
  if (options.mode === SELLING_MODE || options.mode === PARTNER_MODE) {
204
- total = volume.sellingPrice;
213
+ total = volume.sellingPrice ?? 0;
205
214
  } else if (options.mode === OPERATOR_MODE) {
206
- total = volume.operatorNetPrice;
215
+ total = volume.operatorNetPrice ?? 0;
207
216
  }
208
217
  }
209
218
  } else {
@@ -214,43 +223,44 @@ function calculateProductPrice(booking, pricing, options = { mode: SELLING_MODE
214
223
  }
215
224
 
216
225
  export function getTotalAmountToBeReceived(booking, options = {}) {
217
- let filter = (item) => (!options.filter || options.filter(item)) && !item.disabled && item.paymentMethod !== PAY_ON_TOUR;
218
- return booking.customerPaymentStatus === "Refunded" ? 0 : booking.partnerNetPrice + calculateExtraPayments(booking, { ...options, mode: SELLING_MODE, filter })
226
+ let filter = (item) => (!options.filter || options.filter(item)) && !item?.disabled && item?.paymentMethod !== PAY_ON_TOUR;
227
+ return booking?.customerPaymentStatus === "Refunded" ? 0 : (booking?.partnerNetPrice ?? 0) + calculateExtraPayments(booking, { ...options, mode: SELLING_MODE, filter });
219
228
  }
220
229
 
221
230
  export function calculatePartnerNetPrice(booking, product) {
222
231
  return getExtraPaymentsPaidWithPartner(booking, product).reduce(
223
- (prev, curr) => (prev ?? 0) + curr.price,
224
- calculateProductPartnerNetPrice(booking, product.pricing)
232
+ (prev, curr) => (prev ?? 0) + (curr?.price ?? 0),
233
+ calculateProductPartnerNetPrice(booking, product?.pricing)
225
234
  );
226
235
  }
227
236
 
228
237
  export function calculateTotalSellingPrice(booking, product) {
229
- return booking.customerPaymentStatus === "Refunded" ? 0 : calculateTotalPrice(booking, product, { mode: SELLING_MODE });
238
+ return booking?.customerPaymentStatus === "Refunded" ? 0 : calculateTotalPrice(booking, product, { mode: SELLING_MODE });
230
239
  }
231
240
 
232
241
  export function calculateTotalPartnerNetPrice(booking, product) {
233
- return booking.customerPaymentStatus === "Refunded" ? 0 : calculateTotalPrice(booking, product, { mode: PARTNER_MODE });
242
+ return booking?.customerPaymentStatus === "Refunded" ? 0 : calculateTotalPrice(booking, product, { mode: PARTNER_MODE });
234
243
  }
235
244
 
236
245
  export function calculateTotalOperatorPrice(booking, product) {
237
- return booking.operatorPaymentStatus === "No Charge" ? 0 : calculateTotalPrice(booking, product, { mode: OPERATOR_MODE });
246
+ return booking?.operatorPaymentStatus === "No Charge" ? 0 : calculateTotalPrice(booking, product, { mode: OPERATOR_MODE });
238
247
  }
239
248
 
240
249
  function calculateTotalPrice(booking, product, options = { mode: SELLING_MODE }) {
241
- let total = calculateProductPrice(booking, product.pricing, options) ?? 0;
242
- total += calculateStartEndPointPrice(booking, findStartingPoint(product, booking.startingPointId, false), options);
243
- total += calculateStartEndPointPrice(booking, findEndPoint(product, booking.endPointId, false), options);
250
+ let total = calculateProductPrice(booking, product?.pricing, options) ?? 0;
251
+ total += calculateStartEndPointPrice(booking, findStartingPoint(product, booking?.startingPointId, false), options);
252
+ total += calculateStartEndPointPrice(booking, findEndPoint(product, booking?.endPointId, false), options);
244
253
  total += calculateNationalParkFee(booking, product);
245
254
  total += calculateExtraPayments(booking, {
246
255
  ...options,
247
256
  filter: (item) => {
248
- if (item.disabled === true) return false;
249
- if (item.paymentMethod === PAY_ON_TOUR) return true;
250
- if (item.paymentMethod === PAY_BEFORE && item.category === OTHER_EXTRA_PAYMENT) return true;
257
+ if (item?.disabled === true) return false;
258
+ if (item?.paymentMethod === PAY_ON_TOUR) return true;
259
+ if (item?.paymentMethod === PAY_BEFORE && item?.category === OTHER_EXTRA_PAYMENT) return true;
260
+ return false;
251
261
  },
252
262
  getPrice: (item, options) => {
253
- if (item.paymentMethod === PAY_ON_TOUR) {
263
+ if (item?.paymentMethod === PAY_ON_TOUR) {
254
264
  return item.category !== OTHER_EXTRA_PAYMENT ? -getExtraPaymentPrice(item, options) : 0;
255
265
  }
256
266
  return getExtraPaymentPrice(item, options);
@@ -261,18 +271,21 @@ function calculateTotalPrice(booking, product, options = { mode: SELLING_MODE })
261
271
  }
262
272
 
263
273
  export function calculateNationalParkFee(booking, product) {
264
- return booking.nationalParkFee === ALREADY_PAID && product.nationalPark
265
- ? (product.nationalPark.pricing.adult * booking.adults) + (product.nationalPark.pricing.child * booking.children)
274
+ return booking?.nationalParkFee === ALREADY_PAID && product?.nationalPark?.pricing
275
+ ? ((product.nationalPark.pricing.adult ?? 0) * (booking?.adults ?? 0)) + ((product.nationalPark.pricing.child ?? 0) * (booking?.children ?? 0))
266
276
  : 0;
267
277
  }
268
278
 
269
279
  function getExtraPaymentPrice(item, options) {
280
+ if (!item) return 0;
270
281
  switch (options.mode) {
271
282
  case SELLING_MODE:
272
283
  case PARTNER_MODE:
273
- return item.receivedAmount ? item.receivedAmount : item.price;
284
+ return item.receivedAmount ? item.receivedAmount : (item.price ?? 0);
274
285
  case OPERATOR_MODE:
275
- return item.operatorPrice;
286
+ return item.operatorPrice ?? 0;
287
+ default:
288
+ return 0;
276
289
  }
277
290
  }
278
291
 
@@ -285,14 +298,14 @@ export function calculateTotalExtraPaymentsOperatorPrice(booking) {
285
298
  }
286
299
 
287
300
  function calculateExtraPayments(booking, options = { mode: SELLING_MODE }) {
288
- let filter = options.filter ?? ((item) => !item.disabled);
301
+ let filter = options.filter ?? ((item) => !item?.disabled);
289
302
  let getPrice = options.getPrice ?? getExtraPaymentPrice;
290
303
  let total = 0;
291
304
 
292
- for (const item of Object.values(booking.extraPayments ?? {}).filter(filter)) {
305
+ for (const item of Object.values(booking?.extraPayments ?? {}).filter(filter)) {
293
306
  total += getPrice(item, options);
294
307
  if (item.paymentMethod === PAY_ON_TOUR && options.mode === OPERATOR_MODE) {
295
- let ourCommission = item.price - item.operatorPrice;
308
+ let ourCommission = (item.price ?? 0) - (item.operatorPrice ?? 0);
296
309
  if (ourCommission > 0) total -= ourCommission;
297
310
  }
298
311
  }
@@ -300,53 +313,57 @@ function calculateExtraPayments(booking, options = { mode: SELLING_MODE }) {
300
313
  }
301
314
 
302
315
  export function getExtraPaymentsPaidWithPartner(booking, product) {
303
- let startingPoint = findStartingPoint(product, booking.startingPointId);
304
- let endPoint = findEndPoint(product, booking.endPointId);
316
+ let startingPoint = findStartingPoint(product, booking?.startingPointId);
317
+ let endPoint = findEndPoint(product, booking?.endPointId);
305
318
  let addOnDefs = {};
306
319
 
307
- (product.addOns ?? []).forEach((addOn) => {
308
- addOnDefs[addOn.id] = addOn;
320
+ (product?.addOns ?? []).forEach((addOn) => {
321
+ if (addOn?.id) addOnDefs[addOn.id] = addOn;
309
322
  });
310
323
 
311
- let addOns = Object.entries(booking.addOns ?? {}).filter(([id]) => addOnDefs[id] != null).map(([id, addOn]) => {
324
+ let addOns = Object.entries(booking?.addOns ?? {}).filter(([id]) => addOnDefs[id] != null).map(([id, addOn]) => {
312
325
  let addOnDef = addOnDefs[id];
326
+ const adults = addOn?.adults ?? 0;
327
+ const children = addOn?.children ?? 0;
313
328
  return {
314
329
  category: ADD_ON_EXTRA_PAYMENT,
315
- description: addOnDef.title,
316
- price: (addOn.adults * addOnDef.pricing.adult.sellingPrice) + ((addOn.children ?? 0) * (addOnDef.pricing.child?.sellingPrice ?? 0)),
317
- operatorPrice: (addOn.adults * addOnDef.pricing.adult.operatorNetPrice) + ((addOn.children ?? 0) * (addOnDef.pricing.child?.operatorNetPrice ?? 0))
318
- }
330
+ description: addOnDef?.title ?? 'Add-on',
331
+ price: (adults * (addOnDef?.pricing?.adult?.sellingPrice ?? 0)) + (children * (addOnDef?.pricing?.child?.sellingPrice ?? 0)),
332
+ operatorPrice: (adults * (addOnDef?.pricing?.adult?.operatorNetPrice ?? 0)) + (children * (addOnDef?.pricing?.child?.operatorNetPrice ?? 0))
333
+ };
319
334
  });
320
335
 
321
336
  return [
322
- { category: STARTING_POINT_EXTRA_PAYMENT, description: startingPoint.name, price: calculateStartEndPointSellingPrice(booking, startingPoint), operatorPrice: calculateStartEndPointOperatorPrice(booking, startingPoint) },
323
- { category: END_POINT_EXTRA_PAYMENT, description: endPoint.name, price: calculateStartEndPointSellingPrice(booking, endPoint), operatorPrice: calculateStartEndPointOperatorPrice(booking, endPoint) },
324
- { category: NATIONAL_PARK_FEE_EXTRA_PAYMENT, description: `Entrance fee to ${product.nationalPark?.title}`, price: calculateNationalParkFee(booking, product), operatorPrice: calculateNationalParkFee(booking, product) },
337
+ { category: STARTING_POINT_EXTRA_PAYMENT, description: startingPoint?.name ?? 'Starting Point', price: calculateStartEndPointSellingPrice(booking, startingPoint), operatorPrice: calculateStartEndPointOperatorPrice(booking, startingPoint) },
338
+ { category: END_POINT_EXTRA_PAYMENT, description: endPoint?.name ?? 'End Point', price: calculateStartEndPointSellingPrice(booking, endPoint), operatorPrice: calculateStartEndPointOperatorPrice(booking, endPoint) },
339
+ { category: NATIONAL_PARK_FEE_EXTRA_PAYMENT, description: `Entrance fee to ${product?.nationalPark?.title ?? 'National Park'}`, price: calculateNationalParkFee(booking, product), operatorPrice: calculateNationalParkFee(booking, product) },
325
340
  ...addOns
326
341
  ].filter(
327
342
  (item) => (item.price > 0 || item.operatorPrice > 0) &&
328
- !Object.values(booking.extraPayments ?? {}).find(ep => ep.category === item.category && !ep.disabled)
343
+ !Object.values(booking?.extraPayments ?? {}).find(ep => ep.category === item.category && !ep.disabled)
329
344
  ).map((item) => {
330
- if (booking.customerPaymentStatus === "Refunded") item.price = 0;
331
- if (booking.operatorPaymentStatus === "No Charge") item.operatorPrice = 0;
345
+ if (booking?.customerPaymentStatus === "Refunded") item.price = 0;
346
+ if (booking?.operatorPaymentStatus === "No Charge") item.operatorPrice = 0;
332
347
  return item;
333
348
  });
334
349
  }
335
350
 
336
351
  export function getActiveExtraPayments(extraPayments) {
337
- return [...extraPayments]
338
- .filter(([id, item]) => !item.disabled)
339
- .sort(([id1, item1], [id2, item2]) => item1.description.localeCompare(item2.description));
352
+ // Respecting the rule: extraPayments passed from outside is always an array of entries
353
+ return [...(extraPayments ?? [])]
354
+ .filter(([id, item]) => !item?.disabled)
355
+ .sort(([id1, item1], [id2, item2]) => (item1?.description || '').localeCompare(item2?.description || ''));
340
356
  }
341
357
 
342
358
  export function getExtraPaymentsOperatorPrice(extraPayments) {
343
359
  return getActiveExtraPayments(extraPayments).reduce((prev, curr) => {
344
360
  let item = curr[1];
361
+ if (!item) return prev;
345
362
  if (item.paymentMethod === PAY_ON_TOUR) {
346
- let ourCommission = item.price - item.operatorPrice;
363
+ let ourCommission = (item.price ?? 0) - (item.operatorPrice ?? 0);
347
364
  return prev + (ourCommission > 0 ? -ourCommission : 0);
348
365
  }
349
- return prev + item.operatorPrice;
366
+ return prev + (item.operatorPrice ?? 0);
350
367
  }, 0);
351
368
  }
352
369
 
@@ -354,11 +371,20 @@ export function calculateAddOnsPrice(booking, product, options = { mode: SELLING
354
371
  let total = 0;
355
372
  let isSelling = [SELLING_MODE, PARTNER_MODE].includes(options.mode);
356
373
 
357
- for (const [id, addOn] of Object.entries(booking.addOns ?? {})) {
358
- let addOnDef = findAddOn(id, product.addOns ?? []);
359
- if (addOnDef) {
360
- total += (addOn.adults * (isSelling ? addOnDef.pricing.adult.sellingPrice : addOnDef.pricing.adult.operatorNetPrice)) +
361
- (addOn.children > 0 ? addOn.children * (isSelling ? addOnDef.pricing.child.sellingPrice : addOnDef.pricing.child.operatorNetPrice) : 0);
374
+ for (const [id, addOn] of Object.entries(booking?.addOns ?? {})) {
375
+ let addOnDef = findAddOn(id, product?.addOns ?? []);
376
+ if (addOnDef && addOnDef.pricing) {
377
+ const adultPrice = isSelling
378
+ ? (addOnDef.pricing?.adult?.sellingPrice ?? 0)
379
+ : (addOnDef.pricing?.adult?.operatorNetPrice ?? 0);
380
+ const childPrice = isSelling
381
+ ? (addOnDef.pricing?.child?.sellingPrice ?? 0)
382
+ : (addOnDef.pricing?.child?.operatorNetPrice ?? 0);
383
+
384
+ const adults = addOn?.adults ?? 0;
385
+ const children = addOn?.children ?? 0;
386
+
387
+ total += (adults * adultPrice) + (children * childPrice);
362
388
  }
363
389
  }
364
390
  return total;
@@ -381,30 +407,37 @@ function calculateStartEndPointPrice(people, startEndPoint, options = { mode: SE
381
407
  let isSelling = [SELLING_MODE, PARTNER_MODE].includes(options.mode);
382
408
 
383
409
  if (pricing) {
384
- const priceAmount = isSelling ? (pricing.sellingPrice ?? pricing.amount) : pricing.operatorNetPrice;
410
+ const priceAmount = isSelling ? (pricing.sellingPrice ?? pricing.amount ?? 0) : (pricing.operatorNetPrice ?? 0);
411
+ const adults = people?.adults ?? 0;
412
+ const children = people?.children ?? 0;
413
+ const pax = adults + children;
414
+
385
415
  if (pricing.type === "group") {
386
- return priceAmount * Math.ceil((people.adults + people.children) / pricing.max);
416
+ const maxPax = pricing.max || 1;
417
+ return priceAmount * Math.ceil(pax / maxPax);
387
418
  } else if (pricing.type === "person") {
388
- return priceAmount * (people.adults + people.children);
419
+ return priceAmount * pax;
389
420
  }
390
421
  }
391
422
  return 0;
392
423
  }
393
424
 
394
425
  export function findStartingPoint(product, startingPointId, useDefault = true) {
426
+ const startingPoints = product?.startingPoints ?? [];
395
427
  return [
396
- ...product.startingPoints,
428
+ ...startingPoints,
397
429
  { id: TELL_HOTEL_LATER_STARTING_POINT.id, name: TELL_HOTEL_LATER_STARTING_POINT.description, value: '' },
398
430
  { id: CUSTOM_MEETING_POINT.id, name: CUSTOM_MEETING_POINT.description, value: '' },
399
- ].find((item) => item.id === startingPointId) ?? (useDefault ? product.startingPoints[0] : null);
431
+ ].find((item) => item?.id === startingPointId) ?? (useDefault ? startingPoints[0] : null);
400
432
  }
401
433
 
402
434
  export function findEndPoint(product, endPointId, useDefault = true) {
403
- return product.endPoints.find((item) => item.id === endPointId) ?? (useDefault ? product.endPoints[0] ?? { name: "" } : null);
435
+ const endPoints = product?.endPoints ?? [];
436
+ return endPoints.find((item) => item?.id === endPointId) ?? (useDefault ? endPoints[0] ?? { name: "" } : null);
404
437
  }
405
438
 
406
439
  export function findAddOn(id, addOns) {
407
- return addOns.find((item) => item.id === id);
440
+ return (addOns ?? []).find((item) => item?.id === id);
408
441
  }
409
442
 
410
443
  export function inRange(expected, actual, boundary = 10) {