PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.20
Booking for Appointments and Events Calendar – Amelia v1.2.20
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / assets / js / common / appointments.js
ameliabooking / v3 / src / assets / js / common Last commit date
appointments.js 1 year ago attendees.js 1 year ago colorManipulation.js 3 years ago customer.js 1 year ago date.js 1 year ago defaultCustomize.js 1 year ago employee.js 1 year ago events.js 1 year ago formFieldsTemplates.js 3 years ago formatting.js 1 year ago helper.js 1 year ago image.js 1 year ago integrationApple.js 1 year ago integrationGoogle.js 1 year ago integrationOutlook.js 1 year ago integrationStripe.js 1 year ago integrationZoom.js 1 year ago licence.js 2 years ago objectAndArrayManipulation.js 3 years ago pricing.js 1 year ago recurring.js 1 year ago responsive.js 1 year ago scrollElements.js 4 years ago settings.js 1 year ago translationsElementPlus.js 1 year ago
appointments.js
1122 lines
1 import {useTimeInSeconds} from "./date";
2 import {useCart, useCartItem} from "../public/cart";
3 import {useSortedDateStrings} from "./helper";
4 import {settings} from "../../../plugins/settings";
5 import {checkLimitPerEmployee, sortForEmployeeSelection} from "./employee";
6 import {useEntityTax, usePercentageAmount, useRoundAmount, useTaxAmount, useTaxedAmount} from "./pricing";
7
8 function useEmployeeService (store, serviceId, employeeId) {
9 let service = store.getters['entities/getService'](serviceId)
10
11 let employeeService = employeeId ? store.getters['entities/getEmployeeService'](employeeId, serviceId) : service
12
13 return Object.assign(
14 {},
15 service,
16 {
17 price: employeeService.price,
18 minCapacity: employeeService.minCapacity,
19 maxCapacity: employeeService.maxCapacity,
20 customPricing: employeeService.customPricing,
21 }
22 )
23 }
24
25 function useAppointmentServicePrice (employeeService, duration) {
26 return employeeService.customPricing.enabled && (duration in employeeService.customPricing.durations)
27 ? employeeService.customPricing.durations[duration].price : employeeService.price
28 }
29
30 function useAppointmentServiceAmount (employeeService, persons, duration) {
31 return useAppointmentServicePrice(employeeService, duration) * (employeeService.aggregatedPrice ? persons : 1)
32 }
33
34 function useAppointmentAmountData (store, item, coupon, couponLimit) {
35 let instantTotalAmount = 0
36
37 let instantTotalServiceAmount = 0
38
39 let instantTotalExtrasAmount = 0
40
41 let instantDiscountAmount = 0
42
43 let instantTaxAmount = 0
44
45 let instantDepositAmount = 0
46
47 let totalAmount = 0
48
49 let totalServiceAmount = 0
50
51 let totalExtrasAmount = 0
52
53 let discountAmount = 0
54
55 let taxAmount = 0
56
57 let depositAmount = 0
58
59 let useFullAmount = store.getters['booking/getPaymentDeposit']
60
61 let service = store.getters['entities/getService'](
62 item.serviceId
63 )
64
65 let applyCoupon = couponLimit > 0 && coupon.servicesIds.indexOf(service.id) !== -1
66
67 let instantPaymentCount = 1
68
69 if (service.recurringPayment) {
70 instantPaymentCount = service.recurringPayment > item.services[item.serviceId].list.length
71 ? item.services[item.serviceId].list.length : service.recurringPayment
72 }
73
74 let appliedCoupon = false
75
76 let servicesPrices = {}
77
78 let prepaidCount = 0
79
80 let totalCount = 0
81
82 item.services[item.serviceId].list.forEach((appointment, index) => {
83 let employeeService = useEmployeeService(store, item.serviceId, appointment.providerId)
84
85 let amountData = useAppointmentBookingAmountData(
86 store,
87 {
88 price: useAppointmentServicePrice(employeeService, appointment.duration),
89 persons: appointment.persons,
90 aggregatedPrice: service.aggregatedPrice,
91 extras: appointment.extras,
92 serviceId: item.serviceId,
93 coupon: applyCoupon && couponLimit > 0 ? coupon : null
94 },
95 true
96 )
97
98 if (applyCoupon && couponLimit > 0) {
99 appliedCoupon = true
100
101 couponLimit--
102 }
103
104 let appointmentTotalAmount = amountData.total
105
106 let appointmentDiscountAmount = amountData.discount
107
108 let appointmentTaxAmount = amountData.tax
109
110 totalExtrasAmount += amountData.total - amountData.bookable
111
112 totalServiceAmount += amountData.bookable
113
114 let appointmentDepositAmount = 0
115
116 let servicePrice = useAppointmentServicePrice(employeeService, appointment.duration)
117
118 servicesPrices[servicePrice] = !(servicePrice in servicesPrices) ? 1 : servicesPrices[servicePrice] + 1
119
120 if (service.depositPayment !== 'disabled' && (useFullAmount ? !service.fullPayment : true)) {
121 switch (service.depositPayment) {
122 case ('fixed'):
123 appointmentDepositAmount = (service.depositPerPerson && service.aggregatedPrice ? appointment.persons : 1) * service.deposit
124
125 break
126
127 case 'percentage':
128 appointmentDepositAmount = useRoundAmount(
129 usePercentageAmount(
130 appointmentTotalAmount - appointmentDiscountAmount + appointmentTaxAmount,
131 service.deposit
132 )
133 )
134
135 break
136 }
137 }
138
139 totalAmount += appointmentTotalAmount
140
141 discountAmount += appointmentDiscountAmount
142
143 taxAmount += appointmentTaxAmount
144
145 depositAmount += appointmentDepositAmount
146
147 totalCount++
148
149 if (index < instantPaymentCount) {
150 instantTotalAmount = totalAmount
151
152 instantTotalServiceAmount = totalServiceAmount
153
154 instantTotalExtrasAmount = totalExtrasAmount
155
156 instantDiscountAmount = discountAmount
157
158 instantTaxAmount = taxAmount
159
160 instantDepositAmount = depositAmount
161
162 prepaidCount++
163 }
164 })
165
166 return {
167 serviceId: service.id,
168 postpaid: {
169 totalAmount: totalAmount - instantTotalAmount,
170 totalServiceAmount: totalServiceAmount - instantTotalServiceAmount,
171 totalExtrasAmount: totalExtrasAmount - instantTotalExtrasAmount,
172 discountAmount: discountAmount - instantDiscountAmount,
173 taxAmount: taxAmount - instantTaxAmount,
174 depositAmount: 0,
175 count: totalCount - prepaidCount,
176 },
177 prepaid: {
178 totalAmount: instantTotalAmount,
179 totalServiceAmount: instantTotalServiceAmount,
180 totalExtrasAmount: instantTotalExtrasAmount,
181 discountAmount: instantDiscountAmount,
182 taxAmount: instantTaxAmount,
183 depositAmount: instantDepositAmount,
184 count: prepaidCount,
185 },
186 appliedCoupon: appliedCoupon,
187 couponLimit: couponLimit,
188 servicesPrices: servicesPrices,
189 }
190 }
191
192 function useAppointmentBookingAmountData (store, appointment, includedTaxInTotal) {
193 let serviceTax = null
194
195 let excludedServiceTax = settings.payments.taxes.excluded
196
197 let enabledServiceTax = settings.payments.taxes.enabled
198
199 if ('tax' in appointment) {
200 serviceTax = appointment.tax && appointment.tax.length ? appointment.tax[0] : null
201
202 excludedServiceTax = serviceTax ? serviceTax.excluded : excludedServiceTax
203
204 enabledServiceTax = serviceTax !== null
205 } else if (enabledServiceTax) {
206 serviceTax = useEntityTax(store, appointment.serviceId, 'service')
207 }
208
209 let serviceAmount = (appointment.aggregatedPrice ? appointment.persons : 1) * appointment.price
210
211 let appointmentServiceAmount = 0
212
213 let appointmentTotalAmount = 0
214
215 let appointmentDiscountAmount = 0
216
217 let appointmentTaxAmount = 0
218
219 if (appointment.coupon) {
220 appointmentTotalAmount = serviceAmount
221
222 appointmentServiceAmount = serviceAmount
223
224 if (enabledServiceTax && serviceTax && !excludedServiceTax) {
225 serviceAmount = useTaxedAmount(serviceAmount, serviceTax)
226 }
227
228 let serviceDiscountAmount = appointment.coupon.discount
229 ? usePercentageAmount(serviceAmount, appointment.coupon.discount)
230 : 0
231
232 let serviceDiscountedAmount = serviceAmount - serviceDiscountAmount
233
234 serviceAmount = serviceDiscountedAmount
235
236 let deduction = appointment.coupon.deduction
237
238 let serviceDeductionAmount = 0
239
240 if (serviceDiscountedAmount > 0 && deduction > 0) {
241 serviceDeductionAmount = serviceDiscountedAmount >= deduction ? deduction : serviceDiscountedAmount
242
243 serviceAmount = serviceDiscountedAmount - serviceDeductionAmount
244
245 deduction = serviceDiscountedAmount >= deduction ? 0 : deduction - serviceDiscountedAmount
246 }
247
248 if (enabledServiceTax && serviceTax && excludedServiceTax) {
249 appointmentTaxAmount = useTaxAmount(serviceTax, serviceAmount)
250 } else if (enabledServiceTax && serviceTax && !excludedServiceTax) {
251 serviceAmount = useTaxedAmount(
252 (appointment.aggregatedPrice ? appointment.persons : 1) * appointment.price,
253 serviceTax
254 )
255
256 let serviceTaxAmount = useTaxAmount(serviceTax, serviceAmount - serviceDiscountAmount - serviceDeductionAmount)
257
258 if (includedTaxInTotal) {
259 appointmentTotalAmount = serviceAmount + serviceTaxAmount
260
261 appointmentServiceAmount = serviceAmount + serviceTaxAmount
262 } else {
263 appointmentTotalAmount = serviceAmount
264
265 appointmentServiceAmount = serviceAmount
266
267 appointmentTaxAmount = serviceTaxAmount
268 }
269 }
270
271 appointmentDiscountAmount = serviceDiscountAmount + serviceDeductionAmount
272
273 appointment.extras.forEach((selectedExtra) => {
274 let extraTax = null
275
276 let excludedExtraTax = settings.payments.taxes.excluded
277
278 let enabledExtraTax = settings.payments.taxes.enabled
279
280 if ('tax' in selectedExtra) {
281 extraTax = selectedExtra.tax && selectedExtra.tax.length ? selectedExtra.tax[0] : null
282
283 excludedExtraTax = extraTax ? extraTax.excluded : excludedExtraTax
284
285 enabledExtraTax = extraTax !== null
286 } else if (enabledExtraTax) {
287 extraTax = useEntityTax(store, selectedExtra.extraId, 'extra')
288 }
289
290 let extraAggregatedPrice = selectedExtra.aggregatedPrice === null ? appointment.aggregatedPrice : selectedExtra.aggregatedPrice
291
292 let extraAmount = useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons)
293
294 let extraTotalAmount = extraAmount
295
296 if (enabledExtraTax && extraTax && !excludedExtraTax) {
297 extraAmount = useTaxedAmount(extraAmount, extraTax)
298 }
299
300 let extraDiscountAmount = appointment.coupon.discount
301 ? usePercentageAmount(extraAmount, appointment.coupon.discount)
302 : 0
303
304 let extraDiscountedAmount = extraAmount - extraDiscountAmount
305
306 extraAmount = extraDiscountedAmount
307
308 let extraDeductionAmount = 0
309
310 if (extraDiscountedAmount > 0 && deduction > 0) {
311 extraDeductionAmount = extraDiscountedAmount >= deduction ? deduction : extraDiscountedAmount
312
313 extraAmount = extraDiscountedAmount - extraDeductionAmount
314
315 deduction = extraDiscountedAmount >= deduction ? 0 : deduction - extraDiscountedAmount
316 }
317
318 if (enabledExtraTax && extraTax && excludedExtraTax) {
319 appointmentTaxAmount += useTaxAmount(extraTax, extraAmount)
320 } else if (enabledExtraTax && extraTax && !excludedExtraTax) {
321 extraAmount = useTaxedAmount(
322 useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons),
323 extraTax
324 )
325
326 let extraTaxAmount = useTaxAmount(extraTax, extraAmount - extraDiscountAmount - extraDeductionAmount)
327
328 if (includedTaxInTotal) {
329 extraTotalAmount = extraAmount + extraTaxAmount
330 } else {
331 extraTotalAmount = extraAmount
332
333 appointmentTaxAmount += extraTaxAmount
334 }
335 } else if (enabledExtraTax && !extraTax && !excludedExtraTax) {
336 extraTotalAmount = useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons)
337 }
338
339 appointmentTotalAmount += extraTotalAmount
340
341 appointmentDiscountAmount += extraDiscountAmount + extraDeductionAmount
342 })
343 } else {
344 if (enabledServiceTax && serviceTax && excludedServiceTax) {
345 appointmentTaxAmount = useTaxAmount(serviceTax, serviceAmount)
346 } else if (enabledServiceTax && serviceTax && !excludedServiceTax && !includedTaxInTotal) {
347 serviceAmount = useTaxedAmount(
348 (appointment.aggregatedPrice ? appointment.persons : 1) * appointment.price,
349 serviceTax
350 )
351
352 appointmentTaxAmount = useTaxAmount(serviceTax, serviceAmount)
353 }
354
355 appointmentTotalAmount = serviceAmount
356
357 appointmentServiceAmount = serviceAmount
358
359 appointment.extras.forEach((selectedExtra) => {
360 let extraAggregatedPrice = selectedExtra.aggregatedPrice === null ? appointment.aggregatedPrice : selectedExtra.aggregatedPrice
361
362 let extraAmount = useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons)
363
364 let extraTax = null
365
366 let excludedExtraTax = settings.payments.taxes.excluded
367
368 let enabledExtraTax = settings.payments.taxes.enabled
369
370 if ('tax' in selectedExtra) {
371 extraTax = selectedExtra.tax && selectedExtra.tax.length ? selectedExtra.tax[0] : null
372
373 excludedExtraTax = extraTax ? extraTax.excluded : excludedExtraTax
374
375 enabledExtraTax = extraTax !== null
376 } else if (enabledExtraTax) {
377 extraTax = useEntityTax(store, selectedExtra.extraId, 'extra')
378 }
379
380 if (enabledExtraTax && extraTax && excludedExtraTax) {
381 appointmentTaxAmount += useTaxAmount(extraTax, extraAmount)
382 } else if (enabledExtraTax && extraTax && !excludedExtraTax && !includedTaxInTotal) {
383 extraAmount = useTaxedAmount(
384 useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons),
385 extraTax
386 )
387
388 appointmentTaxAmount += useTaxAmount(extraTax, extraAmount)
389 }
390
391 appointmentTotalAmount += extraAmount
392 })
393 }
394
395 return {
396 total: appointmentTotalAmount,
397 bookable: appointmentServiceAmount,
398 discount: appointmentDiscountAmount,
399 tax: appointmentTaxAmount
400 }
401 }
402
403 function useAppointmentsAmountInfo (store) {
404 let amountsInfo = []
405
406 let coupon = store.getters['booking/getCoupon']
407
408 let couponLimit = coupon && coupon.limit ? coupon.limit : 0
409
410 useCart(store).forEach((item) => {
411 let amountData = useAppointmentAmountData(store, item, coupon, couponLimit)
412
413 couponLimit = amountData.couponLimit
414
415 delete (amountData.couponLimit)
416
417 amountsInfo.push(amountData)
418 })
419
420 return amountsInfo
421 }
422
423 function useCapacity (employeeServices) {
424 let options = {
425 availability: false,
426 min: 0,
427 max: 0
428 }
429
430 let serviceMinCapacity = 0
431
432 if (employeeServices.length && employeeServices.length > 1) {
433 employeeServices.forEach(service => {
434 serviceMinCapacity = service.minCapacity
435
436 options.availability = service.bringingAnyone && service.maxCapacity > 1 && (service.maxExtraPeople === null || service.maxExtraPeople > 0)
437
438 if (service.maxCapacity > options.max || options.max === 0) {
439 options.max = service.maxExtraPeople !== null ? (service.maxExtraPeople + 1) : service.maxCapacity
440 }
441
442 if (options.min < service.minCapacity) {
443 options.min = settings.appointments.allowBookingIfNotMin ? 1 : service.minCapacity
444 }
445 })
446
447 } else if (employeeServices.length && employeeServices.length === 1) {
448 let service = employeeServices[0]
449
450 serviceMinCapacity = service.minCapacity
451
452 options.availability = service.bringingAnyone && service.maxCapacity > 1 && (service.maxExtraPeople === null || service.maxExtraPeople > 0)
453 options.min = settings.appointments.allowBookingIfNotMin ? 1 : service.minCapacity
454 options.max = service.maxExtraPeople !== null && service.maxExtraPeople < service.maxCapacity ? (service.maxExtraPeople + 1) : service.maxCapacity
455 }
456
457 if (settings.appointments.openedBookingAfterMin) {
458 options.min = serviceMinCapacity
459 }
460
461 let additionalPeople = settings.appointments.bringingAnyoneLogic === 'additional'
462
463 options.max = options.max > 1 ? (options.max - (additionalPeople ? 1 : 0)) : options.max
464 options.min = options.min > 0 ? (options.min - (additionalPeople ? 1 : 0)) : options.min
465
466 return options
467 }
468
469 function usePaymentError (store, message) {
470 store.commit('booking/setError', message)
471 }
472
473 function usePrepaidPrice (store) {
474 let type = store.getters['bookableType/getType'] ? store.getters['bookableType/getType'] : store.getters['booking/getBookableType']
475
476 let entity = null
477
478 switch (type) {
479 case 'appointment':
480 let appointmentPrice = 0
481
482 useCart(store).forEach((item) => {
483 if ((item.serviceId && (item.serviceId in item.services)) || item.packageId) {
484 entity = store.getters['entities/getService'](
485 item.serviceId
486 )
487
488 appointmentPrice += useAppointmentsTotalAmount(
489 store,
490 entity,
491 useAppointmentsPayments(
492 store,
493 item.serviceId,
494 item.services[item.serviceId].list
495 ).prepaid
496 )
497 }
498 })
499
500 return appointmentPrice
501 case 'package':
502 let price = 0
503
504 if (useCart(store)[0].packageId) {
505 entity = store.getters['entities/getPackage'](
506 useCart(store)[0].packageId
507 )
508
509 price = entity.price
510 }
511
512 return price
513 case 'event':
514 entity = store.getters['eventEntities/getEvent'](store.getters['eventBooking/getSelectedEventId'])
515
516 let eventPrice = 0
517
518 if (entity.customPricing) {
519 let tickets = store.getters['tickets/getTicketsData']
520
521 tickets.forEach(t => {
522 eventPrice += t.price * t.persons
523 })
524
525 return eventPrice
526 }
527
528 let persons = store.getters['persons/getPersons']
529
530 return entity.price * persons
531 }
532 }
533
534 function useDuration (serviceDuration, extras) {
535 let duration = serviceDuration
536
537 extras.forEach((extra) => {
538 duration += (extra.duration * extra.quantity)
539 })
540
541 return duration
542 }
543
544 function useCalendarEvents (slots) {
545 let calendarSlotsValues = []
546
547 useSortedDateStrings(Object.keys(slots)).forEach((date) => {
548 calendarSlotsValues.push({
549 title : 'e',
550 start : date,
551 display: 'background',
552 extendedProps: {
553 slotsTotal: 100,
554 slotsAvailable: 1,
555 slots: slots[date]
556 }
557 })
558 })
559
560 return calendarSlotsValues
561 }
562
563 function useServices (store) {
564 let type = store.getters['booking/getBookableType']
565
566 let services = null
567
568 switch (type) {
569 case ('appointment'): {
570 services = store.getters['entities/getServices']
571
572 break
573 }
574
575 case ('package'): {
576 let packages = store.getters['entities/getPackages']
577
578 let packagesServices = {}
579
580 packages.forEach((pack) => {
581 pack.bookable.forEach((book) => {
582 packagesServices[book.service.id] = book.service
583 })
584 })
585
586 services = Object.values(packagesServices)
587
588 break
589 }
590 }
591
592 return services
593 }
594
595 function useBusySlots (store) {
596 let cartItem = useCartItem(store)
597
598 let activeAppointment = cartItem.services[cartItem.serviceId].list[cartItem.index]
599
600 let busySlots = store.getters['booking/getMultipleAppointmentsOccupied']
601
602 return busySlots[activeAppointment.date] ? Object.keys(busySlots[activeAppointment.date]) : []
603 }
604
605 function useAvailableSlots (store) {
606 let cart = store.getters['booking/getAllMultipleAppointments']
607
608 let cartIndex = store.getters['booking/getCartItemIndex']
609
610 let activeAppointment = cart[cartIndex].services[cart[cartIndex].serviceId].list[cart[cartIndex].index]
611
612 let services = useServices(store)
613
614 if (activeAppointment.date) {
615 let selectedSlots = []
616
617 cart.forEach((cartItem, selectedCartIndex) => {
618 for (let serviceId in cartItem.services) {
619 let service = services.find(i => i.id === parseInt(serviceId))
620
621 cartItem.services[serviceId].list.forEach((i, selectedListIndex) => {
622 let isCurrentAppointment = selectedCartIndex === parseInt(cartIndex) &&
623 selectedListIndex === parseInt(cart[cartIndex].index) &&
624 (cartItem.packageId ? parseInt(cartItem.serviceId) === parseInt(serviceId) : true)
625
626 if (i.date && i.date === activeAppointment.date && i.time && !isCurrentAppointment) {
627 selectedSlots.push({
628 time: i.time,
629 duration: service.duration + i.extras.filter(e => e.quantity && e.duration).map(e => e.duration).reduce((fullDuration, duration) => fullDuration + duration, 0),
630 timeAfter: service.timeAfter,
631 timeBefore: service.timeBefore
632 })
633 }
634 })
635 }
636 })
637
638 let service = services.find(i => i.id === cart[cartIndex].serviceId)
639
640 let defaultSlots = Object.keys(cart[cartIndex].services[cart[cartIndex].serviceId].slots[activeAppointment.date])
641
642 let availableSlots = {}
643
644 for (let i = 0; i < defaultSlots.length; i++) {
645 let defaultSlotSeconds = useTimeInSeconds(defaultSlots[i])
646
647 let isFreeSlot = true
648
649 for (let j = 0; j < selectedSlots.length; j++) {
650 let slotInSeconds = useTimeInSeconds(selectedSlots[j].time)
651
652 if (defaultSlotSeconds > (slotInSeconds - service.duration - service.timeAfter) &&
653 defaultSlotSeconds < (slotInSeconds + selectedSlots[j].duration + selectedSlots[j].timeBefore + service.timeAfter)
654 ) {
655 isFreeSlot = false
656
657 break
658 }
659 }
660
661 if (isFreeSlot) {
662 availableSlots[defaultSlots[i]] = useTimeInSeconds(defaultSlots[i])
663 }
664 }
665
666 return useSortedDateStrings(Object.keys(availableSlots))
667 }
668
669 return 'slots' in activeAppointment ? activeAppointment.slots : []
670 }
671
672 function useFillAppointments (store) {
673 let cartItem = useCartItem(store)
674
675 if (!cartItem.packageId &&
676 Object.keys(cartItem.services).length === 1 &&
677 cartItem.services[cartItem.serviceId].list.length === 1
678 ) {
679 let booking = cartItem.services[cartItem.serviceId].list[0]
680
681 if (!booking.providerId && booking.date && booking.time) {
682 let employeesIds = cartItem.services[cartItem.serviceId].slots[booking.date][booking.time].map(
683 i => i[0]
684 ).filter(
685 (v, i, a) => a.indexOf(v) === i
686 )
687
688 if (settings.roles.limitPerEmployee.enabled) {
689 let chosenEmployees = store.getters['booking/getAllMultipleAppointments'].map(a => Object.values(a.services)[0].list[0])
690 let appCount = store.getters['booking/getMultipleAppointmentsAppCount'](cartItem.serviceId);
691 let result = checkLimitPerEmployee(employeesIds, 0, [], booking, appCount, chosenEmployees, cartItem.serviceId)
692 if (result.bookingFailed !== null) {
693 return {booking: result.bookingFailed, serviceId: parseInt(cartItem.serviceId)}
694 }
695 employeesIds = result.employeeIds
696 }
697
698 if (settings.appointments.employeeSelection === 'random') {
699 booking.providerId = employeesIds[Math.floor(Math.random() * (employeesIds.length) + 1) - 1]
700 } else {
701 employeesIds = sortForEmployeeSelection(store, employeesIds, cartItem.serviceId)
702 booking.providerId = employeesIds[0]
703 }
704 }
705
706 if (!booking.locationId && booking.date && booking.time) {
707 let locationsIds = cartItem.services[cartItem.serviceId].slots[booking.date][booking.time].filter(
708 i => i[0] === booking.providerId
709 ).map(i => i[1])
710
711 booking.locationId = locationsIds.length ? getPreferredEntityId(
712 cartItem.services[cartItem.serviceId].slots[booking.date],
713 booking.date in cartItem.services[cartItem.serviceId].occupied
714 ? cartItem.services[cartItem.serviceId].occupied[booking.date] : {},
715 booking.time,
716 booking.providerId,
717 locationsIds,
718 1
719 ) : null
720 }
721
722 let slots = store.getters['booking/getMultipleAppointmentsSlots']
723
724 let existingApp = booking.date in slots && booking.time in slots[booking.date] && slots[booking.date][booking.time].length > 0 ?
725 slots[booking.date][booking.time].find(s => s[0] === booking.providerId) : null
726
727 store.commit('booking/setMultipleAppointmentsExistingApp', existingApp && existingApp[2] && existingApp[2] > 0)
728 } else {
729 let chosenEmployees = []
730 for (let serviceId of Object.keys(cartItem.services)) {
731 if (cartItem.services[serviceId].list.length &&
732 cartItem.services[serviceId].list.filter(i => i.date && i.time).length
733 ) {
734 let bookingFailed = setPreferredEntitiesData(cartItem.services[serviceId], store, serviceId, chosenEmployees)
735 if (bookingFailed !== null) {
736 return {booking: bookingFailed, serviceId: parseInt(serviceId)}
737 }
738 chosenEmployees = chosenEmployees.concat(cartItem.services[serviceId].list.map((l) => { return { date: l.date, providerId: l.providerId, serviceId: serviceId, existingApp: l.existingApp} }))
739 }
740 }
741 }
742
743 let activeItemServices = cartItem.services
744
745 Object.keys(activeItemServices).forEach((serviceId) => {
746 if (activeItemServices[serviceId].list.filter(i => i.date && i.time).length) {
747 activeItemServices[serviceId].list.forEach((booking) => {
748 if (booking.date && booking.time) {
749 setProviderServicePrice(store, booking.providerId, serviceId)
750 }
751 })
752 }
753 })
754
755 return null
756 }
757
758 function setProviderServicePrice (store, employeeId, serviceId) {
759 let employee = store.getters['entities/getUnfilteredEmployee'](employeeId)
760
761 let service = employee.serviceList.find(i => i.id === parseInt(serviceId))
762
763 let duration = store.getters['booking/getDuration']
764
765 if (store.getters['booking/getDuration'] in service.customPricing.durations) {
766 service.duration = duration
767
768 service.price = service.customPricing.durations[duration].price
769 }
770 }
771
772 function useAppointmentsAmount (store, service, appointments) {
773 let amount = 0
774
775 appointments.forEach((appointment) => {
776 amount += useAppointmentServiceAmount(
777 useEmployeeService(store, service.id, appointment.providerId),
778 appointment.persons,
779 appointment.duration
780 )
781 })
782
783 return amount
784 }
785
786 function useExtraAmount (extra, serviceAggregatedPrice, persons) {
787 let extraAggregatedPrice = extra.aggregatedPrice === null ? serviceAggregatedPrice : extra.aggregatedPrice
788
789 return extra.price * extra.quantity * (extraAggregatedPrice ? persons : 1)
790 }
791
792 function useAppointmentExtraAmount (service, selectedExtra, persons) {
793 let extra = service.extras.find(item => item.id === parseInt(selectedExtra.extraId))
794
795 if (extra) {
796 let extraAggregatedPrice = extra.aggregatedPrice === null ? service.aggregatedPrice : extra.aggregatedPrice
797
798 return extra.price * selectedExtra.quantity * (extraAggregatedPrice ? persons : 1)
799 }
800
801 return 0
802 }
803
804 function useAppointmentExtrasAmount (service, appointments) {
805 let amount = 0
806
807 appointments.forEach((appointment) => {
808 if (appointment.extras) {
809 appointment.extras.forEach((selectedExtra) => {
810 amount += useAppointmentExtraAmount(service, selectedExtra, appointment.persons)
811 })
812 }
813 })
814
815 return amount
816 }
817
818 function useAppointmentsTotalAmount (store, service, appointments) {
819 return useAppointmentsAmount(store, service, appointments) + useAppointmentExtrasAmount(service, appointments)
820 }
821
822 function useAppointmentsPayments (store, serviceId, appointments) {
823 let service = store.getters['entities/getService'](
824 serviceId
825 )
826
827 let prepaidCount = 1
828
829 if (service.recurringPayment) {
830 prepaidCount = service.recurringPayment > appointments.length
831 ? appointments.length : service.recurringPayment
832 }
833
834 return {
835 prepaid: appointments.slice(0, prepaidCount),
836 postpaid: appointments.slice(prepaidCount),
837 }
838 }
839
840 function setPreferredEntitiesData (bookings, store, serviceId, chosenEmployees) {
841 let employeesIds = getAllEntitiesIds(bookings, 0)
842
843 let locationsIds = getAllEntitiesIds(bookings, 1)
844
845 let isSingleEmployee = employeesIds.length === 1
846
847 let isSingleLocation = locationsIds.length === 1
848
849 let appCount = store.getters['booking/getMultipleAppointmentsAppCount'](serviceId);
850
851 for (let bookingIndex = 0; bookingIndex < bookings.list.length; bookingIndex++) {
852 let booking = bookings.list[bookingIndex]
853 if (booking.date && booking.time) {
854 employeesIds = sortForEmployeeSelection(store, employeesIds, serviceId)
855
856 if (settings.roles.limitPerEmployee.enabled) {
857 let result = checkLimitPerEmployee(employeesIds, bookingIndex, bookings.list, booking, appCount, chosenEmployees, serviceId)
858 if (result.bookingFailed !== null) {
859 return result.bookingFailed
860 }
861 employeesIds = result.employeeIds
862 }
863
864 if (!locationsIds.length && isSingleEmployee) {
865 booking.providerId = employeesIds[0]
866
867 booking.locationId = null
868 } else if (!locationsIds.length && !isSingleEmployee) {
869 booking.locationId = null
870
871 for (let i = 0; i < employeesIds.length; i++) {
872 for (let j = 0; j < bookings.slots[booking.date][booking.time].length; j++) {
873 if (bookings.slots[booking.date][booking.time][j][0] === employeesIds[i]) {
874 booking.providerId = employeesIds[i]
875
876 break
877 }
878 }
879 }
880 } else if (isSingleLocation && isSingleEmployee) {
881 booking.providerId = employeesIds[0]
882
883 booking.locationId = locationsIds[0]
884 } else if (!isSingleLocation && isSingleEmployee) {
885 booking.providerId = employeesIds[0]
886
887 booking.locationId = getPreferredEntityId(
888 bookings.slots[booking.date],
889 booking.date in bookings.occupied ? bookings.occupied[booking.date] : {},
890 booking.time,
891 booking.providerId,
892 locationsIds,
893 1
894 )
895 } else if (isSingleLocation && !isSingleEmployee) {
896 booking.locationId = locationsIds[0]
897
898 booking.providerId = getPreferredEntityId(
899 bookings.slots[booking.date],
900 booking.date in bookings.occupied ? bookings.occupied[booking.date] : {},
901 booking.time,
902 booking.locationId,
903 employeesIds,
904 0
905 )
906 } else {
907 let setEntities = false
908 outsideLoop: for (let j = 0; j < employeesIds.length; j++) {
909 for (let i = 0; i < locationsIds.length; i++) {
910 let isPreferred = isPreferredLocationAndEmployee(
911 bookings.slots[booking.date],
912 booking.date in bookings.occupied ? bookings.occupied[booking.date] : {},
913 booking.time,
914 locationsIds[i],
915 employeesIds[j]
916 )
917
918 if (isPreferred) {
919 booking.providerId = employeesIds[j]
920
921 booking.locationId = locationsIds[i]
922
923 setEntities = true
924
925 break outsideLoop
926 }
927 }
928 }
929
930 if (!setEntities) {
931 outsideLoop2: for (let j = 0; j < employeesIds.length; j++) {
932 for (let i = 0; i < locationsIds.length; i++) {
933 for (let k = 0; k < bookings.slots[booking.date][booking.time].length; k++) {
934 if (bookings.slots[booking.date][booking.time][k][0] === employeesIds[j] &&
935 bookings.slots[booking.date][booking.time][k][1] === locationsIds[i]
936 ) {
937 booking.providerId = employeesIds[j]
938
939 booking.locationId = locationsIds[i]
940
941 break outsideLoop2
942 }
943 }
944 }
945 }
946 }
947 }
948
949 let slots = store.getters['booking/getMultipleAppointmentsSlots']
950 let existingApp = booking.date in slots && booking.time in slots[booking.date] && slots[booking.date][booking.time].length > 0 ?
951 slots[booking.date][booking.time].find(s => s[0] === booking.providerId) : null
952 bookings.list[bookingIndex].existingApp = existingApp && existingApp[2] && existingApp[2] > 0
953
954 store.commit('booking/setLastBookedProviderId', {providerId: booking.providerId, fromBackend: false})
955
956 }
957 }
958
959 return null
960 }
961
962 function getAllEntitiesIds (bookings, index) {
963 let ids = {}
964
965 for (let i = 0; i < bookings.list.length; i++) {
966 if (bookings.list[i].date && bookings.list[i].time) {
967 bookings.slots[bookings.list[i].date][bookings.list[i].time].forEach((slotData) => {
968 if (slotData[index]) {
969 if (!(slotData[index] in ids)) {
970 ids[slotData[index]] = 0
971 }
972
973 ids[slotData[index]]++
974 }
975 })
976 }
977 }
978
979 let sortedEntitiesIds = []
980
981 Object.keys(ids).forEach((id) => {
982 sortedEntitiesIds.push({id: parseInt(id), quantity: ids[id]})
983 })
984
985 sortedEntitiesIds.sort((a, b) => b.quantity - a.quantity)
986
987 return sortedEntitiesIds.map(entity => entity.id)
988 }
989
990 function getPreferredEntityId (availableSlots, occupiedSlots, timeString, selectedId, allIds, targetIndex) {
991 let searchIndex = targetIndex ? 0 : 1
992
993 let appointmentsStarts = {}
994
995 Object.keys(occupiedSlots).forEach((time) => {
996 occupiedSlots[time].forEach((slotData) => {
997 if (slotData[searchIndex] === selectedId) {
998 appointmentsStarts[useTimeInSeconds(time)] = slotData[targetIndex]
999 }
1000 })
1001 })
1002
1003 Object.keys(availableSlots).forEach((time) => {
1004 availableSlots[time].forEach((slotData) => {
1005 if (slotData.length >= 3 && slotData[searchIndex] === selectedId) {
1006 appointmentsStarts[useTimeInSeconds(time)] = slotData[targetIndex]
1007 }
1008 })
1009 })
1010
1011 let availableIds = []
1012
1013 availableSlots[timeString].forEach((slotData) => {
1014 if (slotData[searchIndex] === selectedId) {
1015 availableIds.push(slotData[targetIndex])
1016 }
1017 })
1018
1019 if (Object.keys(appointmentsStarts).length) {
1020 let timeInSeconds = useTimeInSeconds(timeString)
1021
1022 let closestSlot = Object.keys(appointmentsStarts).reduce((a, b) => {
1023 return Math.abs(b - timeInSeconds) < Math.abs(a - timeInSeconds) ? b : a
1024 })
1025
1026 if (availableIds.indexOf(appointmentsStarts[closestSlot]) !== -1) {
1027 return appointmentsStarts[closestSlot]
1028 }
1029 }
1030
1031 for (let i = 0; i < allIds.length; i++) {
1032 for (let j = 0; j < availableSlots[timeString].length; j++) {
1033 if (availableSlots[timeString][j][searchIndex] === selectedId &&
1034 allIds[i] === availableSlots[timeString][j][targetIndex]
1035 ) {
1036 return availableSlots[timeString][j][targetIndex]
1037 }
1038 }
1039 }
1040
1041 return null
1042 }
1043
1044 function isPreferredLocationAndEmployee (slotsData, occupiedData, timeString, locationId, employeeId) {
1045 let isEmployeeLocation = false
1046
1047 slotsData[timeString].forEach((slotData) => {
1048 if (slotData[0] === employeeId && slotData[1] === locationId) {
1049 isEmployeeLocation = true
1050 }
1051 })
1052
1053 // inspect if employee is available on proposed location
1054 if (!isEmployeeLocation) {
1055 return false
1056 }
1057
1058 let appointmentStarts = {
1059 onLocation: {},
1060 offLocation: {}
1061 }
1062
1063 Object.keys(occupiedData).forEach((time) => {
1064 occupiedData[time].forEach((slotData) => {
1065 if (slotData[0] === employeeId && slotData[1] === locationId) {
1066 appointmentStarts.onLocation[useTimeInSeconds(time)] = slotData[1]
1067 } else if (slotData[0] === employeeId) {
1068 appointmentStarts.offLocation[useTimeInSeconds(time)] = slotData[1]
1069 }
1070 })
1071 })
1072
1073 Object.keys(slotsData).forEach((time) => {
1074 slotsData[time].forEach((slotData) => {
1075 if (slotData.length >= 3 && slotData[0] === employeeId && slotData[1] === locationId) {
1076 appointmentStarts.onLocation[useTimeInSeconds(time)] = slotData[1]
1077 } else if (slotData.length >= 3 && slotData[0] === employeeId) {
1078 appointmentStarts.offLocation[useTimeInSeconds(time)] = slotData[1]
1079 }
1080 })
1081 })
1082
1083 // inspect if employee has appointments only on proposed location, or has no appointments in that day
1084 if (
1085 (!Object.keys(appointmentStarts.onLocation).length && !Object.keys(appointmentStarts.offLocation).length) ||
1086 (Object.keys(appointmentStarts.onLocation).length && !Object.keys(appointmentStarts.offLocation).length)
1087 ) {
1088 return true
1089 }
1090
1091 let timeInSeconds = useTimeInSeconds(timeString)
1092
1093 appointmentStarts = Object.assign(appointmentStarts.onLocation, appointmentStarts.offLocation)
1094
1095 let closestTime = Object.keys(appointmentStarts).reduce((a, b) => {
1096 return Math.abs(b - timeInSeconds) < Math.abs(a - timeInSeconds) ? b : a
1097 })
1098
1099 return locationId === appointmentStarts[closestTime]
1100 }
1101
1102 export {
1103 useCapacity,
1104 useAvailableSlots,
1105 useBusySlots,
1106 useFillAppointments,
1107 useCalendarEvents,
1108 useAppointmentsPayments,
1109 useAppointmentsAmountInfo,
1110 useAppointmentServiceAmount,
1111 useAppointmentExtrasAmount,
1112 useAppointmentExtraAmount,
1113 useAppointmentsAmount,
1114 useAppointmentsTotalAmount,
1115 useDuration,
1116 usePrepaidPrice,
1117 usePaymentError,
1118 useServices,
1119 useEmployeeService,
1120 useAppointmentBookingAmountData,
1121 }
1122