shell bypass 403
<template>
<div class="am-wrap">
<div id="am-appointments" class="am-body">
<!-- Spinner -->
<div class="am-spinner am-section" v-show="!fetched || !options.fetched">
<img :src="$root.getUrl+'public/img/spinner.svg'"/>
</div>
<!-- Empty State -->
<div class="am-empty-state am-section"
v-if="fetched && packageCustomers.length === 0 && !filterApplied && fetchedFiltered && options.fetched">
<img :src="$root.getUrl+'public/img/emptystate.svg'">
<h2>{{ $root.labels.no_appointments_yet }}</h2>
<p>{{ $root.labels.click_add_appointments }}</p>
</div>
<!-- Appointments -->
<div
v-show="fetched && options.fetched && (packageCustomers.length !== 0 || (packageCustomers.length === 0 && filterApplied) || !fetchedFiltered)"
>
<!-- Global Search & Filters -->
<div class="am-appointments-filter am-section">
<div style="margin-bottom: 16px">
<h1 @click="goBack" style="cursor: pointer;display: inline-block"><img :src="$root.getUrl+'public/img/arrow-back.svg'"> {{ purchasedPackage.name }}</h1>
</div>
<el-form :model="params" class="demo-form-inline" :action="exportAction" method="POST">
<!-- Filters -->
<transition name="fade">
<div class="am-filter-fields" v-show="filterFields">
<el-row :gutter="16" class="am-package-appointment-filters">
<!-- Dates Filter -->
<el-col :sm="24" :md="12" :lg="8" class="v-calendar-column">
<el-form-item prop="dates">
<v-date-picker
@input="changeRange"
v-model="params.dates"
:is-double-paned="false"
mode='range'
popover-visibility="focus"
popover-direction="bottom"
tint-color='#1A84EE'
:show-day-popover=false
:input-props='{class: "el-input__inner"}'
:is-expanded=false
:is-required=true
input-class="el-input__inner"
:formats="vCalendarFormats"
>
</v-date-picker>
<span
v-if="params.dates"
class="am-v-date-picker-suffix el-input__suffix-inner"
@click="clearDateFilter"
>
<i class="el-select__caret el-input__icon el-icon-circle-close"></i>
</span>
</el-form-item>
</el-col>
<!-- Customer Filter -->
<el-col :sm="24" :md="12" :lg="8">
<el-form-item>
<el-select
v-model="params.customerId"
filterable
clearable
:placeholder="$root.labels.customer"
@change="changeFilter"
remote
:remote-method="searchCustomers"
:loading="customersLoading"
>
<el-option
v-for="(item, key) in searchedCustomers.length ? searchedCustomers : options.entities.customers"
:key="key"
:label="(!item.firstName.trim() && !item.lastName.trim()) ? $root.labels.customer + ' ' + item.id : item.firstName + ' ' + item.lastName"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
<!-- Status Filter -->
<el-col :sm="24" :md="12" :lg="8">
<el-form-item>
<el-select
v-model="params.packageStatus"
clearable
:placeholder="$root.labels.package_status"
@change="changeFilter"
>
<el-option
v-for="(item, key) in packageStatuses"
:key="key"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
</transition>
<!-- Dialog Export -->
<transition name="slide">
<el-dialog
:close-on-click-modal="false"
class="am-side-dialog am-dialog-export"
:visible.sync="dialogExport"
:show-close="false"
v-if="dialogExport"
>
<dialog-export
:data="Object.assign(params, exportParams)"
:action="$root.getAjaxUrl + '/report/appointments'"
@updateAction="(action) => {this.exportAction = action}"
@closeDialogExport="dialogExport = false"
>
</dialog-export>
</el-dialog>
</transition>
</el-form>
</div>
<!-- No Results -->
<div class="am-empty-state am-section"
v-show="fetched && Object.keys(packageCustomers).length === 0 && filterApplied && fetchedFiltered && options.fetched">
<img :src="$root.getUrl + 'public/img/emptystate.svg'">
<h2>{{ $root.labels.no_results }}</h2>
</div>
<!-- Content Spinner -->
<div class="am-spinner am-section" v-show="!fetchedFiltered">
<img :src="$root.getUrl+'public/img/spinner.svg'"/>
</div>
<!-- Appointment List -->
<div
class="am-appointments am-section"
v-show="fetchedFiltered && options.fetched && Object.keys(packageCustomers).length !== 0"
>
<!-- Appointment List Header -->
<div class="am-appointments-list-head">
<el-row>
<el-col :lg="12">
<el-row :gutter="10" type="flex" justify="space-around" align="middle">
<!-- Checkbox -->
<el-col :lg="2" :sm="2">
</el-col>
<!-- Appointment List Customer Label -->
<el-col :lg="8" :md="8" :sm="12">
<p>{{ $root.labels.customer }}:</p>
</el-col>
<!-- Appointment List Spots Label -->
<el-col :lg="7" :md="7" :sm="12">
<p>{{ $root.labels.appointments }}:</p>
</el-col>
<!-- Appointment Date Booked Label -->
<el-col :lg="7" :md="7" :sm="12">
<p>{{ $root.labels.package_date_purchased }}:</p>
</el-col>
</el-row>
</el-col>
<el-col :lg="12">
<el-row :gutter="10" type="flex" justify="space-around" align="middle">
<!-- Package Employees Label -->
<el-col :lg="6" :md="6">
<p>{{ $root.labels.employees }}:</p>
</el-col>
<!-- Package Price Label -->
<el-col :lg="5" :sm="5">
<p>{{ $root.labels.price }}:</p>
</el-col>
<!-- Package Payment Status Label -->
<el-col :lg="6" :md="7">
<p>{{ $root.labels.package_payment_status }}:</p>
</el-col>
<!-- Package Status Label -->
<el-col :lg="7" :sm="3" :xs="8">
</el-col>
</el-row>
</el-col>
</el-row>
</div>
<!-- Appointment List Content -->
<div>
<!-- Appointments -->
<div class="am-appointments-list">
<el-collapse>
<el-collapse-item
v-for="(packageCustomer, packageCustomerId) in packageCustomers"
:key="packageCustomerId"
:name="packageCustomerId"
class="am-appointment"
:class="appointmentCameFrom(packageCustomer)"
>
<template slot="title">
<div class="am-appointment-data">
<el-row>
<el-col :lg="12">
<el-row :gutter="15" class="am-appointments-flex-row-middle-align">
<!-- Checkbox -->
<el-col :lg="2" :sm="2">
<span class="am-appointment-checkbox" @click.stop>
<el-checkbox
v-if="$root.settings.capabilities.canDelete === true"
@change="handleCheckPackageAppointments(packageCustomerId)"
v-model="packageCustomer.checked"
:value="packageCustomer.checked"
:label="packageCustomer.checked"
>
</el-checkbox>
</span>
</el-col>
<!-- Customer -->
<el-col :lg="8" :sm="8" :xs="24">
<p class="am-col-title">{{ $root.labels.customer }}:</p>
<template>
<h3>
<span :class="getNoShowClass(packageCustomer.appointments[0].booking.customer.id, customersNoShowCount, null, packageCustomer.appointments[0].booking.customer.status)">
{{ (user = getCustomerInfo(packageCustomer.appointments[0].booking)) !== null ? (!user.firstName.trim() && !user.lastName.trim() ? $root.labels.customer + ' ' + user.id : user.firstName + ' ' + user.lastName) : '' }}
</span>
</h3>
<span>{{ packageCustomer.appointments[0].booking.customer.email }}</span>
</template>
</el-col>
<!-- Appointments Count -->
<el-col :lg="7" :sm="7">
<p class="am-col-title">{{ $root.labels.appointments }}:</p>
<h3>{{ packageCustomer.total }} {{ $root.labels.package_total }}</h3>
<span> {{ packageCustomer.count }} {{ $root.labels.package_to_be_booked }}</span>
</el-col>
<!-- Date purchased -->
<el-col :lg="7" :sm="7" :xs="12">
<p class="am-col-title">{{ $root.labels.package_date_purchased }}:</p>
<h3>{{ getFrontedFormattedDateFromDateTimeString(packageCustomer.appointments[0].booking.packageCustomerService.packageCustomer.purchased) }}</h3>
<span>{{ getFrontedFormattedTimeFromDateTimeString(packageCustomer.appointments[0].booking.packageCustomerService.packageCustomer.purchased) }}</span>
<span :class="'am-appointment-package-purchased-status am-appointment-package-purchased-status-' + getPackagePurchasedExpiration(packageCustomer.appointments[0].booking.packageCustomerService.packageCustomer.end).class">
{{ getPackagePurchasedExpiration(packageCustomer.appointments[0].booking.packageCustomerService.packageCustomer.end).label }}
</span>
</el-col>
</el-row>
</el-col>
<el-col :lg="12">
<el-row :gutter="15" class="am-appointments-flex-row-middle-align">
<el-col :lg="0" :md="2" :sm="2"></el-col>
<!-- Employees -->
<el-col :lg="6" :sm="6">
<p class="am-col-title">{{ $root.labels.employees }}:</p>
<div class="am-category-services-thumbs">
<img
v-for="(providerId, index) in getPurchasedPackageProviders(packageCustomer.appointments)"
v-if="index < 3"
:key="providerId"
:src="pictureLoad(getProviderById(providerId), true)"
@error="imageLoadError(getProviderById(providerId), true)"
>
</div>
</el-col>
<!-- Price -->
<el-col class="am-appointment-payment" :lg="5" :sm="5" :xs="13">
<p class="am-col-title">{{ $root.labels.price }}:</p>
<h3>{{ getFormattedPrice(getPackageDiscountedPrice(packageCustomer.appointments[0].booking.packageCustomerService.packageCustomer)) }}</h3>
</el-col>
<!-- Payment Status -->
<el-col :lg="6" :sm="6" :xs="16" class="am-finance-payment-status">
<p class="am-col-title">{{ $root.labels.package_payment_status }}:</p>
<div class="am-payment-status">
<span :class="'am-payment-status-symbol am-payment-status-symbol-' + getPackagePaymentStatus(packageCustomer.appointments)"></span>
<h3>
{{ getPaymentStatusNiceName(getPackagePaymentStatus(packageCustomer.appointments)) }}
</h3>
</div>
</el-col>
<el-col :lg="7" :sm="3" :xs="8">
<div @click.stop>
<el-button
v-if="packageCustomer.status !== 'canceled'"
@click="updatePackageCustomerStatus(packageCustomerId, 'canceled')"
type="danger"
plain
:loading="packageCustomer.updating"
>
{{ $root.labels.cancel }}
</el-button>
<el-button
v-else
@click="updatePackageCustomerStatus(packageCustomerId, 'approved')"
type="primary"
plain
:loading="packageCustomer.updating"
>
{{ $root.labels.open }}
</el-button>
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</template>
<packages-list-collapsed
:packageCustomer="packageCustomer"
:packageCustomerId="packageCustomerId"
:options="options"
:purchasedPackage="purchasedPackage"
@showDialogEditPackageAppointment="showDialogEditPackageAppointment"
@updatePackageAppointmentBookingStatus="updatePackageAppointmentBookingStatusCallback"
@showDialogNewPackageAppointment="showDialogNewPackageAppointment"
@handleCheckPackageAppointment="handleToaster"
>
</packages-list-collapsed>
</el-collapse-item>
</el-collapse>
</div>
</div>
</div>
<!-- Selected Popover -->
<transition name="slide-vertical">
<div class="am-bottom-popover" v-if="toaster">
<transition name="fade">
<el-button
class="am-button-icon"
@click="switchShowDeleteConfirmation(true)"
v-show="showDeleteButton"
>
<img class="svg-amelia" :alt="$root.labels.delete" :src="$root.getUrl+'public/img/delete.svg'"/>
</el-button>
</transition>
<transition name="slide-vertical">
<div class="am-bottom-popover-confirmation" v-show="showDeleteConfirmation">
<el-row type="flex" justify="start" align="middle">
<h3>{{ confirmationText() }}</h3>
<div class="align-left">
<el-button size="small" @click="switchShowDeleteConfirmation(false)">
{{ $root.labels.cancel }}
</el-button>
<el-button
size="small" @click="deleteSelected"
type="primary"
:loading="deleteLoading"
>
{{ $root.labels.delete }}
</el-button>
</div>
</el-row>
</div>
</transition>
</div>
</transition>
<!-- Pagination -->
<pagination-block
:params="paginationParams"
:show="paginationParams.show"
:count="paginationParams.total"
:label="$root.labels.purchased_packages_pagination"
:visible="paginationParams.total > paginationParams.show"
@change="getAppointments"
>
</pagination-block>
</div>
<!-- Dialog New Appointment -->
<transition name="slide">
<el-dialog
:close-on-click-modal="false"
class="am-side-dialog"
:visible.sync="dialogAppointment"
:show-close="false"
v-if="dialogAppointment"
>
<dialog-appointment
:appointment="appointment"
:recurringAppointments="recurringAppointments"
:savedAppointment="savedAppointment"
:totalBookings="totalBookings"
:bookings="bookings"
:options="options"
:customerCreatedCount="customerCreatedCount"
:customersNoShowCount="customersNoShowCount"
:packageServices="packageServices"
:packageCustomer="packageCustomer"
:have-duplicate="packageCustomer.count > 0 && appointment && packageServices.map(packageService => packageService.id).indexOf(appointment.serviceId) !== -1"
@sortBookings="sortBookings"
@saveCallback="(response) => {appointment.id ? saveAppointmentCallback(response, true) : saveAppointmentCallback(response)}"
@duplicateCallback="duplicateAppointmentCallback(appointment)"
@closeDialog="closeDialogAppointment"
@showDialogNewCustomer="showDialogNewCustomer()"
@editPayment="editPayment"
@openRecurringAppointment="openRecurringAppointment"
>
</dialog-appointment>
</el-dialog>
</transition>
<!-- Dialog New Package Booking -->
<transition name="slide">
<el-dialog
:close-on-click-modal="false"
class="am-side-dialog"
:visible.sync="dialogPackageBooking"
:show-close="false"
v-if="dialogPackageBooking"
>
<dialog-package-booking
:options="options"
:passed-package="purchasedPackage"
:customerCreatedCount="customerCreatedCount"
:new-user="newUser"
@saveCallback="packageBookingCallback"
@closeDialog="closePackageBooking"
@showDialogNewCustomer="showDialogNewCustomer()"
@editPayment="editPayment"
>
</dialog-package-booking>
</el-dialog>
</transition>
<!-- Dialog New Customer -->
<transition name="slide">
<el-dialog
:close-on-click-modal="false"
class="am-side-dialog"
:visible.sync="dialogCustomer"
:show-close="false"
v-if="dialogCustomer">
<dialog-customer
:customer="customer"
@closeDialog="dialogCustomer=false"
@saveCallback="saveCustomerCallback"
>
</dialog-customer>
</el-dialog>
</transition>
<!-- Dialog Payment -->
<transition name="slide">
<el-dialog
:close-on-click-modal="false"
class="am-side-dialog am-dialog-coupon"
:visible.sync="dialogPayment"
:show-close="false"
v-if="dialogPayment"
>
<dialog-payment
:modalData="selectedPaymentModalData"
:bookingFetched=true
@closeDialogPayment="dialogPayment = false"
@updatePaymentCallback="updatePaymentCallback"
@deletePaymentCallback="deletePaymentCallback"
>
</dialog-payment>
</el-dialog>
</transition>
</div>
</div>
</template>
<script>
import PackagesListCollapsed from './../services/PackagesListCollapsed.vue'
import appointmentMixin from '../../../js/backend/mixins/appointmentMixin'
import appointmentPriceMixin from '../../../js/backend/mixins/appointmentPriceMixin'
import customerMixin from '../../../js/backend/mixins/customerMixin'
import dateMixin from '../../../js/common/mixins/dateMixin'
import DialogAppointment from './../appointments/DialogAppointment.vue'
import DialogPackageBooking from './../services/DialogPackageBooking.vue'
import DialogCustomer from '../customers/DialogCustomer.vue'
import DialogExport from '../parts/DialogExport.vue'
import DialogPayment from '../finance/DialogFinancePayment.vue'
import durationMixin from '../../../js/common/mixins/durationMixin'
import entitiesMixin from '../../../js/common/mixins/entitiesMixin'
import Form from 'form-object'
import helperMixin from '../../../js/backend/mixins/helperMixin'
import imageMixin from '../../../js/common/mixins/imageMixin'
import moment from 'moment'
import notifyMixin from '../../../js/backend/mixins/notifyMixin'
import PageHeader from '../parts/PageHeader.vue'
import paymentMixin from '../../../js/backend/mixins/paymentMixin'
import priceMixin from '../../../js/common/mixins/priceMixin'
import customFieldMixin from '../../../js/common/mixins/customFieldMixin'
import PaginationBlock from '../parts/PaginationBlock.vue'
import packageMixin from '../../../js/frontend/mixins/packageMixin'
import taxMixin from '../../../js/common/mixins/taxesMixin'
export default {
mixins: [
customFieldMixin,
paymentMixin,
entitiesMixin,
appointmentMixin,
imageMixin,
dateMixin,
durationMixin,
notifyMixin,
customerMixin,
priceMixin,
helperMixin,
appointmentPriceMixin,
packageMixin,
taxMixin
],
props: [
'purchasedPackage',
'dialogPackageBooking'
],
data () {
return {
customersNoShowCount: [],
customersLoading: false,
changedRange: false,
packageServices: [],
packageCustomer: null,
showDeleteButton: true,
appointment: null,
customer: null,
deleteLoading: false,
dialogAppointment: false,
dialogPayment: false,
dialogExport: false,
fetched: false,
fetchedFiltered: false,
filterFields: true,
form: new Form(),
params: {
page: 1,
dates: this.getDatePickerInitRange(),
providers: [],
search: '',
status: '',
services: [],
customerId: '',
packageStatus: ''
},
selectedPaymentModalData: {
paymentId: null,
bookingStart: null,
bookings: null,
service: null,
providers: null,
customer: null
},
showDeleteConfirmation: false,
timer: null,
toaster: false,
updateBookingStatusId: 0,
updateBookingStatusLoading: false,
appointmentsDeleteCount: {
success: 0,
error: 0
},
packageCustomersDeleteCount: {
success: 0,
error: 0
},
packageCustomers: [],
newUser: null,
packageStatuses: [
{ value: 'approved', label: this.$root.labels.active },
{ value: 'expired', label: this.$root.labels.expired },
{ value: 'canceled', label: this.$root.labels.canceled }
],
bookingCount: [
{ value: 1, label: this.$root.labels.available },
{ value: 0, label: this.$root.labels.fully_booked }
],
paginationParams: {
page: 1,
show: this.$root.settings.general.itemsPerPageBackEnd,
total: 0
}
}
},
created () {
Form.defaults.axios = this.$http
// Set filter params based on URL GET fields
let urlParams = this.getUrlQueryParams(window.location.href)
if (!('dateFrom' in urlParams) || !('dateTo' in urlParams)) {
this.params.dates = this.getDatePickerInitRange()
} else {
this.params.dates = {
start: moment(urlParams['dateFrom']).toDate(),
end: moment(urlParams['dateTo']).toDate()
}
}
if (urlParams['status']) { this.params.status = urlParams['status'] }
if (urlParams['bookingId']) { this.params.bookingId = urlParams['bookingId'] }
this.getAppointmentOptions(true, true)
},
mounted () {
if (this.$root.settings.payments.wc && this.$root.settings.payments.wc.enabled) {
this.exportParams.fields.push({label: this.$root.labels.wc_order_id, value: 'wcOrderId', checked: true})
}
},
updated () {
if (this.fetched) this.inlineSVG()
},
methods: {
getPackagePurchasedExpiration (end) {
if (end === null) {
return { label: this.$root.labels.unlimited, class: 'unlimited' }
} else if (this.getDateTime(end) < this.getNowDate()) {
return { label: this.$root.labels.expired, class: 'expired' }
} else {
return { label: (this.$root.labels.expires_on + ' ' + this.getFrontedFormattedDateFromDateTimeString(end)), class: 'active' }
}
},
clearDateFilter () {
this.params.dates = null
this.paginationParams.page = 1
this.getAppointments()
},
getPackageDiscountedPrice (packCustomer) {
let amountData = this.getAmountData(
packCustomer.tax && packCustomer.tax.length ? packCustomer.tax[0] : null,
packCustomer.price,
this.options.entities.coupons.length && packCustomer.couponId
? this.options.entities.coupons.find(c => c.id === packCustomer.couponId)
: null
)
return amountData.total - amountData.discount + amountData.tax
},
saveCustomerCallback (response) {
this.options.entities.customers.push(response.user)
this.newUser = response.user
this.customerCreatedCount++
},
getAvailableServicesForPurchase (packageCustomer) {
let packageCustomerId = packageCustomer.appointments[0].packageCustomerId
let availableServicesIds = []
packageCustomer.data.forEach((purchases) => {
if (purchases.bookings.filter(item => item.count > 0 && item.packageCustomerId === packageCustomerId).length) {
availableServicesIds.push(purchases.serviceId)
}
})
return this.options.entities.services.filter(item => availableServicesIds.indexOf(item.id) !== -1)
},
showDialogNewPackageAppointment (packageCustomer) {
this.packageServices = this.getAvailableServicesForPurchase(packageCustomer)
this.packageCustomer = packageCustomer
this.showDialogNewAppointment(
{
customerId: packageCustomer.appointments[0].booking.packageCustomerService.packageCustomer.customerId,
id: packageCustomer.appointments[0].packageCustomerId
}
)
},
showDialogEditPackageAppointment (id, packageCustomer) {
this.packageServices = this.getAvailableServicesForPurchase(packageCustomer)
this.packageCustomer = packageCustomer
this.showDialogEditAppointment(id, packageCustomer.appointments[0].booking.customerId)
},
getPurchasedPackageProviders (app) {
return _.uniq(app.map(a => a.provider ? a.provider.id : 0)).filter(a => a !== 0)
},
goBack () {
this.$emit('closePurchasedPackages')
},
switchShowDeleteConfirmation (bool) {
this.showDeleteConfirmation = bool
this.showDeleteButton = !bool
},
confirmationText () {
let message = this.checkedAppointmentsCount() < 2 ? this.$root.labels.confirm_delete_appointment : this.$root.labels.confirm_delete_appointment_plural
Object.keys(this.packageCustomers).forEach((packageCustomerId) => {
if (this.packageCustomers[packageCustomerId].checked) {
message = this.$root.labels.confirm_delete_package_purchase
}
})
return message
},
checkedAppointmentsCount () {
let cnt = 0
Object.keys(this.packageCustomers).forEach((packageCustomerId) => {
cnt += this.packageCustomers[packageCustomerId].appointments.filter(app => app.checked).length
})
return cnt
},
deleteSelected () {
this.deleteLoading = true
let selectedAppointments = []
let selectedPackageCustomers = []
Object.keys(this.packageCustomers).forEach((packageCustomerId) => {
this.packageCustomers[packageCustomerId].appointments.forEach((appointment) => {
if (appointment.checked && !this.packageCustomers[packageCustomerId].checked) {
selectedAppointments.push(appointment.id)
}
})
if (this.packageCustomers[packageCustomerId].checked) {
selectedPackageCustomers.push(packageCustomerId)
}
})
Object.keys(this.packageCustomers).forEach((packageCustomerId) => {
this.packageCustomers[packageCustomerId].appointments.forEach((appointment) => {
if (appointment.checked && !this.packageCustomers[packageCustomerId].checked) {
this.form.post(`${this.$root.getAjaxUrl}/bookings/delete/` + appointment.booking.id)
.then(() => {
this.deleteSelectedCallback(selectedAppointments, selectedPackageCustomers, 'appointment', true)
})
.catch(() => {
this.deleteSelectedCallback(selectedAppointments, selectedPackageCustomers, 'appointment', false)
})
}
})
})
Object.keys(this.packageCustomers).forEach((packageCustomerId) => {
if (this.packageCustomers[packageCustomerId].checked) {
this.form.post(`${this.$root.getAjaxUrl}/packages/customers/delete/` + packageCustomerId)
.then(() => {
this.deleteSelectedCallback(selectedAppointments, selectedPackageCustomers, 'packageCustomer', true)
})
.catch(() => {
this.deleteSelectedCallback(selectedAppointments, selectedPackageCustomers, 'packageCustomer', false)
})
}
})
},
deleteSelectedCallback (selectedAppointments, selectedPackageCustomers, type, result) {
if (type === 'appointment') {
selectedAppointments.pop()
if (result) {
this.appointmentsDeleteCount.success++
} else {
this.appointmentsDeleteCount.error++
}
}
if (type === 'packageCustomer') {
selectedPackageCustomers.pop()
if (result) {
this.packageCustomersDeleteCount.success++
} else {
this.packageCustomersDeleteCount.error++
}
}
if (selectedAppointments.length === 0 && selectedPackageCustomers.length === 0) {
if (this.appointmentsDeleteCount.success) {
this.notify(
this.$root.labels.success,
this.appointmentsDeleteCount.success + ' ' + (this.appointmentsDeleteCount.success > 1 ? this.$root.labels.appointments_deleted : this.$root.labels.appointment_deleted),
'success')
}
if (this.appointmentsDeleteCount.error) {
this.notify(
this.$root.labels.error,
this.appointmentsDeleteCount.error + ' ' + (this.appointmentsDeleteCount.error > 1 ? this.$root.labels.appointments_not_deleted : this.$root.labels.appointment_not_deleted),
'error')
}
if (this.packageCustomersDeleteCount.success) {
this.notify(
this.$root.labels.success,
this.packageCustomersDeleteCount.success + ' ' + (this.packageCustomersDeleteCount.success > 1 ? this.$root.labels.package_customers_deleted : this.$root.labels.package_customer_deleted),
'success')
}
if (this.packageCustomersDeleteCount.error) {
this.notify(
this.$root.labels.error,
this.packageCustomersDeleteCount.error + ' ' + (this.packageCustomersDeleteCount.error > 1 ? this.$root.labels.package_customers_not_deleted : this.$root.labels.package_customer_not_deleted),
'error')
}
this.appointmentsDeleteCount.success = 0
this.appointmentsDeleteCount.error = 0
this.packageCustomersDeleteCount.success = 0
this.packageCustomersDeleteCount.error = 0
this.getAppointmentOptions(true)
this.toaster = false
this.deleteLoading = false
this.showDeleteConfirmation = false
this.showDeleteButton = true
}
},
packageBookingCallback () {
this.$emit('savePackageBookingCallback')
this.getAppointments()
},
getAppointmentOptions (fetchAppointments, first = false) {
this.options.fetched = false
this.customersLoading = true
this.fetchEntities((success) => {
if (success && fetchAppointments) {
this.customersLoading = false
this.getAppointments(first)
}
this.fetched = true
this.options.fetched = true
}, {
types: ['locations', 'employees', 'categories', 'custom_fields', 'packages', 'coupons', 'resources', 'coupons'],
lite: true,
page: 'appointments',
isFrontEnd: false,
isPanel: false
})
},
getAppointments (first = false) {
this.fetchedFiltered = false
let params = JSON.parse(JSON.stringify(this.params))
let dates = []
if (params.dates) {
if (params.dates.start) {
dates.push(moment(params.dates.start).format('YYYY-MM-DD'))
}
if (params.dates.end) {
dates.push(moment(params.dates.end).format('YYYY-MM-DD'))
}
params.dates = dates
}
params.page = this.paginationParams.page
params.packageId = this.purchasedPackage.id
Object.keys(params).forEach((key) => (!params[key] && params[key] !== 0) && delete params[key])
this.$http.get(`${this.$root.getAjaxUrl}/package/appointments`, {
params: this.getAppropriateUrlParams(params)
})
.then(response => {
this.toaster = false
let customersIds = this.options.entities.customers.map(customer => parseInt(customer.id))
let customers = this.options.entities.customers
let packageCustomers = {}
this.useSortedDateStrings(Object.keys(response.data.data.appointments)).forEach((dateKey) => {
response.data.data.appointments[dateKey].appointments.forEach((app) => {
app.checked = false
app.bookings.forEach((booking) => {
if (customersIds.indexOf(parseInt(booking.customer.id)) === -1) {
customersIds.push(booking.customer.id)
customers.push(booking.customer)
}
let packageCustomerId = parseInt(booking.packageCustomerService.packageCustomer.id)
if (!(packageCustomerId in packageCustomers)) {
packageCustomers[packageCustomerId] = {
status: booking.packageCustomerService.packageCustomer.status,
checked: false,
updating: false,
appointments: [],
count: 0,
total: 0,
data: []
}
}
let newPackageBooking =
{
status: booking.status === 'canceled' || booking.status === 'rejected' ? booking.status : app.status,
packageCustomerId: packageCustomerId,
canceled: false,
checked: false,
bookingStarts: app.bookingStart,
bookingEnd: app.bookingEnd,
provider: app.provider,
service: app.service,
id: app.id,
booking: booking
}
packageCustomers[packageCustomerId].appointments.push(newPackageBooking)
})
})
})
let emptyPackages = this.groupBy(response.data.data.emptyPackageBookings, 'packageCustomer')
if (emptyPackages) {
for (let pack in emptyPackages) {
if (customersIds.indexOf(parseInt(emptyPackages[pack][0].packageCustomer.customer.id)) === -1) {
customersIds.push(parseInt(emptyPackages[pack][0].packageCustomer.customer.id))
customers.push(emptyPackages[pack][0].packageCustomer.customer)
}
let packageCustomerId = parseInt(pack)
if (emptyPackages.hasOwnProperty(packageCustomerId)) {
if (!(pack in packageCustomers)) {
packageCustomers[packageCustomerId] = {
status: emptyPackages[pack][0].packageCustomer.status,
checked: false,
updating: false,
appointments: [],
count: 0,
total: 0,
data: []
}
}
let newPackageBooking =
{
status: null,
packageCustomerId: packageCustomerId,
canceled: false,
checked: false,
bookingStarts: null,
provider: null,
service: null,
id: null,
booking: {
customer: emptyPackages[pack][0].packageCustomer.customer,
packageCustomerService: emptyPackages[pack][0],
id: packageCustomerId,
payments: emptyPackages[pack][0].packageCustomer.payments
}
}
packageCustomers[packageCustomerId].appointments.push(newPackageBooking)
}
}
}
response.data.data.availablePackageBookings.forEach((customerData) => {
customerData.packages.forEach((packageData) => {
packageData.services.forEach((purchasePackageData) => {
purchasePackageData.bookings.forEach((purchaseData) => {
if (purchaseData.packageCustomerId in packageCustomers) {
if (purchaseData.sharedCapacity) {
if (packageCustomers[purchaseData.packageCustomerId].total === 0) {
packageCustomers[purchaseData.packageCustomerId].count = purchaseData.total - packageCustomers[purchaseData.packageCustomerId].appointments.filter(a => a.id && a.status !== 'canceled').length
packageCustomers[purchaseData.packageCustomerId].total = purchaseData.total
}
} else {
packageCustomers[purchaseData.packageCustomerId].count += purchaseData.count
packageCustomers[purchaseData.packageCustomerId].total += purchaseData.total
}
packageCustomers[purchaseData.packageCustomerId].data.push(purchasePackageData)
}
})
})
})
})
this.options.entities.customers = Object.values(customers.sort((a, b) => (a.firstName.toLowerCase() > b.firstName.toLowerCase()) ? 1 : -1))
this.packageCustomers = Object.keys(packageCustomers).length === 0 ? [] : packageCustomers
this.paginationParams.total = response.data.data.totalPackagePurchases
this.changedRange = false
this.fetched = true
this.fetchedFiltered = true
})
.catch(e => {
console.log(e.message)
this.fetched = true
this.fetchedFiltered = true
})
},
appointmentCameFrom (cust) {
let arr = []
cust.appointments.forEach(app => {
if (app.booking.info) {
arr.push('front')
} else {
arr.push('back')
}
})
if (arr.indexOf('front') !== -1 && arr.indexOf('back') !== -1) {
return 'am-mixed-appointment'
}
if (arr.indexOf('front') !== -1 && arr.indexOf('back') < 0) {
return 'am-front-appointment'
}
if (arr.indexOf('front') < 0 && arr.indexOf('back') !== -1) {
return 'am-back-appointment'
}
return ''
},
changeRange () {
this.setDatePickerSelectedDaysCount(this.params.dates.start, this.params.dates.end)
this.changedRange = true
this.changeFilter()
},
changeFilter () {
if (!this.params.customerId) {
this.searchedCustomers = []
}
this.paginationParams.page = 1
this.getAppointments()
},
handleResize () {
this.filterFields = window.innerWidth >= 992
},
handleCheckPackageAppointments (packageCustomerId) {
if (this.packageCustomers[packageCustomerId].checked) {
this.packageCustomers[packageCustomerId].appointments.filter(appointment => appointment.id !== null).forEach((appointment) => {
appointment.checked = this.packageCustomers[packageCustomerId].checked
})
}
Object.keys(this.packageCustomers).forEach((key) => {
if (key !== packageCustomerId) {
this.packageCustomers[key].checked = false
this.packageCustomers[key].appointments.filter(appointment => appointment.id !== null).forEach((appointment) => {
appointment.checked = false
})
}
})
this.handleToaster(packageCustomerId)
},
handleToaster (packageCustomerId) {
let hasAnyAppointmentChecked = false
let hasAnyPackageCustomerChecked = false
Object.keys(this.packageCustomers).forEach((key) => {
if (packageCustomerId !== key) {
this.packageCustomers[key].appointments.filter(appointment => appointment.id !== null).forEach((appointment) => {
appointment.checked = false
})
}
this.packageCustomers[key].appointments.filter(appointment => appointment.id !== null).forEach((appointment) => {
if (appointment.checked) {
hasAnyAppointmentChecked = true
}
})
if (this.packageCustomers[key].appointments.filter(appointment => appointment.id !== null).length !== 0 &&
(
this.packageCustomers[key].appointments.filter(appointment => appointment.checked && appointment.id !== null).length <
this.packageCustomers[key].appointments.filter(appointment => appointment.id !== null).length
)
) {
this.packageCustomers[key].checked = false
}
if (this.packageCustomers[key].checked) {
hasAnyPackageCustomerChecked = true
}
})
this.toaster = hasAnyAppointmentChecked || hasAnyPackageCustomerChecked
},
showDialogNewCustomer () {
this.customer = this.getInitCustomerObject()
this.dialogCustomer = true
},
closePackageBooking () {
this.$emit('closePackageBooking')
},
openRecurringAppointment (id) {
this.dialogAppointment = false
setTimeout(() => {
this.showDialogEditAppointment(id)
}, 200)
},
getPackagePaymentStatus (app) {
let allPayments = app[0].booking.payments
if (allPayments.every(p => p.status === 'refunded')) {
return 'refunded'
}
let bookingPrice = this.getPackageDiscountedPrice(app[0].booking.packageCustomerService.packageCustomer)
bookingPrice -= allPayments.filter(p => p.wcOrderId && p.wcItemCouponValue).reduce((partialSum, a) => partialSum + a.wcItemCouponValue, 0)
bookingPrice += allPayments.filter(p => p.wcOrderId && p.wcItemTaxValue).reduce((partialSum, a) => partialSum + a.wcItemTaxValue, 0)
let paidAmount = allPayments.filter(p => p.status !== 'pending' && p.status !== 'refunded').reduce((partialSum, a) => partialSum + a.amount, 0)
if (Math.round(paidAmount * 100) / 100 >= Math.round(bookingPrice * 100) / 100) {
return 'paid'
} else if (paidAmount > 0) {
return 'partiallyPaid'
}
return 'pending'
},
updatePackageAppointmentBookingStatusCallback (booking, packageCustomer, originalStatus) {
this.updateAppointmentBookingStatus(booking, booking.status, packageCustomer, (success) => {
if ((originalStatus === 'approved' || originalStatus === 'pending') && (booking.status === 'canceled' || booking.status === 'rejected')) {
packageCustomer.count++
} else if ((originalStatus === 'canceled' || originalStatus === 'rejected') && (booking.status === 'approved' || booking.status === 'pending')) {
packageCustomer.count--
}
})
},
updatePackageCustomerStatus (packageCustomerId, status) {
this.packageCustomers[packageCustomerId].updating = true
this.form.post(this.$root.getAjaxUrl + '/packages/customers/' + packageCustomerId, {
'status': status
})
.then(response => {
this.packageCustomers[packageCustomerId].updating = false
this.packageCustomers[packageCustomerId].status = status
this.notify(this.$root.labels.success, response.message, 'success')
})
.catch(error => {
this.packageCustomers[packageCustomerId].updating = false
if (error.response) {
this.notify(this.$root.labels.error, error.response.data.message, 'error')
}
})
},
groupBy (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key].id] = rv[x[key].id] || []).push(x)
return rv
}, {})
}
},
computed: {
filterApplied () {
return !!this.params.services.length || !!this.params.providers.length || !!this.params.customerId ||
(this.params.dates && (!!this.params.dates.start || !!this.params.dates.end)) || !!this.params.status ||
!!this.params.packageStatus
}
},
watch: {
},
components: {
PaginationBlock,
PageHeader,
DialogExport,
DialogAppointment,
DialogPackageBooking,
DialogCustomer,
DialogPayment,
PackagesListCollapsed
}
}
</script>