Add age range based prices to groups price config

This commit is contained in:
Björn Fromme
2021-08-09 17:34:58 +02:00
parent 3916dede50
commit fc42801c8a
7 changed files with 576 additions and 299 deletions
@@ -6,7 +6,23 @@
</div>
<div class="form-group" v-show="!submitted">
<label>Personenzahl</label>
<input type="number" class="form-control" v-model="selectedPax" @change="onPaxUpdated()"/>
<input type="number" class="form-control" v-model.number="selectedPax" @change="onPaxUpdated()"
v-show="nightsCount > 0 && !includesBonusCard"/>
<div class="flex space-x-4" v-show="nightsCount > 0 && includesBonusCard">
<div class="w-1/3 flex flex-col space-y-1">
<span>ab 15 Jahre</span>
<input type="number" class="form-control" v-model.number="selectedPaxGroup3" @change="onPaxUpdated()"/>
</div>
<div class="w-1/3 flex flex-col space-y-1">
<span>7-14 Jahre</span>
<input type="number" class="form-control" v-model.number="selectedPaxGroup2" @change="onPaxUpdated()"/>
</div>
<div class="w-1/3 flex flex-col space-y-1">
<span>4-6 Jahre</span>
<input type="number" class="form-control" v-model.number="selectedPaxGroup1" @change="onPaxUpdated()"/>
</div>
</div>
<p class="text-sm italic" v-show="nightsCount === 0">Bitte wählen Sie einen Zeitraum</p>
</div>
<div class="form-group" v-show="options.length > 0 && !submitted">
<label>Optionale Zusatzleistungen</label>
@@ -22,7 +38,9 @@
<label>Optionale Verpflegungsleistungen</label>
<select class="form-control" v-model="selectedBoard" v-if="selectedPax >= 30">
<option :value="null">Keine Auswahl</option>
<option v-for="board of boards" :value="board" :key="board.uid">{{ board.title }} ({{ formatCurrency(board.price) }} pro Person und Nacht)</option>
<option v-for="board of boards" :value="board" :key="board.uid">{{ board.title }}
({{ formatCurrency(board.price) }} pro Person und Nacht)
</option>
</select>
<div v-show="selectedPax < 30">
<span class="label label-warning">Erst ab 30 Personen buchbar.</span>
@@ -33,38 +51,39 @@
<div class="panel-body">
<table class="table table-striped" v-show="nightsCount > 0">
<tbody>
<tr>
<th>Zeitraum</th>
<td>{{ rangeFormatted }}, {{ nightsCount }} Nächte</td>
</tr>
<tr>
<th>Basispreis</th>
<td>{{ formatCurrency(pricePax.base) }}</td>
</tr>
<tr v-if="pricePax.additional > 0">
<th>Aufpreis Personenzahl</th>
<td>{{ formatCurrency(pricePax.additional) }}</td>
</tr>
<tr v-if="priceShortTerm > 0">
<th>Aufpreis Kurzzeit</th>
<td>{{ formatCurrency(priceShortTerm) }}</td>
</tr>
<tr v-if="selectedOptions.length > 0">
<th>Zusatzleistungen</th>
<td>{{ formatCurrency(priceOptions) }}</td>
</tr>
<tr v-if="priceBoard > 0">
<th>Verpflegung</th>
<td>{{ formatCurrency(priceBoard) }}</td>
</tr>
<tr v-if="priceRunningCosts > 0">
<th>Strom- und Abfallgebühren</th>
<td>{{ formatCurrency(priceRunningCosts) }}</td>
</tr>
<tr>
<th>Gesamtpreis</th>
<td><strong>{{ formatCurrency(priceTotal) }}</strong><br><small>zzgl. vor Ort zu entrichtender Ortstaxe</small></td>
</tr>
<tr>
<th>Zeitraum</th>
<td>{{ rangeFormatted }}, {{ nightsCount }} Nächte</td>
</tr>
<tr>
<th>Basispreis</th>
<td>{{ formatCurrency(pricePax.base) }}</td>
</tr>
<tr v-if="pricePax.additional > 0">
<th>Aufpreis Personenzahl</th>
<td>{{ formatCurrency(pricePax.additional) }}</td>
</tr>
<tr v-if="priceShortTerm > 0">
<th>Aufpreis Kurzzeit</th>
<td>{{ formatCurrency(priceShortTerm) }}</td>
</tr>
<tr v-if="selectedOptions.length > 0">
<th>Zusatzleistungen</th>
<td>{{ formatCurrency(priceOptions) }}</td>
</tr>
<tr v-if="priceBoard > 0">
<th>Verpflegung</th>
<td>{{ formatCurrency(priceBoard) }}</td>
</tr>
<tr v-if="priceRunningCosts > 0">
<th>Strom- und Abfallgebühren</th>
<td>{{ formatCurrency(priceRunningCosts) }}</td>
</tr>
<tr>
<th>Gesamtpreis</th>
<td><strong>{{ formatCurrency(priceTotal) }}</strong><br><small>zzgl. vor Ort zu entrichtender
Ortstaxe</small></td>
</tr>
</tbody>
</table>
<div v-show="nightsCount === 0">
@@ -114,260 +133,304 @@
</template>
<script>
import $ from 'jquery';
import dayjs from 'dayjs'
import 'dayjs/locale/de'
import 'flatpickr';
import { German } from 'flatpickr/dist/l10n/de';
import Vue from 'vue';
import axios from 'axios';
import $ from 'jquery';
import dayjs from 'dayjs'
import 'dayjs/locale/de'
import 'flatpickr';
import {German} from 'flatpickr/dist/l10n/de';
import Vue from 'vue';
import axios from 'axios';
export default {
props: {
loaderUri: {
type: String,
required: true
},
endpoint: {
type: String,
required: true
},
configs: {
type: Array,
required: true
},
boards: {
type: Array,
required: true
},
options: {
type: Array,
required: true
}
export default {
props: {
loaderUri: {
type: String,
required: true
},
data () {
return {
processing: false,
submitted: false,
picker: null,
formErrors: {},
selectedFrom: null,
selectedTo: null,
selectedRange: [],
selectedPax: 30,
selectedOptions: [],
selectedBoard: null,
name: null,
email: null,
phone: null,
remarks: null
}
endpoint: {
type: String,
required: true
},
methods: {
formatCurrency (value) {
if (0 === value) {
return '-';
}
return parseFloat(value).toFixed(2).replace(/\./, ',') + ' €';
},
formatOptionPriceType (option) {
if (2 === option.type) {
return ' ('+this.formatCurrency(option.price)+' pro Person)';
}
if (3 === option.type) {
return ' ('+this.formatCurrency(option.price)+' pro Nacht)';
}
if (4 === option.type) {
return ' ('+this.formatCurrency(option.price)+' pro Person und Nacht)';
}
configs: {
type: Array,
required: true
},
boards: {
type: Array,
required: true
},
options: {
type: Array,
required: true
}
},
data() {
return {
processing: false,
submitted: false,
picker: null,
formErrors: {},
selectedFrom: null,
selectedTo: null,
selectedRange: [],
selectedPax: 30,
selectedPaxGroup1: 0,
selectedPaxGroup2: 0,
selectedPaxGroup3: 30,
selectedOptions: [],
selectedBoard: null,
name: null,
email: null,
phone: null,
remarks: null
}
},
methods: {
formatCurrency(value) {
if (0 === value) {
return '-';
}
return parseFloat(value).toFixed(2).replace(/\./, ',') + ' €';
},
formatOptionPriceType(option) {
if (2 === option.type) {
return ' (' + this.formatCurrency(option.price) + ' pro Person)';
}
if (3 === option.type) {
return ' (' + this.formatCurrency(option.price) + ' pro Nacht)';
}
if (4 === option.type) {
return ' (' + this.formatCurrency(option.price) + ' pro Person und Nacht)';
}
return ' ('+this.formatCurrency(option.price)+')';
},
initSelectableRanges () {
let enabledDates = [];
for (let config of this.configs) {
const dateFrom = dayjs(config.dateFrom);
const dateTo = dayjs(config.dateTo);
enabledDates.push({
from: dateFrom.format('DD.MM.YYYY'),
to: dateTo.format('DD.MM.YYYY')
});
}
this.picker.set('enable', enabledDates);
if (enabledDates.length > 0) {
const firstDate = enabledDates[0].from;
this.picker.set('minDate', firstDate);
}
},
updateSelectedRange () {
let range = [];
let currentDate = this.selectedFrom;
while (currentDate < this.selectedTo) {
range.push(currentDate);
currentDate = currentDate.add(1, 'day');
}
this.selectedRange = range;
},
onPaxUpdated () {
if (this.selectedPax < 30) {
this.selectedBoard = null;
}
},
submitForm () {
let options = [];
for (let option of this.selectedOptions) {
options.push(option.uid);
}
let formData = {
'tx_epproducts_ajax[groupsPriceInquiry][name]': this.name,
'tx_epproducts_ajax[groupsPriceInquiry][email]': this.email,
'tx_epproducts_ajax[groupsPriceInquiry][phone]': this.phone,
'tx_epproducts_ajax[groupsPriceInquiry][remarks]': this.remarks,
'tx_epproducts_ajax[groupsPriceInquiry][dateFrom]': this.selectedFrom.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][dateTo]': this.selectedTo.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][pax]': this.selectedPax,
'tx_epproducts_ajax[groupsPriceInquiry][board]': this.selectedBoard ? this.selectedBoard.uid : null,
'tx_epproducts_ajax[groupsPriceInquiry][options]': options,
'tx_epproducts_ajax[groupsPriceInquiry][summary]': this.priceSummary
};
this.processing = true;
this.submitted = false;
axios({
url: this.endpoint,
method: 'POST',
data: $.param(formData)
}).then(response => {
if ('validation' === response.data.status) {
this.formErrors = response.data.errors;
} else {
this.submitted = true;
}
}).catch(error => {
}).finally(() => {
this.processing = false;
return ' (' + this.formatCurrency(option.price) + ')';
},
initSelectableRanges() {
let enabledDates = [];
for (let config of this.configs) {
const dateFrom = dayjs(config.dateFrom);
const dateTo = dayjs(config.dateTo);
enabledDates.push({
from: dateFrom.format('DD.MM.YYYY'),
to: dateTo.format('DD.MM.YYYY')
});
}
this.picker.set('enable', enabledDates);
if (enabledDates.length > 0) {
const firstDate = enabledDates[0].from;
this.picker.set('minDate', firstDate);
}
},
computed: {
nightsCount () {
return this.selectedRange.length;
},
rangeFormatted () {
if (null === this.selectedFrom || null === this.selectedTo) {
return '-';
updateSelectedRange() {
let range = [];
let currentDate = this.selectedFrom;
while (currentDate < this.selectedTo) {
range.push(currentDate);
currentDate = currentDate.add(1, 'day');
}
this.selectedRange = range;
},
onPaxUpdated() {
if (this.selectedPax < 30) {
this.selectedBoard = null;
}
},
submitForm() {
let options = [];
for (let option of this.selectedOptions) {
options.push(option.uid);
}
let formData = {
'tx_epproducts_ajax[groupsPriceInquiry][name]': this.name,
'tx_epproducts_ajax[groupsPriceInquiry][email]': this.email,
'tx_epproducts_ajax[groupsPriceInquiry][phone]': this.phone,
'tx_epproducts_ajax[groupsPriceInquiry][remarks]': this.remarks,
'tx_epproducts_ajax[groupsPriceInquiry][dateFrom]': this.selectedFrom.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][dateTo]': this.selectedTo.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][pax]': this.includesBonusCard ? '' : this.selectedPax,
'tx_epproducts_ajax[groupsPriceInquiry][paxGroup1]': this.includesBonusCard ? this.selectedPaxGroup1 : '',
'tx_epproducts_ajax[groupsPriceInquiry][paxGroup2]': this.includesBonusCard ? this.selectedPaxGroup2 : '',
'tx_epproducts_ajax[groupsPriceInquiry][paxGroup3]': this.includesBonusCard ? this.selectedPaxGroup3 : '',
'tx_epproducts_ajax[groupsPriceInquiry][board]': this.selectedBoard ? this.selectedBoard.uid : null,
'tx_epproducts_ajax[groupsPriceInquiry][options]': options,
'tx_epproducts_ajax[groupsPriceInquiry][summary]': this.priceSummary
};
this.processing = true;
this.submitted = false;
axios({
url: this.endpoint,
method: 'POST',
data: $.param(formData)
}).then(response => {
if ('validation' === response.data.status) {
this.formErrors = response.data.errors;
} else {
this.submitted = true;
}
}).catch(error => {
}).finally(() => {
this.processing = false;
});
}
},
computed: {
nightsCount() {
return this.selectedRange.length;
},
rangeFormatted() {
if (null === this.selectedFrom || null === this.selectedTo) {
return '-';
}
return this.selectedFrom.format('DD.MM.YYYY') + ' - ' + this.selectedTo.format('DD.MM.YYYY');
},
pricePax () {
let base = 0;
let additional = 0;
for (let date of this.selectedRange) {
for (let config of this.configs) {
if (date >= dayjs(config.dateFrom) && date < dayjs(config.dateTo)) {
base += config.price;
if (this.selectedPax > config.personsIncluded) {
return this.selectedFrom.format('DD.MM.YYYY') + ' - ' + this.selectedTo.format('DD.MM.YYYY');
},
includesBonusCard() {
let included = false
for (let date of this.selectedRange) {
for (let config of this.configs) {
if (date >= dayjs(config.dateFrom) && date < dayjs(config.dateTo)) {
if (config.bonusCardIncluded) {
included = true
}
}
}
}
return included
},
pricePax() {
let base = 0
let additional = 0
for (let date of this.selectedRange) {
for (let config of this.configs) {
if (date >= dayjs(config.dateFrom) && date < dayjs(config.dateTo)) {
base += config.price
if (this.totalPax > config.personsIncluded) {
if (false === config.bonusCardIncluded) {
additional += (this.selectedPax - config.personsIncluded) * config.priceAdditionalPerson;
} else {
// Distribute additional persons costs by group
let included = config.personsIncluded
if (this.selectedPaxGroup3 <= included) {
included -= this.selectedPaxGroup3
} else {
additional += (this.selectedPaxGroup3 - included) * config.priceAdditionalPersonByGroup[3]
included = 0
}
if (this.selectedPaxGroup2 <= included) {
included -= this.selectedPaxGroup2
} else {
additional += (this.selectedPaxGroup2 - included) * config.priceAdditionalPersonByGroup[2]
included = 0
}
if (this.selectedPaxGroup1 > included) {
additional += (this.selectedPaxGroup1 - included) * config.priceAdditionalPersonByGroup[1]
}
}
}
}
}
return { base, additional };
},
priceOptions () {
let price = 0;
for (let option of this.selectedOptions) {
if (true === option.ignoreWithBoard && this.selectedBoard) {
continue;
}
if (1 === option.type) {
price += option.price;
} else if (2 === option.type) {
price += option.price * this.selectedPax;
} else if (3 === option.type) {
price += option.price * this.nightsCount;
} else if (4 === option.type) {
price += option.price * this.nightsCount * this.selectedPax;
}
}
return {base, additional};
},
totalPax() {
return this.includesBonusCard ? this.selectedPaxGroup1 + this.selectedPaxGroup2 + this.selectedPaxGroup3 : this.selectedPax
},
priceOptions() {
let price = 0;
for (let option of this.selectedOptions) {
if (true === option.ignoreWithBoard && this.selectedBoard) {
continue;
}
return price;
},
priceBoard () {
let price = 0;
if (this.selectedBoard) {
price = this.selectedBoard.price * this.nightsCount * this.selectedPax;
}
return price;
},
priceShortTerm () {
let price = 0;
const totalPricePax = this.pricePax.base + this.pricePax.additional;
if (2 === this.nightsCount) {
price = totalPricePax * 0.2;
}
if (3 === this.nightsCount) {
price = totalPricePax * 0.1;
}
return price;
},
priceRunningCosts () {
if (this.selectedBoard) {
return 0;
}
return this.nightsCount * this.selectedPax * 1.7;
},
priceTotal () {
return this.pricePax.base + this.pricePax.additional + this.priceOptions
+ this.priceBoard + this.priceShortTerm + this.priceRunningCosts;
},
priceSummary () {
return {
paxBase: this.formatCurrency(this.pricePax.base),
paxAdditional: this.formatCurrency(this.pricePax.additional),
shortTerm: this.formatCurrency(this.priceShortTerm),
options: this.formatCurrency(this.priceOptions),
board: this.formatCurrency(this.priceBoard),
runningCosts: this.formatCurrency(this.priceRunningCosts),
total: this.formatCurrency(this.priceTotal)
if (1 === option.type) {
price += option.price;
} else if (2 === option.type) {
price += option.price * this.totalPax;
} else if (3 === option.type) {
price += option.price * this.nightsCount;
} else if (4 === option.type) {
price += option.price * this.nightsCount * this.totalPax;
}
}
return price;
},
mounted () {
Vue.nextTick(() => {
this.picker = $(this.$refs.picker).flatpickr({
mode: 'range',
dateFormat: 'd.m.Y',
locale: German,
onChange: selectedDates => {
if (2 === selectedDates.length) {
this.selectedFrom = dayjs(selectedDates[0]);
this.selectedTo = dayjs(selectedDates[1]);
this.updateSelectedRange();
}
}
});
this.initSelectableRanges();
});
priceBoard() {
let price = 0;
if (this.selectedBoard) {
price = this.selectedBoard.price * this.nightsCount * this.totalPax;
}
return price;
},
priceShortTerm() {
let price = 0;
const totalPricePax = (this.pricePax.base + this.pricePax.additional);
if (2 === this.nightsCount) {
price = totalPricePax * 0.2;
}
if (3 === this.nightsCount) {
price = totalPricePax * 0.1;
}
return price;
},
priceRunningCosts() {
if (this.selectedBoard) {
return 0;
}
return this.nightsCount * this.selectedPax * 1.7;
},
priceTotal() {
return this.pricePax.base + this.pricePax.additional + this.priceOptions
+ this.priceBoard + this.priceShortTerm + this.priceRunningCosts;
},
priceSummary() {
return {
paxBase: this.formatCurrency(this.pricePax.base),
paxAdditional: this.formatCurrency(this.pricePax.additional),
shortTerm: this.formatCurrency(this.priceShortTerm),
options: this.formatCurrency(this.priceOptions),
board: this.formatCurrency(this.priceBoard),
runningCosts: this.formatCurrency(this.priceRunningCosts),
total: this.formatCurrency(this.priceTotal)
}
}
},
mounted() {
Vue.nextTick(() => {
this.picker = $(this.$refs.picker).flatpickr({
mode: 'range',
dateFormat: 'd.m.Y',
locale: German,
onChange: selectedDates => {
if (2 === selectedDates.length) {
this.selectedFrom = dayjs(selectedDates[0]);
this.selectedTo = dayjs(selectedDates[1]);
this.updateSelectedRange();
}
}
});
this.initSelectableRanges();
});
}
}
</script>
<style scoped lang="scss">
.form-wrapper {
position: relative;
}
.form-control[readonly] {
background-color: white;
}
.form-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(white, 0.85);
}
.form-wrapper {
position: relative;
}
.form-control[readonly] {
background-color: white;
}
.form-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(white, 0.85);
}
</style>
@@ -29,7 +29,19 @@
</tr>
<tr>
<th>Anzahl der Personen</th>
<td>{pax}</td>
<td>{pax -> v:or(alternative: '-')}</td>
</tr>
<tr>
<th>Anzahl der Personen (ab 15 Jahre)</th>
<td>{paxGroup3 -> v:or(alternative: '-')}</td>
</tr>
<tr>
<th>Anzahl der Personen (7-14 Jahre)</th>
<td>{paxGroup2 -> v:or(alternative: '-')}</td>
</tr>
<tr>
<th>Anzahl der Personen (4-6 Jahre)</th>
<td>{paxGroup1 -> v:or(alternative: '-')}</td>
</tr>
<tr>
<th>Verpflegung</th>