FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
ameliabooking
/
assets
/
views
/
backend
/
finance
Edit File: Finance.vue
<template> <div class="am-wrap"> <div id="am-finance" class="am-body"> <!-- Page Header --> <page-header :financeTotal="options.fetched ? getTotal() : 0" :addNewTaxBtnDisplay="options.fetched ? addNewTaxBtnDisplay : false" @newTaxBtnClicked="showDialogNewTax()" :addNewCouponBtnDisplay="options.fetched ? addNewCouponBtnDisplay : false" @newCouponBtnClicked="showDialogNewCoupon()" > </page-header> <!-- Finance --> <div class="am-finances am-section"> <el-tabs v-model="financeTabs" @tab-click="handleTabClick"> <el-tab-pane :label="$root.labels.payments" name="payments"> <!-- Filter Finance --> <div class="am-finance-filter" v-show="hasPayments && options.fetched" > <el-form class="" :action="exportPaymentsAction" method="POST"> <el-row :gutter="16"> <!-- Date Filter --> <el-col :md="24" :lg="5" class="v-calendar-column"> <el-form-item class="calc-width-mobile"> <v-date-picker @input="changeRange" v-model="paymentsParams.dates" :is-double-paned="true" 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" :placeholder="$root.labels.pick_a_date" :formats="vCalendarFormats" > </v-date-picker> <span v-if="paymentsParams.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> <transition name="fade"> <div class="am-filter-fields" v-show="filterPaymentsFields"> <!-- Customers Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="paymentsParams.customerId" filterable clearable :placeholder="$root.labels.customer" @change="filterPayments" remote :remote-method="searchCustomers" :loading="loadingCustomers" > <el-option v-for="(item, key) in searchedCustomers.length ? searchedCustomers : options.entities.customers" :key="key" :label="item.firstName + ' ' + item.lastName" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> <!-- Employees Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="paymentsParams.providerId" filterable clearable :placeholder="$root.labels.employee" @change="filterPayments" > <el-option v-for="item in visibleEmployees" :key="item.id" :label="item.firstName + ' ' + item.lastName" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> <!-- Services Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="paymentsParams.services" multiple filterable :placeholder="$root.labels.services" @change="filterPayments" collapse-tags > <div v-for="category in options.entities.categories" :key="category.id"> <div class="am-drop-parent" @click="selectAllInCategoryFinance(category.id)" > <span>{{ category.name }}</span> </div> <el-option v-for="service in category.serviceList" :key="service.id" :label="service.name" :value="service.id" class="am-drop-child" > </el-option> </div> </el-select> </el-form-item> </el-col> <!-- Events Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="paymentsParams.events" multiple clearable filterable :remote-method="query => searchEvents(query, false, options.entities.events)" :loading="loadingEvents" :remote="options.entities.events.length === $root.settings.general.eventsFilterLimit" :placeholder="$root.labels.events" @change="filterPayments" > <el-option v-for="item in searchedEvents" :key="item.id" :label="item.name + ( item.periods && item.periods.length ? ' (' + getFrontedFormattedDate(item.periods[0].periodStart.split(' ')[0]) + ')' : '')" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> <!-- Status Filter --> <el-col :md="6" :lg="3"> <el-form-item> <el-select v-model="paymentsParams.status" clearable :placeholder="$root.labels.status" class="calc-width" @change="filterPayments" > <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" > <span>{{ item.label }}</span> </el-option> </el-select> </el-form-item> </el-col> </div> </transition> <div class=""> <!-- Toggle More Filters --> <el-button class="button-filter-toggle am-button-icon" title="Toggle Filters" @click="filterPaymentsFields = !filterPaymentsFields" > <img class="svg-amelia" alt="Toggle Filters" :src="$root.getUrl + 'public/img/filter.svg'"/> </el-button> <!-- Export Payments --> <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.export_tooltip_payments"></div> <el-button class="button-export am-button-icon" @click="dialogPaymentsExport = true" > <img class="svg-amelia" alt="Export" :src="$root.getUrl+'public/img/export.svg'"/> </el-button> </el-tooltip> </div> </el-row> <!-- Dialog Export Payments --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-export" :visible.sync="dialogPaymentsExport" :show-close="false" v-if="dialogPaymentsExport" > <dialog-export :data="getExportData()" :action="$root.getAjaxUrl + '/report/payments'" @updateAction="(action) => {this.exportPaymentsAction = action}" @closeDialogExport="dialogPaymentsExport = false" > </dialog-export> </el-dialog> </transition> </el-form> </div> <!-- Spinner --> <div class="am-spinner am-section" v-show="isPaymentsFiltering || !options.fetched" > <img :src="$root.getUrl + 'public/img/spinner.svg'"/> </div> <!-- Empty State --> <EmptyState :visible="!hasPayments && !isPaymentsFiltering && options.fetched" :title="$root.labels.no_payments_yet" > </EmptyState> <!-- Table Header --> <div class="am-finance-list-head" v-show="hasPayments && hasPaymentsFiltered && !isPaymentsFiltering && options.fetched" > <el-row> <!-- Payment Date, Customer, Employee --> <el-col :lg="14"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <!-- Check All --> <el-col :lg="2" v-if="$root.settings.capabilities.canDelete === true"> <p> <el-checkbox :style="{display: 'none'}" v-model="checkPaymentData.allChecked" @change="checkPaymentData = handleCheckAll(payments, checkPaymentData)"> </el-checkbox> </p> </el-col> <!-- Payment Date --> <el-col :lg="6"> <p>{{ $root.labels.payment_date }}:</p> </el-col> <!-- Customer --> <el-col :lg="8"> <p>{{ $root.labels.customer }}:</p> </el-col> <!-- Employee --> <el-col :lg="8"> <p>{{ $root.labels.employee }}:</p> </el-col> </el-row> </el-col> <!-- Service, Status --> <el-col :lg="10"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="12"> <p>{{ $root.labels.service }}/{{ $root.labels.event }}:</p> </el-col> <!-- Status --> <el-col :lg="12"> <p>{{ $root.labels.status }}:</p> </el-col> </el-row> </el-col> </el-row> </div> <!-- Collapsible Data --> <div class="am-finance-list" v-show="hasPayments && hasPaymentsFiltered && !isPaymentsFiltering && options.fetched" > <el-collapse> <el-collapse-item v-for="(payment, index) in payments" :key="payment.id" :name="payment.id" class="am-finance" > <template slot="title"> <div class="am-finance-data"> <span class="am-entity-color am-event-color" v-if="payment.appointmentId === 0"> </span> <span class="am-entity-color am-appointment-color" v-else> </span> <el-row> <!-- Payment Date, Customer, Employee --> <el-col :lg="14"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <!-- Checkbox --> <el-col :lg="2" :sm="1" v-if="$root.settings.capabilities.canDelete === true"> <span @click.stop> <el-checkbox :style="{display: 'none'}" v-model="payment.checked" @change="checkPaymentData = handleCheckSingle(payments, checkPaymentData)"> </el-checkbox> </span> </el-col> <!-- Payment Date --> <el-col :lg="6" :sm="6"> <p class="am-col-title">{{ $root.labels.payment_date }}:</p> <h4>{{ getFrontedFormattedDate(payment.dateTime) }}</h4> </el-col> <!-- Customer --> <el-col :lg="8" :sm="8"> <p class="am-col-title">{{ $root.labels.customer }}:</p> <h3 :class="getNoShowClass(payment.customerId, null, null, payment.customerStatus)"> {{ payment.customerFirstName + ' ' + payment.customerLastName }} </h3> <span>{{ payment.customerEmail }}</span> </el-col> <!-- Employee --> <el-col :lg="8" :sm="8"> <p class="am-col-title">{{ $root.labels.employee }}:</p> <div class="am-assigned" v-if="payment.providers.length === 1"> <img :src="pictureLoad(getProviderById(payment.providers[0].id), true)" @error="imageLoadError(getProviderById(payment.providers[0].id), true)"/> <h4>{{ ((user = getProviderById(payment.providers[0].id)) !== null ? user.firstName + ' ' + user.lastName : '') }}</h4> </div> <!-- Multiple Employees --> <el-tooltip placement="top-start" v-if="payment.providers.length > 1"> <div class="am-all-event-employees" slot="content"> <div v-for="pp in payment.providers" :key="pp.id"> {{ ((user = getProviderById(pp.id)) !== null ? user.firstName + ' ' + user.lastName : '') }} </div> </div> <div class="am-assigned am-multiple-employees"> <img v-if="index <= 4" v-for="(pp, index) in payment.providers" :key="pp.id" :src="pictureLoad(getProviderById(pp.id), true)" @error="imageLoadError(getProviderById(pp.id), true)"/> <h4 v-if="index > 4 && (payment.providers.length - 5 > 0)"> + {{ payment.providers.length - 5 }}</h4> </div> </el-tooltip> </el-col> </el-row> </el-col> <el-col :lg="10"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" :sm="1" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="12" :sm="14" class="am-payment-service"> <p class="am-col-title">{{ $root.labels.service }}/{{ $root.labels.event }}:</p> <img v-if="payment.packageId" :src="$root.getUrl + 'public/img/am-package-black.svg'" > <h4>{{ payment.name }}</h4> </el-col> <!-- Status --> <el-col class="am-finance-payment-status" :lg="6" :sm="5"> <p class="am-col-title">{{ $root.labels.status }}:</p> <div class="am-payment-status"> <span :class="'am-payment-status-symbol am-payment-status-symbol-' + payment.fullStatus"></span> <h4> {{ getPaymentStatusNiceName(payment.fullStatus) }} </h4> </div> </el-col> <!-- Details Button --> <el-col :lg="6" :sm="4" class="align-right"> <div @click.stop> <el-button @click="showDialogEditPayment(payment)"> {{ $root.labels.details }} </el-button> </div> </el-col> </el-row> </el-col> </el-row> </div> </template> <div class="am-finance-details"> <el-row> <!-- Appointment Date --> <el-col :lg="14"> <el-row :gutter="10" class="am-finance-flex-row-top-align"> <el-col :lg="2" :sm="1" class="hide-on-mobile"></el-col> <el-col :lg="22" :sm="23"> <p class="am-data"> {{ (payment.packageId ? $root.labels.package_purchased : !payment.appointmentId ? $root.labels.event_date : $root.labels.appointment_date) }}: </p> <p class="am-value"> {{ getFrontedFormattedDateTime(payment.packageId ? payment.dateTime : payment.bookingStart) }} </p> </el-col> </el-row> </el-col> <!-- Type, Amount --> <el-col :lg="10"> <el-row :gutter="10" class="am-finance-flex-row-top-align"> <el-col :lg="0" :sm="1" class="hide-on-mobile"></el-col> <!-- Type --> <el-col class="am-finance-payment-gateway" :lg="8" :sm="8"> <p class="am-data">{{ $root.labels.method }}:</p> <div style="margin-bottom: 8px"> <img :src="$root.getUrl + 'public/img/payments/' + getPaymentIconName(payment)" :style="{width: getPaymentIconWidth(payment.gateway)}"> <p class="am-value" v-if="!longNamePayments(payment.gateway)">{{ getPaymentGatewayNiceName(payment) }}</p> </div> <div v-for="payment2 in payment.secondaryPayments.filter(p => p.status !== 'pending' && p.status !== 'refunded')" style="margin-bottom: 8px"> <img :src="$root.getUrl + 'public/img/payments/' + getPaymentIconName(payment2)" :style="{width: getPaymentIconWidth(payment2.gateway)}"> <p class="am-value" v-if="!longNamePayments(payment2.gateway)">{{ getPaymentGatewayNiceName(payment2) }}</p> </div> </el-col> <!-- Amount --> <el-col :lg="8" :sm="8"> <p class="am-data">{{ $root.labels.amount }}:</p> <p class="am-value">{{ getFormattedPrice(payment.status === 'paid' || payment.status === 'partiallyPaid' ? payment.amount : 0) }}</p> <div v-for="payment2 in payment.secondaryPayments.filter(p => p.status !== 'pending' && p.status !== 'refunded')"> <p class="am-value">{{ getFormattedPrice(payment.status === 'paid' || payment.status === 'partiallyPaid' ? payment2.amount : 0) }}</p> </div> </el-col> <!-- ID --> <el-col :lg="8" :sm="8"> <p class="am-data">{{ $root.labels.id }}:</p> <p class="am-value">{{ payment.id }}</p> <div v-for="payment2 in payment.secondaryPayments.filter(p => p.status !== 'pending' && p.status !== 'refunded')"> <p class="am-value">{{ payment2.id }}</p> </div> </el-col> </el-row> </el-col> </el-row> </div> </el-collapse-item> </el-collapse> </div> <!-- No Results --> <div class="am-empty-state am-section" v-show="hasPayments && !hasPaymentsFiltered && !isPaymentsFiltering && options.fetched"> <img :src="$root.getUrl + 'public/img/emptystate.svg'"> <h2>{{ $root.labels.no_results }}</h2> </div> <!-- Pagination --> <pagination-block :params="paymentsParams" :count="paymentsFilteredCount" :show="paymentsParams.show" :label="$root.labels.payments_lower" :visible="hasPayments && hasPaymentsFiltered && !isPaymentsFiltering && options.fetched" @change="filterPayments" > </pagination-block> </el-tab-pane> <el-tab-pane :label="$root.labels.coupons" name="coupons" v-if="$root.settings.payments.coupons && (notInLicence('starter') ? licenceVisible() : true)" > <!-- Filter Coupons --> <div class="am-finance-filter" v-show="hasCoupons && options.fetched"> <el-form class="" :action="exportCouponsAction" method="POST"> <el-row :gutter="16"> <el-col :md="24" :lg="8"> <div class="am-search"> <el-form-item> <el-input class="calc-width-mobile" :placeholder="searchCouponsPlaceholder" v-model="couponsParams.search" > </el-input> </el-form-item> </div> </el-col> <transition name="fade"> <div class="am-filter-fields" v-show="filterCouponsFields"> <!-- Services Filter --> <el-col :md="24" :lg="8"> <el-form-item> <el-select v-model="couponsParams.services" multiple filterable :placeholder="$root.labels.services" @change="filterCoupons" collapse-tags > <div v-for="category in options.entities.categories" :key="category.id"> <div class="am-drop-parent" @click="selectAllInCategoryCoupons(category.id)" > <span>{{ category.name }}</span> </div> <el-option v-for="service in category.serviceList" :key="service.id" :label="service.name" :value="service.id" class="am-drop-child" > </el-option> </div> </el-select> </el-form-item> </el-col> <!-- Events Filter --> <el-col :md="24" :lg="7"> <el-form-item> <el-select v-model="couponsParams.events" multiple filterable :placeholder="$root.labels.events" @change="filterCoupons" collapse-tags > <el-option v-for="event in options.entities.events" :key="event.id" :label="event.name + ( event.periods && event.periods.length ? ' (' + getFrontedFormattedDate(event.periods[0].periodStart.split(' ')[0]) + ')' : '')" :value="event.id" class="am-drop-child" > </el-option> </el-select> </el-form-item> </el-col> </div> </transition> <!-- Toggle More Filters --> <div class=""> <el-button class="button-filter-toggle am-button-icon" title="Toggle Filters" @click="filterCouponsFields = !filterCouponsFields"> <img class="svg-amelia" alt="Toggle Filters" :src="$root.getUrl+'public/img/filter.svg'"/> </el-button> <!-- Export Coupons --> <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.export_tooltip_coupons"></div> <el-button class="button-export am-button-icon" @click="dialogCouponsExport = true" > <img class="svg-amelia" alt="Export" :src="$root.getUrl+'public/img/export.svg'"/> </el-button> </el-tooltip> </div> </el-row> <!-- Dialog Export Coupons --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-export" :visible.sync="dialogCouponsExport" :show-close="false" v-if="dialogCouponsExport" > <dialog-export :data="Object.assign(couponsParams, exportParamsCoupons)" :action="$root.getAjaxUrl + '/report/coupons'" @updateAction="(action) => {this.exportCouponsAction = action}" @closeDialogExport="dialogCouponsExport = false" > </dialog-export> </el-dialog> </transition> </el-form> </div> <!-- Spinner --> <div class="am-spinner am-section" v-show="isCouponsFiltering || !options.fetched" > <img :src="$root.getUrl + 'public/img/spinner.svg'"/> </div> <!-- Empty State --> <EmptyState :visible="!hasCoupons && !isCouponsFiltering" :licence="'starter'" :title="$root.labels.no_coupons_yet" > </EmptyState> <!-- /Empty State --> <!-- Coupons Table Header --> <div class="am-finance-list-head" v-show="hasCoupons && hasCouponsFiltered && !isCouponsFiltering && options.fetched" > <el-row> <!-- Checkbox, Code, Discount, Deduction --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 7:8"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <!-- Checkbox --> <el-col :lg="2" v-if="$root.settings.capabilities.canDelete === true"> <p> <el-checkbox v-model="checkCouponData.allChecked" @change="checkCouponData = handleCheckAll(coupons, checkCouponData)"> </el-checkbox> </p> </el-col> <!-- Code --> <el-col :lg="6"> <p>{{ $root.labels.code }}:</p> </el-col> <!-- Discount --> <el-col :lg="8"> <p>{{ $root.labels.discount }}:</p> </el-col> <!-- Deduction --> <el-col :lg="8"> <p>{{ $root.labels.deduction }}:</p> </el-col> </el-row> </el-col> <!-- Service, Usage, Number of Times Used --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 17:16"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 4:6"> <p>{{ $root.labels.service }}:</p> </el-col> <!-- Package --> <el-col :lg="4" v-if="$root.licence.isPro || $root.licence.isDeveloper"> <p>{{ $root.labels.package }}:</p> </el-col> <!-- Event --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 4:6"> <p>{{ $root.labels.event }}:</p> </el-col> <!-- Usage Limit --> <el-col :lg="3"> <p>{{ $root.labels.usage_limit }}:</p> </el-col> <!-- Number of Times Used --> <el-col :lg="3"> <p>{{ $root.labels.times_used }}:</p> </el-col> <!-- Expiration Date --> <el-col :lg="3"> <p>{{ $root.labels.expiration_date }}:</p> </el-col> </el-row> </el-col> </el-row> </div> <div class="am-finance-list" v-show="hasCoupons && hasCouponsFiltered && !isCouponsFiltering && options.fetched" > <div v-for="coupon in coupons" :class="{'am-coupon-row am-hidden-entity' : coupon.status === 'hidden', 'am-coupon-row' : coupon.status === 'visible'}" > <el-row> <!-- Checkbox, Code, Discount, Deduction --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 7:8"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <!-- Checkbox --> <el-col :lg="2" :sm="1" v-if="$root.settings.capabilities.canDelete === true"> <span @click.stop> <el-checkbox v-model="coupon.checked" @change="checkCouponData = handleCheckSingle(coupons, checkCouponData)" > </el-checkbox> </span> </el-col> <!-- Code --> <el-col :lg="6" :sm="9"> <p class="am-col-title">{{ $root.labels.code }}:</p> <h4>{{ coupon.code }}</h4> </el-col> <!-- Discount --> <el-col :lg="8" :sm="5"> <p class="am-col-title">{{ $root.labels.discount }}:</p> <h4>{{ coupon.discount }}</h4> </el-col> <!-- Deduction --> <el-col :lg="8" :sm="5"> <p class="am-col-title">{{ $root.labels.deduction }}:</p> <h4>{{ getFormattedPrice(coupon.deduction) }}</h4> </el-col> </el-row> </el-col> <!-- Service, Usage, Number of Times Used --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 17:16"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" :sm="1" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 4:6" :sm="9"> <p class="am-col-title">{{ $root.labels.service }}:</p> <h4 v-if="coupon.allServices">{{$root.labels.all_services}}</h4> <h4 v-else> {{coupon.serviceList.length > 0 ? (coupon.serviceList[0].name) : ''}} <br> <span> {{ (coupon.serviceList.length > 1 ? (coupon.serviceList.length === 2 ? ('+' + (coupon.serviceList.length - 1) + ' ' + $root.labels.service) : ('+' + (coupon.serviceList.length - 1) + ' ' + $root.labels.services) ) : '') }} </span> </h4> </el-col> <!-- Package --> <el-col :lg="4" :sm="9" v-if="$root.licence.isPro || $root.licence.isDeveloper"> <p class="am-col-title">{{ $root.labels.package }}:</p> <h4 v-if="coupon.allPackages">{{$root.labels.all_packages}}</h4> <h4 v-else> {{coupon.packageList.length > 0 ? (coupon.packageList[0].name) : ''}} <br> <span> {{ (coupon.packageList.length > 1 ? (coupon.packageList.length === 2 ? ('+' + (coupon.packageList.length - 1) + ' ' + $root.labels.package) : ('+' + (coupon.packageList.length - 1) + ' ' + $root.labels.packages) ) : '') }} </span> </h4> </el-col> <!-- Event --> <el-col :lg="($root.licence.isPro || $root.licence.isDeveloper) ? 4:6" :sm="9"> <p class="am-col-title">{{ $root.labels.event }}:</p> <h4 v-if="coupon.allEvents">{{$root.labels.all_events}}</h4> <h4 v-else> {{coupon.eventList.length > 0 ? (coupon.eventList[0].name) : ''}} <br> <span> {{ (coupon.eventList.length > 1 ? (coupon.eventList.length === 2 ? ('+' + (coupon.eventList.length - 1) + ' ' + $root.labels.event) : ('+' + (coupon.eventList.length - 1) + ' ' + $root.labels.events) ) : '') }} </span> </h4> </el-col> <!-- Usage Limit --> <el-col :lg="3" :sm="5"> <p class="am-col-title">{{ $root.labels.usage_limit }}:</p> <h4>{{ coupon.limit }}</h4> </el-col> <!-- Number of Times Used --> <el-col :lg="3" :sm="5"> <p class="am-col-title">{{ $root.labels.times_used }}:</p> <h4>{{ coupon.used }}</h4> </el-col> <!-- Expiration Date --> <el-col :lg="3" :sm="5"> <p class="am-col-title">{{ $root.labels.expiration_date }}:</p> <h4>{{ coupon.expirationDate ? getFrontedFormattedDate(coupon.expirationDate) : '/'}}</h4> </el-col> <!-- Edit Button --> <el-col :lg="3" :sm="4" class="align-right"> <div @click.stop> <el-button @click="showDialogEditCoupon(coupon.id)">{{ $root.labels.edit }}</el-button> </div> </el-col> </el-row> </el-col> </el-row> </div> </div> <!-- No Results --> <div class="am-empty-state am-section" v-show="hasCoupons && !hasCouponsFiltered && !isCouponsFiltering && options.fetched"> <img :src="$root.getUrl + 'public/img/emptystate.svg'"> <h2>{{ $root.labels.no_results }}</h2> </div> <!-- Pagination --> <pagination-block :params="couponsParams" :count="couponsFilteredCount" :label="$root.labels.coupons_lower" :visible="hasCoupons && hasCouponsFiltered && !isCouponsFiltering && options.fetched" @change="filterCoupons" > </pagination-block> </el-tab-pane> <el-tab-pane :label="$root.labels.taxes" name="taxes" v-if="$root.settings.payments.taxes.enabled && (notInLicence() ? licenceVisible() : true)" > <!-- Filter Taxes --> <div class="am-finance-filter" v-show="hasTaxes && options.fetched"> <el-form class=""> <el-row :gutter="16"> <el-col :md="24" :lg="4"> <div class="am-search"> <el-form-item> <el-input class="calc-width-mobile-tax" :placeholder="searchTaxesPlaceholder" v-model="taxesParams.search" > </el-input> </el-form-item> </div> </el-col> <transition name="fade"> <div class="am-filter-fields" v-show="filterTaxesFields"> <!-- Services Filter --> <el-col :md="24" :lg="5"> <el-form-item> <el-select v-model="taxesParams.services" multiple filterable :placeholder="$root.labels.services" @change="filterTaxes" collapse-tags > <div v-for="category in options.entities.categories" :key="category.id"> <div class="am-drop-parent" @click="selectAllInCategoryTaxes(category.id)" > <span>{{ category.name }}</span> </div> <el-option v-for="service in category.serviceList" :key="service.id" :label="service.name" :value="service.id" class="am-drop-child" > </el-option> </div> </el-select> </el-form-item> </el-col> <!-- Extras Filter --> <el-col :md="24" :lg="5"> <el-form-item> <el-select v-model="taxesParams.extras" multiple filterable :placeholder="$root.labels.extras" @change="filterTaxes" collapse-tags > <div v-for="service in options.entities.services" :key="service.id" v-if="service.extras.length"> <div class="am-drop-parent" @click="selectAllInServiceTaxes(service.id)" > <span>{{ service.name }} ({{getCategoryById(service.categoryId).name}})</span> </div> <el-option v-for="extra in service.extras" :key="extra.id" :label="extra.name" :value="extra.id" class="am-drop-child" > </el-option> </div> </el-select> </el-form-item> </el-col> <!-- Packages Filter --> <el-col :md="24" :lg="5"> <el-form-item> <el-select v-model="taxesParams.packages" multiple filterable :placeholder="$root.labels.packages" @change="filterTaxes" collapse-tags > <el-option v-for="pack in options.entities.packages" :key="pack.id" :label="pack.name" :value="pack.id" class="am-drop-child" > </el-option> </el-select> </el-form-item> </el-col> <!-- Events Filter --> <el-col :md="24" :lg="5"> <el-form-item> <el-select v-model="taxesParams.events" multiple filterable :placeholder="$root.labels.events" @change="filterTaxes" collapse-tags > <el-option v-for="event in options.entities.events" :key="event.id" :label="event.name + ( event.periods && event.periods.length ? ' (' + getFrontedFormattedDate(event.periods[0].periodStart.split(' ')[0]) + ')' : '')" :value="event.id" class="am-drop-child" > </el-option> </el-select> </el-form-item> </el-col> </div> </transition> <!-- Toggle More Filters --> <div class=""> <el-button class="button-filter-toggle am-button-icon" title="Toggle Filters" style="right: 8px;" @click="filterTaxesFields = !filterTaxesFields"> <img class="svg-amelia" alt="Toggle Filters" :src="$root.getUrl+'public/img/filter.svg'"/> </el-button> </div> </el-row> </el-form> </div> <!-- Spinner --> <div class="am-spinner am-section" v-show="isTaxesFiltering || !options.fetched" > <img :src="$root.getUrl + 'public/img/spinner.svg'"/> </div> <!-- Empty State --> <EmptyState :visible="!hasTaxes && !isTaxesFiltering" :licence="'basic'" :title="$root.labels.no_taxes_yet" > </EmptyState> <!-- /Empty State --> <!-- Taxes Table Header --> <div class="am-finance-list-head" v-show="hasTaxes && hasTaxesFiltered && !isTaxesFiltering && options.fetched" > <el-row> <!-- Checkbox, Name, Amount --> <el-col :lg="7"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <!-- Checkbox --> <el-col :lg="2" v-if="$root.settings.capabilities.canDelete === true"> <p> <el-checkbox v-model="checkTaxData.allChecked" @change="checkTaxData = handleCheckAll(coupons, checkTaxData)"> </el-checkbox> </p> </el-col> <!-- Name --> <el-col :lg="11"> <p>{{ $root.labels.name }}:</p> </el-col> <!-- Rate --> <el-col :lg="11"> <p>{{ $root.labels.rate }}:</p> </el-col> </el-row> </el-col> <!-- Service, Extra, Event, Package --> <el-col :lg="17"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="5"> <p>{{ $root.labels.service }}:</p> </el-col> <!-- Extra --> <el-col :lg="5"> <p>{{ $root.labels.extra }}:</p> </el-col> <!-- Package --> <el-col :lg="5"> <p>{{ $root.labels.package }}:</p> </el-col> <!-- Event --> <el-col :lg="5"> <p>{{ $root.labels.event }}:</p> </el-col> </el-row> </el-col> </el-row> </div> <div class="am-finance-list" v-show="hasTaxes && hasTaxesFiltered && !isTaxesFiltering && options.fetched" > <div v-for="tax in taxes" :class="{'am-coupon-row am-hidden-entity' : tax.status === 'hidden', 'am-coupon-row' : tax.status === 'visible'}" > <el-row> <!-- Checkbox, Name, Amount --> <el-col :lg="7"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <!-- Checkbox --> <el-col :lg="2" :sm="1" v-if="$root.settings.capabilities.canDelete === true"> <span @click.stop> <el-checkbox v-model="tax.checked" @change="checkTaxData = handleCheckSingle(taxes, checkTaxData)" > </el-checkbox> </span> </el-col> <!-- Name --> <el-col :lg="11" :sm="11"> <p class="am-col-title">{{ $root.labels.name }}:</p> <h4>{{ tax.name }}</h4> </el-col> <!-- Rate --> <el-col :lg="11" :sm="11"> <p class="am-col-title">{{ $root.labels.rate }}:</p> <h4>{{ tax.type === 'percentage' ? tax.amount + '%' : getFormattedPrice(tax.amount) }}</h4> </el-col> </el-row> </el-col> <!-- Service, Extra, Event, Package --> <el-col :lg="17"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" :sm="1" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="5" :sm="9"> <p class="am-col-title">{{ $root.labels.service }}:</p> <h4 v-if="tax.allServices">{{$root.labels.all_services}}</h4> <h4 v-else> {{tax.serviceList.length > 0 ? (tax.serviceList[0].name) : ''}} <br> <span> {{ (tax.serviceList.length > 1 ? (tax.serviceList.length === 2 ? ('+' + (tax.serviceList.length - 1) + ' ' + $root.labels.service) : ('+' + (tax.serviceList.length - 1) + ' ' + $root.labels.services) ) : '') }} </span> </h4> </el-col> <!-- Extra --> <el-col :lg="5" :sm="9"> <p class="am-col-title">{{ $root.labels.extra }}:</p> <h4 v-if="tax.allExtras">{{$root.labels.all_extras}}</h4> <h4 v-else> {{tax.extraList.length > 0 ? (tax.extraList[0].name) : ''}} <br> <span> {{ (tax.extraList.length > 1 ? (tax.extraList.length === 2 ? ('+' + (tax.extraList.length - 1) + ' ' + $root.labels.extra) : ('+' + (tax.extraList.length - 1) + ' ' + $root.labels.extras) ) : '') }} </span> </h4> </el-col> <!-- Package --> <el-col :lg="5" :sm="9"> <p class="am-col-title">{{ $root.labels.package }}:</p> <h4 v-if="tax.allPackages">{{$root.labels.all_packages}}</h4> <h4 v-else> {{tax.packageList.length > 0 ? (tax.packageList[0].name) : ''}} <br> <span> {{ (tax.packageList.length > 1 ? (tax.packageList.length === 2 ? ('+' + (tax.packageList.length - 1) + ' ' + $root.labels.package) : ('+' + (tax.packageList.length - 1) + ' ' + $root.labels.packages) ) : '') }} </span> </h4> </el-col> <!-- Event --> <el-col :lg="5" :sm="9"> <p class="am-col-title">{{ $root.labels.event }}:</p> <h4 v-if="tax.allEvents">{{$root.labels.all_events}}</h4> <h4 v-else> {{tax.eventList.length > 0 ? (tax.eventList[0].name) : ''}} <br> <span> {{ (tax.eventList.length > 1 ? (tax.eventList.length === 2 ? ('+' + (tax.eventList.length - 1) + ' ' + $root.labels.event) : ('+' + (tax.eventList.length - 1) + ' ' + $root.labels.events) ) : '') }} </span> </h4> </el-col> <!-- Edit Button --> <el-col :lg="4" :sm="4" class="align-right"> <div @click.stop> <el-button @click="showDialogEditTax(tax.id)">{{ $root.labels.edit }}</el-button> </div> </el-col> </el-row> </el-col> </el-row> </div> </div> <!-- No Results --> <div class="am-empty-state am-section" v-show="hasTaxes && !hasTaxesFiltered && !isTaxesFiltering && options.fetched"> <img :src="$root.getUrl + 'public/img/emptystate.svg'"> <h2>{{ $root.labels.no_results }}</h2> </div> <!-- Pagination --> <pagination-block :params="taxesParams" :count="taxesFilteredCount" :label="$root.labels.taxes_lower" :visible="hasTaxes && hasTaxesFiltered && !isTaxesFiltering && options.fetched" @change="filterTaxes" > </pagination-block> </el-tab-pane> <el-tab-pane :label="$root.labels.invoices" name="invoices" v-if="notInLicence() ? licenceVisible() : true" > <!-- Filter Finance --> <div class="am-finance-filter" v-show="hasInvoices && options.fetched" > <el-form class=""> <el-row :gutter="16"> <!-- Date Filter --> <el-col :md="24" :lg="6" class="v-calendar-column"> <el-form-item class="calc-width-mobile-invoice"> <v-date-picker @input="changeRangeInvoices" v-model="invoiceParams.dates" :is-double-paned="true" 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" :placeholder="$root.labels.pick_a_date" :formats="vCalendarFormats" > </v-date-picker> <span v-if="invoiceParams.dates" class="am-v-date-picker-suffix el-input__suffix-inner" @click="clearInvoiceDateFilter()" > <i class="el-select__caret el-input__icon el-icon-circle-close"></i> </span> </el-form-item> </el-col> <transition name="fade"> <div class="am-filter-fields" v-show="filterInvoicesFields"> <!-- Customers Filter --> <el-col :md="6" :lg="3"> <el-form-item> <el-select v-model="invoiceParams.customerId" filterable clearable :placeholder="$root.labels.customer" @change="filterInvoices" remote :remote-method="searchCustomers" :loading="loadingCustomers" > <el-option v-for="(item, key) in searchedCustomers.length ? searchedCustomers : options.entities.customers" :key="key" :label="item.firstName + ' ' + item.lastName" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> <!-- Employees Filter --> <el-col :md="6" :lg="3"> <el-form-item> <el-select v-model="invoiceParams.providerId" filterable clearable :placeholder="$root.labels.employee" @change="filterInvoices" > <el-option v-for="item in visibleEmployees" :key="item.id" :label="item.firstName + ' ' + item.lastName" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> <!-- Services Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="invoiceParams.services" multiple filterable :placeholder="$root.labels.services" @change="filterInvoices" collapse-tags > <div v-for="category in options.entities.categories" :key="category.id"> <div class="am-drop-parent" @click="selectAllInCategoryFinance(category.id)" > <span>{{ category.name }}</span> </div> <el-option v-for="service in category.serviceList" :key="service.id" :label="service.name" :value="service.id" class="am-drop-child" > </el-option> </div> </el-select> </el-form-item> </el-col> <!-- Events Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="invoiceParams.events" multiple clearable :placeholder="$root.labels.events" @change="filterInvoices" > <el-option v-for="item in options.entities.events" :key="item.id" :label="item.name" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> <!-- Packages Filter --> <el-col :md="6" :lg="4"> <el-form-item> <el-select v-model="invoiceParams.packages" multiple clearable :placeholder="$root.labels.packages" @change="filterInvoices" > <el-option v-for="item in options.entities.packages" :key="item.id" :label="item.name" :value="item.id" > </el-option> </el-select> </el-form-item> </el-col> </div> </transition> <div class=""> <!-- Toggle More Filters --> <el-button class="button-filter-toggle button-filter-toggle-invoice am-button-icon" title="Toggle Filters" @click="filterInvoicesFields = !filterInvoicesFields" > <img class="svg-amelia" alt="Toggle Filters" :src="$root.getUrl + 'public/img/filter.svg'"/> </el-button> </div> </el-row> </el-form> </div> <!-- Spinner --> <div class="am-spinner am-section" v-show="isInvoicesFiltering" > <img :src="$root.getUrl + 'public/img/spinner.svg'"/> </div> <!-- Empty State --> <EmptyState :visible="invoicesTotalCount === 0 && !isInvoicesFiltering" :licence="'basic'" :title="$root.labels.no_payments_yet" > </EmptyState> <!-- Table Header --> <div class="am-finance-list-head" v-show="invoicesTotalCount !== 0 && invoices.length !== 0 && !isInvoicesFiltering" > <el-row> <!-- Payment Date, Customer, Employee --> <el-col :lg="14"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="1"> <p></p> </el-col> <!-- Issue Date --> <el-col :lg="8"> <p>{{ $root.labels.issue_date }}:</p> </el-col> <!-- Invoice Number --> <el-col :lg="3"> <p>{{ $root.labels.invoice }}:</p> </el-col> <!-- Total --> <el-col :lg="4"> <p>{{ $root.labels.total }}:</p> </el-col> <!-- Customer --> <el-col :lg="8"> <p>{{ $root.labels.customer }}:</p> </el-col> </el-row> </el-col> <!-- Status --> <el-col :lg="10"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="12"> <p>{{ $root.labels.service }}/{{ $root.labels.event }}:</p> </el-col> <!-- Status --> <el-col :lg="12"> <p>{{ $root.labels.status }}:</p> </el-col> </el-row> </el-col> </el-row> </div> <!-- Collapsible Data --> <div class="am-finance-list" v-show="invoicesTotalCount !== 0 && invoices.length !== 0 && !isInvoicesFiltering" > <div v-for="(payment, index) in invoices" :key="payment.id" :name="payment.id" class="am-finance" > <div class="am-finance-data am-finance-data-invoices"> <span class="am-entity-color am-event-color" v-if="payment.appointmentId === 0"> </span> <span class="am-entity-color am-appointment-color" v-else> </span> <el-row> <!-- Payment Date, Customer --> <el-col :lg="14"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="1" :sm="1"> <span></span> </el-col> <!-- Issue Date --> <el-col :lg="8" :sm="8"> <p class="am-col-title">{{ $root.labels.issue_date }}:</p> <h4>{{ getFrontedFormattedDate(payment.created) }}</h4> </el-col> <!-- Invoice Number --> <el-col :lg="3" :sm="3"> <p class="am-col-title">{{ $root.labels.invoice }}:</p> <h4>{{ payment.invoiceNumber ? '#' + payment.invoiceNumber : '' }}</h4> </el-col> <!-- Total --> <el-col :lg="4" :sm="4"> <p class="am-col-title">{{ $root.labels.total }}:</p> <h4>{{ getFormattedPrice(payment.total) }}</h4> </el-col> <!-- Customer --> <el-col :lg="8" :sm="8"> <p class="am-col-title">{{ $root.labels.customer }}:</p> <h3 :class="getNoShowClass(payment.customerId)"> {{ payment.customerFirstName + ' ' + payment.customerLastName }} </h3> <span>{{ payment.customerEmail }}</span> </el-col> </el-row> </el-col> <el-col :lg="10"> <el-row :gutter="10" class="am-finance-flex-row-middle-align"> <el-col :lg="0" :sm="1" class="hide-on-mobile"></el-col> <!-- Service --> <el-col :lg="12" :sm="14" class="am-payment-service"> <p class="am-col-title">{{ $root.labels.service }}/{{ $root.labels.event }}:</p> <img v-if="payment.packageId" :src="$root.getUrl + 'public/img/am-package-black.svg'" > <h4>{{ getBookableName(payment) }}</h4> </el-col> <!-- Status --> <el-col class="am-finance-payment-status" :lg="6" :sm="5"> <p class="am-col-title">{{ $root.labels.status }}:</p> <div class="am-payment-status"> <span :class="'am-payment-status-symbol am-payment-status-symbol-' + payment.fullStatus"></span> <h4> {{ getPaymentStatusNiceName(payment.fullStatus) }} </h4> </div> </el-col> <!-- Details Button --> <el-col :lg="6" :sm="4" class="align-right"> <div @click.stop> <el-button @click="showDialogEditPayment(payment, true)"> {{ $root.labels.details }} </el-button> </div> </el-col> </el-row> </el-col> </el-row> </div> </div> </div> <!-- No Results --> <div class="am-empty-state am-section" v-show="invoicesTotalCount !== 0 && invoices.length === 0 && !isInvoicesFiltering"> <img :src="$root.getUrl + 'public/img/emptystate.svg'"> <h2>{{ $root.labels.no_results }}</h2> </div> <!-- Pagination --> <pagination-block :params="invoiceParams" :count="invoicesFilteredCount" :show="invoiceParams.show" :label="$root.labels.payments_lower" :visible="invoicesTotalCount !== 0 && invoices.length !== 0 && !isInvoicesFiltering" @change="filterInvoices" > </pagination-block> </el-tab-pane> </el-tabs> </div> <!-- Selected Popover Delete --> <group-delete name="payments" :entities="payments" :checkGroupData="checkPaymentData" :confirmDeleteMessage="$root.labels.confirm_delete_payment" :successMessage="{single: $root.labels.payment_deleted, multiple: $root.labels.payments_deleted}" :errorMessage="{single: $root.labels.payment_not_deleted, multiple: $root.labels.payments_not_deleted}" @groupDeleteCallback="paymentGroupDeleteCallback" > </group-delete> <!-- Selected Popover Delete --> <group-delete name="taxes" :entities="taxes" :checkGroupData="checkTaxData" :confirmDeleteMessage="$root.labels.confirm_delete_tax" :successMessage="{single: $root.labels.tax_deleted, multiple: $root.labels.taxes_deleted}" :errorMessage="{single: $root.labels.tax_not_deleted, multiple: $root.labels.taxes_not_deleted}" @groupDeleteCallback="taxGroupDeleteCallback" > </group-delete> <!-- Selected Popover Delete --> <group-delete name="coupons" :entities="coupons" :checkGroupData="checkCouponData" :confirmDeleteMessage="$root.labels.confirm_delete_coupon" :successMessage="{single: $root.labels.coupon_deleted, multiple: $root.labels.coupons_deleted}" :errorMessage="{single: $root.labels.coupon_not_deleted, multiple: $root.labels.coupons_not_deleted}" @groupDeleteCallback="couponGroupDeleteCallback" > </group-delete> <!-- Dialog Tax --> <transition name="slide" v-if="$root.settings.payments.taxes.enabled"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-coupon" :visible.sync="dialogTax" :show-close="false" v-if="dialogTax" > <dialog-tax :tax="tax" :taxes="taxes" :services="options.entities.services" :extras="options.entities.extras" :events="options.entities.events" :packages="options.entities.packages" :taxFetched="taxFetched" @closeDialog="closeDialogTax()" @saveCallback="savedTax" @duplicateCallback="duplicateTaxCallback" > </dialog-tax> </el-dialog> </transition> <!-- Dialog Coupon --> <transition name="slide" v-if="$root.settings.payments.coupons"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-coupon" :visible.sync="dialogCoupon" :show-close="false" v-if="dialogCoupon" > <dialog-coupon :coupon="coupon" :services="options.entities.services" :events="options.entities.events" :packages="options.entities.packages" :couponFetched="couponFetched" @closeDialog="closeDialogCoupon()" @saveCallback="savedCoupon" @duplicateCallback="duplicateCouponCallback" > </dialog-coupon> </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="bookingFetched" :customersNoShowCount="customersNoShowCount" :invoice-in-dialog="invoiceInDialog" @updatePaymentCallback="updatePaymentCallback" @deletePaymentCallback="updatePaymentCallback" @closeDialogPayment="closeDialogPayment()" > </dialog-payment> </el-dialog> </transition> <!-- Help Button --> <el-col :md="6" class=""> <a class="am-help-button" href="https://wpamelia.com/admin-finances/" target="_blank" rel="nofollow"> <i class="el-icon-question"></i> {{ $root.labels.need_help }}? </a> </el-col> <!-- <dialog-new-customize></dialog-new-customize>--> </div> </div> </template> <script> import PageHeader from '../parts/PageHeader.vue' import Form from 'form-object' import stashMixin from '../../../js/backend/mixins/stashMixin' import licenceMixin from '../../../js/common/mixins/licenceMixin' import customerMixin from '../../../js/backend/mixins/customerMixin' import imageMixin from '../../../js/common/mixins/imageMixin' import dateMixin from '../../../js/common/mixins/dateMixin' import checkMixin from '../../../js/backend/mixins/checkMixin' import DialogTax from './DialogFinanceTax.vue' import DialogCoupon from './DialogFinanceCoupon.vue' import DialogPayment from './DialogFinancePayment.vue' import DialogExport from '../parts/DialogExport.vue' import notifyMixin from '../../../js/backend/mixins/notifyMixin' import entitiesMixin from '../../../js/common/mixins/entitiesMixin' import paymentMixin from '../../../js/backend/mixins/paymentMixin' import GroupDelete from '../parts/GroupDelete.vue' import priceMixin from '../../../js/common/mixins/priceMixin' import PaginationBlock from '../parts/PaginationBlock.vue' import helperMixin from '../../../js/backend/mixins/helperMixin' // import DialogNewCustomize from '../parts/DialogNewCustomize.vue' import appointmentPriceMixin from '../../../js/backend/mixins/appointmentPriceMixin' import moment from "moment/moment"; import eventMixin from '../../../js/backend/mixins/eventMixin' export default { mixins: [ stashMixin, licenceMixin, customerMixin, paymentMixin, entitiesMixin, imageMixin, dateMixin, checkMixin, notifyMixin, priceMixin, helperMixin, appointmentPriceMixin, eventMixin ], data () { return { customersNoShowCount: [], bookingFetched: false, paymentsFilteredCount: 0, paymentsTotalCount: 0, taxFetched: false, taxesFilteredCount: 0, taxesTotalCount: 0, couponFetched: false, couponsFilteredCount: 0, couponsTotalCount: 0, invoicesFilteredCount: 0, invoicesTotalCount: 0, paymentsFiltering: false, taxesFiltering: false, couponsFiltering: false, invoicesFiltering: false, fetchedFilteredPayments: false, fetchedFilteredTaxes: false, fetchedFilteredCoupons: false, fetchedFilteredInvoices: false, addNewTaxBtnDisplay: false, addNewCouponBtnDisplay: false, dialogTax: false, dialogCoupon: false, dialogPayment: false, invoiceInDialog: false, tax: null, coupon: null, form: new Form(), checkPaymentData: { toaster: false, allChecked: false }, checkTaxData: { toaster: false, allChecked: false }, checkCouponData: { toaster: false, allChecked: false }, options: { entities: { events: [], services: [], packages: [], extras: [], employees: [], customers: [] }, fetched: false }, dialogPaymentsExport: false, dialogCouponsExport: false, paymentsParams: { page: 1, show: this.$root.settings.general.itemsPerPageBackEnd, dates: this.getDatePickerInitRange(), status: '', services: [], events: [], providerId: '', customerId: '' }, exportParamsPayments: { fields: [ {label: this.$root.labels.service + '/' + this.$root.labels.event, value: 'service', checked: true}, {label: this.$root.labels.booking_start, value: 'bookingStart', checked: true}, {label: this.$root.labels.customer, value: 'customer', checked: true}, {label: this.$root.labels.customer_email, value: 'customerEmail', checked: true}, {label: this.$root.labels.employee, value: 'employee', checked: true}, {label: this.$root.labels.employee_email, value: 'employeeEmail', checked: true}, {label: this.$root.labels.location, value: 'location', checked: true}, {label: this.$root.labels.amount, value: 'amount', checked: true}, {label: this.$root.labels.method, value: 'type', checked: true}, {label: this.$root.labels.status, value: 'status', checked: true}, {label: this.$root.labels.payment_date, value: 'paymentDate', checked: true}, {label: this.$root.labels.coupon_code, value: 'couponCode', checked: true}, {label: this.$root.labels.payment_created, value: 'paymentCreated', checked: true} ] }, exportPaymentsAction: '', taxesParams: { page: 1, status: '', services: [], extras: [], events: [], packages: [], search: '' }, couponsParams: { page: 1, status: '', services: [], events: [], search: '' }, exportParamsCoupons: { fields: [ {label: this.$root.labels.code, value: 'code', checked: true}, {label: this.$root.labels.discount, value: 'discount', checked: true}, {label: this.$root.labels.deduction, value: 'deduction', checked: true}, {label: this.$root.labels.services, value: 'services', checked: true}, {label: this.$root.labels.events, value: 'events', checked: true}, {label: this.$root.labels.limit, value: 'limit', checked: true}, {label: this.$root.labels.used, value: 'used', checked: true} ] }, exportCouponsAction: '', invoiceParams: { page: 1, show: this.$root.settings.general.itemsPerPageBackEnd, dates: this.getDatePickerInitRange(), services: [], events: [], packages: [], providerId: '', customerId: '', invoices: true }, statuses: [ { value: 'paid', label: this.$root.labels.paid }, { value: 'pending', label: this.$root.labels.pending }, { value: 'partiallyPaid', label: this.$root.labels.partially_paid } ], selectedPaymentModalData: { paymentId: null, bookingStart: null, bookings: null, service: null, providers: null, customer: null }, // Filter filterPaymentsFields: true, filterTaxesFields: true, searchTaxesPlaceholder: this.$root.labels.finance_taxes_search_placeholder, filterCouponsFields: true, searchCouponsPlaceholder: this.$root.labels.finance_coupons_search_placeholder, filterInvoicesFields: true, // Finance financeTabs: 'payments', payments: [], taxes: [], coupons: [], invoices: [], timer: null, cancelSource: null } }, created () { Form.defaults.axios = this.$http if (!this.$root.licence.isBasic && !this.$root.licence.isStarter && !this.$root.licence.isLite) { this.statuses.push( { value: 'refunded', label: this.$root.labels.refunded } ) } // Set filter params based on URL GET fields let urlParams = this.getUrlQueryParams(window.location.href) if (!('dateFrom' in urlParams) || !('dateTo' in urlParams)) { this.paymentsParams.dates = this.getDatePickerInitRange() } else { this.paymentsParams.dates = { start: this.$moment(urlParams['dateFrom']).toDate(), end: this.$moment(urlParams['dateTo']).toDate() } } if (urlParams['status']) { this.paymentsParams.status = urlParams['status'] } this.getPayments() if (this.$root.settings.payments.taxes.enabled) { this.getTaxes() } if (this.$root.settings.payments.coupons) { this.getCoupons() } this.getInvoices() this.handleResize() this.getFinanceOptions() }, mounted () { this.inlineSVG() if (this.$root.settings.payments.wc && this.$root.settings.payments.wc.enabled) { this.exportParamsPayments.fields.push({label: this.$root.labels.wc_order_id, value: 'wcOrderId', checked: true}) } }, methods: { getExportData () { if (!this.paymentsParams.dates) { this.paymentsParams.dates = { start: '', end: '' } } return Object.assign(this.paymentsParams, this.exportParamsPayments) }, clearDateFilter () { this.paymentsParams.dates = null this.paymentsParams.page = 1 this.filterPayments() }, clearInvoiceDateFilter () { this.invoiceParams.dates = null this.invoiceParams.page = 1 this.filterInvoices() }, getBookableName (payment) { if (payment.secondaryPayments && Object.values(payment.secondaryPayments).length > 0) { if (payment.name === Object.values(payment.secondaryPayments)[0].name) { return payment.name } return payment.name + ' +' + Object.values(payment.secondaryPayments).length } return payment.name }, getPaymentsStatusAndTotal (payment) { let allPayments = [payment].concat(payment.secondaryPayments ? Object.values(payment.secondaryPayments) : []) let bookings = {} allPayments.forEach((singlePayment) => { let index = payment.type + '_' + (singlePayment.packageCustomerId ? singlePayment.packageCustomerId : singlePayment.customerBookingId) if (index in bookings) { bookings[index].booking.payments.push(singlePayment) } else { bookings[index] = { booking: { price: singlePayment.bookedPrice, aggregatedPrice: singlePayment.aggregatedPrice, persons: singlePayment.persons, extras: singlePayment.extras, coupon: singlePayment.coupon, tax: singlePayment.bookedTax, payments: [singlePayment] } } } }) let amountData = this.getPaymentAmountData(Object.values(bookings), payment.type) let bookingPrice = amountData.total if (allPayments.every(p => p.status === 'refunded')) { return {status: 'refunded', total: bookingPrice} } let paidAmount = amountData.paid if (Math.round(paidAmount * 100) / 100 >= Math.round(bookingPrice * 100) / 100) { return {status: 'paid', total: bookingPrice} } else if (paidAmount > 0) { return {status: 'partiallyPaid', total: bookingPrice} } return {status: 'pending', total: bookingPrice} }, savedTax () { this.updateStashEntities({}) this.getTaxes() }, savedCoupon () { this.updateStashEntities({}) this.getCoupons() }, formatForFetch (params) { let dates = [] if (params.dates) { if (params.dates.start) { dates.push(this.$moment(params.dates.start).format('YYYY-MM-DD')) } if (params.dates.end) { dates.push(this.$moment(params.dates.end).format('YYYY-MM-DD')) } params.dates = dates } Object.keys(params).forEach((key) => (!params[key] && params[key] !== 0) && delete params[key]) return params }, getPayments () { this.paymentsFiltering = true this.fetchedFilteredPayments = false if (this.cancelSource) { this.cancelSource.cancel() } let params = JSON.parse(JSON.stringify(this.paymentsParams)) params = this.formatForFetch(params) this.cancelSource = this.$http.CancelToken.source() this.$http.get(`${this.$root.getAjaxUrl}/payments`, { params: this.getAppropriateUrlParams(params), cancelToken: this.cancelSource.token }) .then(response => { let customersIds = this.options.entities.customers.map(customer => customer.id) let customers = this.options.entities.customers let $this = this response.data.data.payments.forEach(function (payment) { payment.checked = false let statusTotal = $this.getPaymentsStatusAndTotal(payment) payment.fullStatus = statusTotal.status if (customersIds.indexOf(payment.customerId) === -1) { customers.push({ id: payment.customerId, firstName: payment.customerFirstName, lastName: payment.customerLastName, email: payment.customerEmail }) } }) this.options.entities.customers = Object.values(customers) this.payments = response.data.data.payments this.paymentsFilteredCount = response.data.data.filteredCount this.paymentsTotalCount = response.data.data.totalCount this.cancelSource = null this.paymentsFiltering = false this.fetchedFilteredPayments = true }) .catch(e => { console.log(e.message) if (!this.$http.isCancel(e)) { this.paymentsFiltering = false this.fetchedFilteredPayments = true } }) }, getInvoices () { if (this.$root.licence.isLite || this.$root.licence.isStarter) { this.invoicesFiltering = false this.fetchedFilteredInvoices = false return } this.invoicesFiltering = true this.fetchedFilteredInvoices = false let params = JSON.parse(JSON.stringify(this.invoiceParams)) params = this.formatForFetch(params) this.$http.get(`${this.$root.getAjaxUrl}/payments`, { params: this.getAppropriateUrlParams(params) }) .then(response => { let $this = this response.data.data.payments.forEach(function (payment) { let statusTotal = $this.getPaymentsStatusAndTotal(payment) payment.fullStatus = statusTotal.status payment.total = statusTotal.total }) this.invoices = response.data.data.payments this.invoicesFilteredCount = response.data.data.filteredCount this.invoicesTotalCount = response.data.data.totalCount this.invoicesFiltering = false this.fetchedFilteredInvoices = true }) .catch(e => { console.log(e.message) this.invoicesFiltering = false this.fetchedFilteredInvoices = true }) }, getTotal () { switch (this.financeTabs) { case ('payments'): return this.paymentsFilteredCount case ('coupons'): return this.couponsFilteredCount case ('taxes'): return this.taxesFilteredCount case ('invoices'): return this.invoicesFilteredCount } }, getFinanceOptions () { this.options.fetched = false this.searchCustomers( '', () => { let customersIds = this.options.entities.customers.map(customer => parseInt(customer.id)) let customers = this.options.entities.customers this.searchedCustomers.forEach((customer) => { if (customersIds.indexOf(parseInt(customer.id)) === -1) { customersIds.push(customer.id) customers.push(customer) } this.customersNoShowCount.push({customerId: customer.id, noShowCount: customer.noShowCount}) }) this.options.entities.customers = Object.values(customers) } ) this.$http.get(`${this.$root.getAjaxUrl}/entities`, { params: this.getAppropriateUrlParams({ types: ['categories', 'employees', 'events', 'packages'] }) }) .then(response => { this.options.entities = response.data.data this.options.entities.services = this.getServicesFromCategories(this.options.entities.categories) let extras = [] this.options.entities.services.forEach((service) => { extras = extras.concat(service.extras) }) this.options.entities.extras = extras this.setEntitiesToTaxes() this.options.fetched = true this.searchedEvents = this.options.entities.events }) .catch(e => { console.log(e.message) this.options.fetched = true }) }, setEntitiesToTaxes () { this.taxes.forEach((tax, taxIndex) => { ['service', 'extra', 'event', 'package'].forEach((type) => { tax[type + 'List'].forEach((item, itemIndex) => { let entity = this.options.entities[type + 's'].find(i => i.id === item.id) if (typeof entity !== 'undefined' && entity) { this.taxes[taxIndex][type + 'List'][itemIndex] = entity } }) }) }) }, getTaxes () { if (this.$root.licence.isLite || this.$root.licence.isStarter) { this.taxesFiltering = false this.fetchedFilteredTaxes = false return } this.taxesFiltering = true this.fetchedFilteredTaxes = false let params = JSON.parse(JSON.stringify(this.taxesParams)) Object.keys(params).forEach((key) => (!params[key] && params[key] !== 0) && delete params[key]) this.$http.get(`${this.$root.getAjaxUrl}/taxes`, { params: params }) .then(response => { response.data.data.taxes.forEach(function (taxItem) { taxItem.checked = false }) this.taxes = response.data.data.taxes this.taxesFilteredCount = response.data.data.filteredCount this.taxesTotalCount = response.data.data.totalCount this.setEntitiesToTaxes() this.taxesFiltering = false this.fetchedFilteredTaxes = true }) .catch(e => { console.log(e.message) this.taxesFiltering = false this.fetchedFilteredTaxes = true }) }, getCoupons () { if (this.$root.licence.isLite) { this.couponsFiltering = false this.fetchedFilteredCoupons = false return } this.couponsFiltering = true this.fetchedFilteredCoupons = false let params = JSON.parse(JSON.stringify(this.couponsParams)) Object.keys(params).forEach((key) => (!params[key] && params[key] !== 0) && delete params[key]) this.$http.get(`${this.$root.getAjaxUrl}/coupons`, { params: params }) .then(response => { response.data.data.coupons.forEach(function (coupItem) { coupItem.checked = false }) this.coupons = response.data.data.coupons this.couponsFilteredCount = response.data.data.filteredCount this.couponsTotalCount = response.data.data.totalCount this.couponsFiltering = false this.fetchedFilteredCoupons = true }) .catch(e => { console.log(e.message) this.couponsFiltering = false this.fetchedFilteredCoupons = true }) }, getPaymentAppointment (payment, invoice) { this.$http.get(`${this.$root.getAjaxUrl}/appointments/` + payment.appointmentId, {params: {'customerId': payment.customerId}}) .then(response => { this.selectedPaymentModalData = this.getPaymentData(payment, response.data.data.appointment, null, null, invoice ? response.data.data.recurring : []) this.selectedPaymentModalData.recurring = response.data.data.recurring this.bookingFetched = true }) .catch(e => { console.log(e.message) }) }, getEvent (payment) { this.$http.get(`${this.$root.getAjaxUrl}/events/` + payment.eventId) .then(response => { this.selectedPaymentModalData = this.getPaymentData(payment, null, response.data.data.event, null) this.bookingFetched = true }) .catch(e => { console.log(e.message) }) }, getPackage (payment) { let packObj = { package: {name: payment.name}, booking: { price: payment.bookedPrice, payments: [payment].concat(payment.secondaryPayments ? Object.values(payment.secondaryPayments) : []), extras: [], tax: payment.bookedTax ? JSON.parse(payment.bookedTax) : null, } } this.selectedPaymentModalData = this.getPaymentData(payment, null, null, packObj) this.bookingFetched = true }, getTax (taxId) { this.$http.get(`${this.$root.getAjaxUrl}/taxes/` + taxId) .then(response => { let tax = response.data.data.tax let servicesIds = this.options.entities.services.map(service => service.id) tax.serviceList = tax.serviceList.filter(service => servicesIds.indexOf(service.id) !== -1) let extrasIds = [] this.options.entities.services.forEach((service) => { extrasIds = extrasIds.concat(service.extras.map(extra => extra.id)) }) tax.extraList = tax.extraList.filter(extra => extrasIds.indexOf(extra.id) !== -1) let packagesIds = this.options.entities.packages.map(pack => pack.id) tax.packageList = tax.packageList.filter(pack => packagesIds.indexOf(pack.id) !== -1) this.tax = tax this.taxFetched = true }) .catch(e => { console.log(e.message) }) }, getCoupon (couponId) { this.$http.get(`${this.$root.getAjaxUrl}/coupons/` + couponId) .then(response => { let coupon = response.data.data.coupon coupon.expirationDate = coupon.expirationDate ? this.getDate(coupon.expirationDate) : null let packagesIds = this.options.entities.packages.map(pack => pack.id) coupon.packageList = coupon.packageList.filter(pack => packagesIds.indexOf(pack.id) !== -1) this.coupon = coupon this.couponFetched = true }) .catch(e => { console.log(e.message) }) }, paymentGroupDeleteCallback () { this.checkPaymentData.allChecked = false this.checkPaymentData.toaster = false this.getPayments() }, taxGroupDeleteCallback () { this.checkTaxData.allChecked = false this.checkTaxData.toaster = false this.updateStashEntities({}) this.getTaxes() }, couponGroupDeleteCallback () { this.checkCouponData.allChecked = false this.checkCouponData.toaster = false this.updateStashEntities({}) this.getCoupons() }, changeRange () { this.setDatePickerSelectedDaysCount(this.paymentsParams.dates.start, this.paymentsParams.dates.end) this.filterPayments() }, changeRangeInvoices () { this.setDatePickerSelectedDaysCount(this.invoiceParams.dates.start, this.invoiceParams.dates.end) this.invoiceParams.page = 1 this.filterInvoices() }, filterPayments () { this.getPayments() }, filterTaxes () { this.getTaxes() }, filterCoupons () { this.getCoupons() }, filterInvoices () { this.getInvoices() }, updatePaymentCallback () { this.dialogPayment = false this.getPayments() }, duplicateTaxCallback (tax) { setTimeout(() => { this.$set(this, 'tax', tax) this.$set(this.tax, 'id', 0) this.dialogTax = true }, 300) }, duplicateCouponCallback (coupon) { setTimeout(() => { this.$set(this, 'coupon', coupon) this.$set(this.coupon, 'id', 0) this.dialogCoupon = true }, 300) }, handleResize () { this.filterPaymentsFields = window.innerWidth >= 992 this.filterTaxesFields = window.innerWidth >= 992 this.filterCouponsFields = window.innerWidth >= 992 }, showDialogNewTax () { this.tax = this.getInitTaxObject() this.dialogTax = true }, showDialogNewCoupon () { this.coupon = this.getInitCouponObject() this.dialogCoupon = true }, showDialogEditTax (taxId) { this.dialogTax = true this.getTax(taxId) }, showDialogEditCoupon (couponId) { this.dialogCoupon = true this.getCoupon(couponId) }, showDialogEditPayment (payment, invoice = false) { this.dialogPayment = true this.invoiceInDialog = invoice if (payment.appointmentId) { this.getPaymentAppointment(payment, invoice) } if (payment.eventId) { this.getEvent(payment) } if (payment.packageId) { this.getPackage(payment) } }, handleTabClick (tab) { if (tab.name === 'coupons') { this.addNewTaxBtnDisplay = false this.addNewCouponBtnDisplay = true } else if (tab.name === 'taxes') { this.addNewTaxBtnDisplay = true this.addNewCouponBtnDisplay = false } else if (tab.name === 'invoices') { this.addNewCouponBtnDisplay = false this.addNewTaxBtnDisplay = false } else { this.addNewTaxBtnDisplay = false this.addNewCouponBtnDisplay = false } }, selectAllInCategoryFinance (id) { let services = this.getCategoryServices(id) let servicesIds = services.map(service => service.id) // Deselect all services if they are already selected if (_.isEqual(_.intersection(servicesIds, this.paymentsParams.services), servicesIds)) { this.paymentsParams.services = _.difference(this.paymentsParams.services, servicesIds) } else { this.paymentsParams.services = _.uniq(this.paymentsParams.services.concat(servicesIds)) } this.filterPayments() }, selectAllInCategoryTaxes (id) { let services = this.getCategoryServices(id) let servicesIds = services.map(service => service.id) // Deselect all services if they are already selected if (_.isEqual(_.intersection(servicesIds, this.taxesParams.services), servicesIds)) { this.taxesParams.services = _.difference(this.taxesParams.services, servicesIds) } else { this.taxesParams.services = _.uniq(this.taxesParams.services.concat(servicesIds)) } this.filterTaxes() }, selectAllInServiceTaxes (id) { let service = this.getServiceById(id) let extrasIds = service.extras.map(extra => extra.id) // Deselect all extras if they are already selected if (_.isEqual(_.intersection(extrasIds, this.taxesParams.extras), extrasIds)) { this.taxesParams.extras = _.difference(this.taxesParams.extras, extrasIds) } else { this.taxesParams.extras = _.uniq(this.taxesParams.extras.concat(extrasIds)) } this.filterTaxes() }, selectAllInCategoryCoupons (id) { let services = this.getCategoryServices(id) let servicesIds = services.map(service => service.id) // Deselect all services if they are already selected if (_.isEqual(_.intersection(servicesIds, this.couponsParams.services), servicesIds)) { this.couponsParams.services = _.difference(this.couponsParams.services, servicesIds) } else { this.couponsParams.services = _.uniq(this.couponsParams.services.concat(servicesIds)) } this.filterCoupons() }, getInitTaxObject () { return { id: 0, name: '', amount: 0, type: 'percentage', status: 'visible', serviceList: [], extraList: [], eventList: [], packageList: [] } }, getInitCouponObject () { return { id: 0, code: '', discount: 0, deduction: 0, limit: 0, customerLimit: 0, status: 'visible', notificationInterval: 0, notificationRecurring: false, serviceList: [], eventList: [], packageList: [] } }, closeDialogPayment () { this.dialogPayment = false this.bookingFetched = false }, closeDialogTax () { this.tax = null this.dialogTax = false this.taxFetched = false }, closeDialogCoupon () { this.coupon = null this.dialogCoupon = false this.couponFetched = false } }, watch: { 'taxesParams.search' () { clearTimeout(this.timer) this.timer = setTimeout(this.filterTaxes, 500) }, 'couponsParams.search' () { clearTimeout(this.timer) this.timer = setTimeout(this.filterCoupons, 500) }, 'dialogPayment' () { if (this.dialogPayment === false) { this.bookingFetched = false } }, 'dialogTax' () { if (this.dialogTax === false) { this.taxFetched = false this.tax = null } }, 'dialogCoupon' () { if (this.dialogCoupon === false) { this.couponFetched = false this.coupon = null } } }, computed: { hasPayments () { return this.paymentsTotalCount !== 0 }, hasPaymentsFiltered () { return this.payments.length !== 0 }, hasInvoices () { return this.invoicesTotalCount !== 0 }, hasInvoicesFiltered () { return this.invoices.length !== 0 }, isPaymentsFiltering () { return this.paymentsFiltering && this.financeTabs === 'payments' }, isInvoicesFiltering () { return this.invoicesFiltering && this.financeTabs === 'invoices' }, hasTaxes () { return this.taxesTotalCount !== 0 }, hasTaxesFiltered () { return this.taxes.length !== 0 }, isTaxesFiltering () { return this.taxesFiltering && this.financeTabs === 'taxes' }, hasCoupons () { return this.couponsTotalCount !== 0 }, hasCouponsFiltered () { return this.coupons.length !== 0 }, isCouponsFiltering () { return this.couponsFiltering && this.financeTabs === 'coupons' } }, components: { PageHeader, DialogTax, DialogCoupon, DialogPayment, DialogExport, GroupDelete, PaginationBlock // DialogNewCustomize } } </script>
Save
Back