PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.31
Booking for Appointments and Events Calendar – Amelia v1.2.31
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / assets / views / backend / services / DialogService.vue
ameliabooking / assets / views / backend / services Last commit date
DialogCategory.vue 1 year ago DialogPackage.vue 2 years ago DialogPackageBooking.vue 2 years ago DialogResource.vue 2 years ago DialogService.vue 1 year ago Packages.vue 1 year ago PackagesListCollapsed.vue 2 years ago Services.vue 1 year ago
DialogService.vue
1918 lines
1 <template>
2 <div>
3 <!-- Dialog Loader -->
4 <div class="am-dialog-loader" v-show="passedService === null || dialogLoading">
5 <div class="am-dialog-loader-content">
6 <img :src="$root.getUrl+'public/img/spinner.svg'" class="">
7 <p>{{ $root.labels.loader_message }}</p>
8 </div>
9 </div>
10
11 <div class="am-dialog-scrollable" :class="{'am-edit':service.id !== 0}" v-if="passedService !== null && !dialogLoading">
12
13 <!-- Dialog Header -->
14 <div class="am-dialog-header">
15 <el-row>
16 <el-col :span="18">
17 <h2 v-if="service.id !== 0">{{ $root.labels.edit_service }}</h2>
18 <h2 v-else>{{ $root.labels.new_service }}</h2>
19 </el-col>
20 <el-col :span="6" class="align-right">
21 <el-button @click="closeDialog" class="am-dialog-close" size="small" icon="el-icon-close"></el-button>
22 </el-col>
23 </el-row>
24 </div>
25
26 <!-- Form -->
27 <el-form :model="service" ref="service" :rules="rulesService" label-position="top">
28 <el-tabs v-model="serviceTabs">
29
30 <!-- Service Details -->
31 <el-tab-pane :label="$root.labels.details" name="service_details">
32
33 <!-- Profile Photo -->
34 <div class="am-service-profile">
35
36 <picture-upload
37 :delete-icon-visibility="service.pictureThumbPath && service.pictureFullPath"
38 :edited-entity="this.service"
39 :entity-name="'service'"
40 @pictureSelected="servicePictureSelected"
41 v-on:deleteImage="() => deleteImage(service)"
42 >
43 </picture-upload>
44 <el-popover
45 ref="color-popover"
46 v-model="colorPopover"
47 placement="bottom"
48 width="160"
49 trigger="click"
50 popper-class="am-color-popover"
51 >
52 <span
53 v-for="color in serviceColors" :key="color"
54 class="am-service-color"
55 :class="{ 'color-active' : '#' + color === service.color}"
56 @click="changeServiceColor"
57 :data-color="'#'+color"
58 :style="'background-color:#'+color"
59 >
60 </span>
61
62 <el-form-item :label="$root.labels.hex + ':'">
63 <el-input v-model="service.color" auto-complete="off"></el-input>
64 </el-form-item>
65 <div class="align-right">
66 <el-button type="primary" size="mini" @click="colorPopover = false">{{ $root.labels.ok }}</el-button>
67 </div>
68
69 </el-popover>
70 <span class="am-service-color" :style="bgColor(service.color)" v-popover:color-popover></span>
71 <h2>{{ service.name }}</h2>
72 </div>
73
74 <!-- Name -->
75 <el-form-item prop="name">
76 <label slot="label">
77 {{ $root.labels.name + ':' }}
78 <div class="am-service-translate" @click="showDialogTranslate('serviceName')">
79 <img class="am-dialog-translate-svg" width="16px" :src="$root.getUrl+'public/img/translate.svg'">
80 {{ $root.labels.translate }}
81 </div>
82 </label>
83 <el-input id="am-service-name" v-model="service.name" auto-complete="off" @input="clearValidation()" @change="trimProperty(service, 'name')"></el-input>
84 </el-form-item>
85
86 <!-- Category -->
87 <el-form-item :label="$root.labels.category + ':'" prop="categoryId">
88 <el-select
89 v-model="service.categoryId"
90 placeholder=""
91 @change="clearValidation()"
92 >
93 <el-option
94 v-for="item in visibleCategories"
95 :disabled="item.disabled"
96 :key="item.id"
97 :label="item.name"
98 :value="item.id">
99 </el-option>
100 </el-select>
101 <img :src="$root.getUrl+'public/img/oval-spinner.svg'" class="svg-amelia is-spinner">
102 </el-form-item>
103
104 <!-- Show On Front -->
105 <div :class="licenceClass('starter')" class="am-setting-box am-switch-box">
106 <el-row type="flex" align="middle" :gutter="24">
107 <el-col :span="19">
108 {{ $root.labels.service_show_on_site }}
109 <el-tooltip placement="top">
110 <div slot="content" v-html="$root.labels.service_show_on_site_tooltip"></div>
111 <i class="el-icon-question am-tooltip-icon"></i>
112 </el-tooltip>
113 </el-col>
114 <el-col :span="5" class="align-right">
115 <el-switch
116 v-model="service.show"
117 :disabled="notInLicence('starter')"
118 active-text=""
119 inactive-text=""
120 >
121 </el-switch>
122 </el-col>
123 </el-row>
124
125 <LicenceBlock :licence="'starter'"/>
126 </div>
127
128 <el-row :class="licenceClass()">
129 <el-form-item label="placeholder" :label="$root.labels.service_recurring_cycle + ':'">
130 <label slot="label">
131 {{ $root.labels.service_recurring_cycle }}:
132 <el-tooltip placement="top">
133 <div slot="content" v-html="$root.labels.service_recurring_cycle_tooltip"></div>
134 <i class="el-icon-question am-tooltip-icon"></i>
135 </el-tooltip>
136 </label>
137
138 <el-select
139 v-model="service.recurringCycle"
140 :disabled="notInLicence()"
141 @change="clearValidation()"
142 >
143 <el-option
144 v-for="cycle in recurringCycles"
145 :key="cycle.value"
146 :label="cycle.label"
147 :value="cycle.value"
148 >
149 </el-option>
150 </el-select>
151
152 </el-form-item>
153
154 <LicenceBlock/>
155 </el-row>
156
157 <el-row v-if="service.recurringCycle !== 'disabled'">
158 <el-form-item label="placeholder" :label="$root.labels.service_recurring_sub + ':'">
159 <label slot="label">
160 {{ $root.labels.service_recurring_sub }}:
161 <el-tooltip placement="top">
162 <div slot="content" v-html="$root.labels.service_recurring_sub_tooltip"></div>
163 <i class="el-icon-question am-tooltip-icon"></i>
164 </el-tooltip>
165 </label>
166
167 <el-select
168 v-model="service.recurringSub"
169 @change="clearValidation()"
170 >
171 <el-option
172 v-for="cycle in recurringSubs"
173 :key="cycle.value"
174 :label="cycle.label"
175 :value="cycle.value"
176 >
177 </el-option>
178 </el-select>
179
180 </el-form-item>
181 </el-row>
182
183 <el-row v-if="service.recurringCycle !== 'disabled'">
184 <el-form-item label="placeholder" :label="$root.labels.service_recurring_payment + ':'">
185 <label slot="label">
186 {{ $root.labels.service_recurring_payment }}:
187 <el-tooltip placement="top">
188 <div slot="content" v-html="$root.labels.service_recurring_payment_tooltip"></div>
189 <i class="el-icon-question am-tooltip-icon"></i>
190 </el-tooltip>
191 </label>
192
193 <el-select
194 v-model="service.recurringPayment"
195 @change="clearValidation()"
196 >
197 <el-option
198 v-for="cycle in recurringPayments"
199 :key="cycle.value"
200 :label="cycle.label"
201 :value="cycle.value"
202 >
203 </el-option>
204 </el-select>
205
206 </el-form-item>
207 </el-row>
208
209 <!-- Limit Appointments per Customer -->
210 <div :class="licenceClass()" class="am-setting-box am-switch-box">
211 <el-row type="flex" align="middle" :gutter="24">
212 <el-col :span="19">
213 {{ $root.labels.limit_app_per_customer }}
214 <el-tooltip placement="top">
215 <div slot="content" v-html="$root.labels.limit_app_per_customer_tt"></div>
216 <i class="el-icon-question am-tooltip-icon"></i>
217 </el-tooltip>
218 </el-col>
219 <el-col :span="5" class="align-right">
220 <el-switch
221 v-model="service.limitPerCustomer.enabled"
222 :disabled="notInLicence()"
223 >
224 </el-switch>
225 </el-col>
226 </el-row>
227
228 <el-row :gutter="24" v-if="service.limitPerCustomer.enabled">
229 <el-col>
230 <el-form-item>
231 <label slot="label">
232 {{ $root.labels.number_of_appointments }}:
233 </label>
234 <el-input-number
235 :min="0"
236 id="am-limit-service-number"
237 :placeholder="$root.settings.roles.limitPerCustomerService.numberOfApp.toString()"
238 v-model="service.limitPerCustomer.numberOfApp">
239 </el-input-number>
240 </el-form-item>
241 </el-col>
242 </el-row>
243
244 <el-row :gutter="24" v-if="service.limitPerCustomer.enabled">
245 <el-col :span="12">
246 <el-form-item>
247 <label slot="label">
248 {{ $root.labels.time_frame }}:
249 </label>
250 <el-select
251 v-model="service.limitPerCustomer.timeFrame"
252 clearable
253 :placeholder="limitPerCustomerTimeFrames.find(t => t.value === $root.settings.roles.limitPerCustomerService.timeFrame).label"
254 >
255 <el-option
256 v-for="timeFrame in limitPerCustomerTimeFrames"
257 :key="timeFrame.value"
258 :label="timeFrame.label"
259 :value="timeFrame.value"
260 >
261 </el-option>
262 </el-select>
263 </el-form-item>
264 </el-col>
265 <el-col :span="12">
266 <el-form-item>
267 <label slot="label">
268 {{ $root.labels.period }}:
269 </label>
270 <el-input-number
271 :min="0"
272 id="am-limit-service-period"
273 v-model="service.limitPerCustomer.period"
274 :placeholder="$root.settings.roles.limitPerCustomerService.period.toString()"
275 ></el-input-number>
276 </el-form-item>
277 </el-col>
278 </el-row>
279
280
281 <el-row v-if="service.limitPerCustomer.enabled" :gutter="24" style="margin-top: 10px; display: flex;align-items: baseline;">
282 <el-col :span="12">
283 {{ $root.labels.limit_app_per_customer_from }}:
284 <el-tooltip placement="top">
285 <div slot="content" v-html="$root.labels.limit_app_per_customer_from_tt"></div>
286 <i class="el-icon-question am-tooltip-icon"></i>
287 </el-tooltip>
288 </el-col>
289 <el-col :span="12">
290 <el-select
291 v-model="service.limitPerCustomer.from"
292 :placeholder="limitPerCustomerFrom.find(t => t.value === $root.settings.roles.limitPerCustomerService.from).label"
293 clearable
294 >
295 <el-option
296 v-for="option in limitPerCustomerFrom"
297 :key="option.value"
298 :label="option.label"
299 :value="option.value"
300 >
301 </el-option>
302 </el-select>
303 </el-col>
304 </el-row>
305
306 <LicenceBlock/>
307 </div>
308
309 <!-- Employees -->
310 <el-form-item v-if="!notInLicence('starter')" :label="$root.labels.employees + ':'" prop="providers" id="providersElement">
311 <el-select
312 v-model="service.providers"
313 value-key="id"
314 placeholder=""
315 multiple
316 collapse-tags
317 @change="changeEmployees"
318 >
319 <el-option
320 v-for="item in visibleEmployees"
321 :key="item.id"
322 :label="item.firstName + ' ' + item.lastName"
323 :value="item"
324 >
325 </el-option>
326 </el-select>
327 </el-form-item>
328
329 <!-- Description -->
330 <content-block
331 :label="$root.labels.description"
332 :entity="service"
333 textProperty="description"
334 htmlProperty="descriptionHtml"
335 @showDialogTranslate="showDialogTranslate('serviceDescription')"
336 :hasTranslation="true"
337 :hasQuill="true"
338 :textModeProp="true"
339 :allowImage="false"
340 >
341 </content-block>
342 </el-tab-pane>
343
344 <!-- Duration & Pricing -->
345 <el-tab-pane :label="$root.labels.duration_and_pricing" name="pricing">
346 <el-row :gutter="24">
347
348 <!-- Duration -->
349 <el-col :span="12">
350 <el-form-item :label="$root.labels.duration + ':'" prop="duration">
351 <el-select
352 v-model="service.duration"
353 placeholder=""
354 @change="clearValidation()"
355 >
356 <el-option
357 v-for="item in getPossibleDurationsInSeconds(service.duration)"
358 :key="item"
359 :label="secondsToNiceDuration(item)"
360 :disabled="isDurationSelected(item)"
361 :value="item"
362 >
363 </el-option>
364 </el-select>
365 </el-form-item>
366 </el-col>
367
368 <!-- Price -->
369 <el-col :span="12">
370 <el-form-item :label="$root.labels.price + ':'" prop="price">
371 <div class="el-input">
372 <money v-model="service.price" v-bind="moneyComponentData" @input="priceChanged" class="el-input__inner"></money>
373 </div>
374 </el-form-item>
375 </el-col>
376
377 </el-row>
378
379 <!-- Pending Time -->
380 <div :class="licenceClass('starter')">
381 <el-row :gutter="24">
382
383 <!-- Buffer Time Before -->
384 <el-col :span="12">
385 <el-form-item label="placeholder">
386 <label slot="label">
387 {{ $root.labels.service_buffer_time_before }}:
388 <el-tooltip placement="top">
389 <div slot="content" v-html="$root.labels.service_buffer_time_before_tooltip"></div>
390 <i class="el-icon-question am-tooltip-icon"></i>
391 </el-tooltip>
392 </label>
393 <el-select
394 v-model="service.timeBefore"
395 :disabled="notInLicence('starter')"
396 placeholder=""
397 clearable
398 @change="clearValidation()"
399 >
400 <el-option
401 v-for="item in getPossibleDurationsInSeconds(service.timeBefore)"
402 :key="item"
403 :label="secondsToNiceDuration(item)"
404 :value="item"
405 >
406 </el-option>
407 </el-select>
408 </el-form-item>
409 </el-col>
410
411 <!-- Buffer Time After -->
412 <el-col :span="12">
413 <el-form-item label="placeholder">
414 <label slot="label">
415 {{ $root.labels.service_buffer_time_after }}:
416 <el-tooltip placement="top">
417 <div slot="content" v-html="$root.labels.service_buffer_time_after_tooltip"></div>
418 <i class="el-icon-question am-tooltip-icon"></i>
419 </el-tooltip>
420 </label>
421 <el-select
422 v-model="service.timeAfter"
423 :disabled="notInLicence('starter')"
424 placeholder=""
425 clearable
426 @change="clearValidation()"
427 >
428 <el-option
429 v-for="item in getPossibleDurationsInSeconds(service.timeAfter)"
430 :key="item"
431 :label="secondsToNiceDuration(item)"
432 :value="item"
433 >
434 </el-option>
435 </el-select>
436 </el-form-item>
437 </el-col>
438
439 </el-row>
440 <LicenceBlock :licence="'starter'"/>
441 </div>
442 <!-- Capacity -->
443 <div :class="licenceClass('starter')">
444 <el-row :gutter="24">
445
446 <!-- Minimum Capacity -->
447 <el-col :span="12">
448 <el-form-item label="placeholder">
449 <label slot="label">
450 {{ $root.labels.minimum_capacity }}:
451 <el-tooltip placement="top">
452 <div slot="content" v-html="$root.labels.minimum_capacity_tooltip"></div>
453 <i class="el-icon-question am-tooltip-icon"></i>
454 </el-tooltip>
455 </label>
456 <el-input-number
457 v-model="service.minCapacity" :min="1"
458 :disabled="notInLicence('starter')"
459 @input="checkCapacityLimits()"
460 >
461 </el-input-number>
462 </el-form-item>
463 </el-col>
464
465 <!-- Maximum Capacity -->
466 <el-col :span="12">
467 <el-form-item label="placeholder">
468 <label slot="label">
469 {{ $root.labels.maximum_capacity }}:
470 <el-tooltip placement="top">
471 <div slot="content" v-html="$root.labels.maximum_capacity_tooltip"></div>
472 <i class="el-icon-question am-tooltip-icon"></i>
473 </el-tooltip>
474 </label>
475 <el-input-number
476 v-model="service.maxCapacity"
477 :disabled="notInLicence('starter')"
478 :min="service.minCapacity"
479 @input="changeCapacity"
480 >
481 </el-input-number>
482 </el-form-item>
483 </el-col>
484
485 </el-row>
486 <LicenceBlock :licence="'starter'"/>
487 </div>
488
489
490 <!-- Bringing Anyone -->
491 <div class="am-setting-box am-switch-box" v-if="service.maxCapacity > 1">
492 <el-row type="flex" align="middle" :gutter="24">
493 <el-col :span="19">
494 {{ $root.labels.limit_extra_people }}
495 <el-tooltip placement="top">
496 <div slot="content" v-html="$root.labels.limit_extra_people_tooltip"></div>
497 <i class="el-icon-question am-tooltip-icon"></i>
498 </el-tooltip>
499 </el-col>
500 <el-col :span="5" class="align-right">
501 <el-switch
502 v-model="maxExtraPeopleEnabled"
503 active-text=""
504 inactive-text=""
505 @change="changeMaxExtraPeople"
506 >
507 </el-switch>
508 </el-col>
509 </el-row>
510
511 <el-row v-if="maxExtraPeopleEnabled" style="margin-top: 10px">
512 <el-col>
513 <el-form-item label="placeholder">
514 <label slot="label">
515 {{ $root.labels.limit_extra_people_set }}:
516 </label>
517 <el-input-number
518 v-model="service.maxExtraPeople"
519 :min="service.minCapacity - 1"
520 :max="service.maxCapacity - 1"
521 @input="clearValidation()"
522 >
523 </el-input-number>
524 </el-form-item>
525 </el-col>
526 </el-row>
527 </div>
528
529 <!-- Bringing Anyone -->
530 <div class="am-setting-box am-switch-box" v-if="service.maxCapacity > 1">
531 <el-row type="flex" align="middle" :gutter="24">
532 <el-col :span="19">
533 {{ $root.labels.bringing_anyone }}
534 <el-tooltip placement="top">
535 <div slot="content" v-html="$root.labels.bringing_anyone_tooltip"></div>
536 <i class="el-icon-question am-tooltip-icon"></i>
537 </el-tooltip>
538 </el-col>
539 <el-col :span="5" class="align-right">
540 <el-switch
541 v-model="service.bringingAnyone"
542 active-text=""
543 inactive-text=""
544 >
545 </el-switch>
546 </el-col>
547 </el-row>
548 </div>
549
550 <!-- Aggregated Price -->
551 <div class="am-setting-box am-switch-box" v-if="service.maxCapacity > 1 && service.bringingAnyone">
552 <el-row type="flex" align="middle" :gutter="24">
553 <el-col :span="19">
554 {{ $root.labels.aggregated_price }}
555 <el-tooltip placement="top">
556 <div slot="content" v-html="$root.labels.aggregated_price_tooltip"></div>
557 <i class="el-icon-question am-tooltip-icon"></i>
558 </el-tooltip>
559 </el-col>
560 <el-col :span="5" class="align-right">
561 <el-switch
562 v-model="service.aggregatedPrice"
563 active-text=""
564 inactive-text=""
565 >
566 </el-switch>
567 </el-col>
568 </el-row>
569 </div>
570
571 <div v-if="service.maxCapacity > 1 || service.duration" class="am-custom-pricing">
572 <div class="am-custom-pricing-label">{{ $root.labels.custom_pricing }}</div>
573 <div id="am-service-pricing-person" class="am-setting-box am-switch-box" :class="licenceClass()" v-if="service.maxCapacity > 1">
574 <!-- Person Pricing Enabled -->
575 <el-row type="flex" align="middle" :gutter="24">
576 <el-col :span="19" style="display: flex; gap: 8px; align-items: center">
577 {{ $root.labels.custom_person_pricing_enabled }}
578 <span class="am-custom-pricing-label-new">
579 {{ $root.labels.new_caps }}
580 </span>
581 </el-col>
582 <el-col :span="5" class="align-right">
583 <el-switch
584 v-model="personPricingEnabled"
585 :disabled="notInLicence()"
586 active-text=""
587 inactive-text=""
588 @change="togglePersonPricing"
589 >
590 </el-switch>
591 </el-col>
592 </el-row>
593
594 <person-price
595 v-if="isPersonPricingEnabled(service.customPricing)"
596 :service="service"
597 :enabledDelete="true"
598 :enabledRange="true"
599 @disable="personPricingEnabled = false"
600 >
601 </person-price>
602
603 <LicenceBlock/>
604 </div>
605
606 <div id="am-service-pricing-duration" class="am-setting-box am-switch-box" :class="licenceClass()" v-if="service.duration">
607 <!-- Custom Duration Enabled -->
608 <el-row type="flex" align="middle" :gutter="24">
609 <el-col :span="19">
610 {{ $root.labels.custom_duration_pricing_enabled }}
611 </el-col>
612 <el-col :span="5" class="align-right">
613 <el-switch
614 v-model="durationPricingEnabled"
615 :disabled="notInLicence()"
616 active-text=""
617 inactive-text=""
618 @change="toggleDurationPricing"
619 >
620 </el-switch>
621 </el-col>
622 </el-row>
623
624 <custom-duration
625 v-if="isDurationPricingEnabled(service.customPricing)"
626 :service="service"
627 :enabledDelete="true"
628 :enabledAdd="true"
629 :enabledDuration="true"
630 >
631 </custom-duration>
632
633 <LicenceBlock/>
634 </div>
635 </div>
636
637 <div id="am-service-deposit" class="am-setting-box am-switch-box" :class="licenceClass()" v-if="depositAvailable()">
638 <!-- Deposit Enabled -->
639 <el-row type="flex" align="middle" :gutter="24">
640 <el-col :span="19">
641 {{ $root.labels.deposit_enabled }}
642 </el-col>
643 <el-col :span="5" class="align-right">
644 <el-switch
645 v-model="depositEnabled"
646 :disabled="notInLicence()"
647 active-text=""
648 inactive-text=""
649 @change="depositEnabledChanged"
650 >
651 </el-switch>
652 </el-col>
653 </el-row>
654
655 <el-row :gutter="24" v-if="depositEnabled" class="am-service-deposit">
656 <el-col :span="12">
657 <el-form-item>
658 <label slot="label">
659 {{ $root.labels.deposit_payment }}:
660 <el-tooltip placement="top">
661 <div slot="content" v-html="$root.labels.deposit_payment_tooltip"></div>
662 <i class="el-icon-question am-tooltip-icon"></i>
663 </el-tooltip>
664 </label>
665 <el-select
666 v-model="depositPayment"
667 placeholder=""
668 @change="depositChanged()"
669 >
670 <el-option
671 v-for="(item, index) in depositOptions"
672 :key="index"
673 :label="item.label"
674 :value="item.value"
675 >
676 </el-option>
677 </el-select>
678 </el-form-item>
679 </el-col>
680
681 <el-col :span="12">
682 <el-form-item :label="$root.labels.deposit_amount + (depositPayment === 'fixed' ? ' (' + getCurrencySymbol() + ')' : '') + (depositPayment === 'percentage' ? ' (%)' : '') + ':'" prop="deposit">
683 <div v-if="depositPayment === 'fixed'" class="el-input">
684 <money v-model="service.deposit" v-bind="moneyComponentData" @input="depositChanged" class="el-input__inner"></money>
685 </div>
686
687 <el-input-number
688 v-if="depositPayment === 'percentage'"
689 v-model="service.deposit"
690 :min="0"
691 :max="100"
692 @input="depositChanged()"
693 >
694 </el-input-number>
695 </el-form-item>
696 </el-col>
697 </el-row>
698
699 <el-row :gutter="24" v-if="depositEnabled && service.aggregatedPrice && service.maxCapacity > 1 && depositPayment === 'fixed'">
700 <el-col>
701 <el-checkbox v-model="service.depositPerPerson">
702 {{$root.labels.deposit_per_person}}
703 <el-tooltip placement="top">
704 <div slot="content" v-html="$root.labels.deposit_by_the_number_of_people"></div>
705 <i class="el-icon-question am-tooltip-icon"></i>
706 </el-tooltip>
707 </el-checkbox>
708 </el-col>
709 </el-row>
710
711 <el-row class="am-service-deposit" :gutter="24" v-if="depositEnabled">
712 <el-col>
713 <el-checkbox v-model="service.fullPayment">
714 {{$root.labels.allow_total_amount}}
715 <el-tooltip placement="top">
716 <div slot="content" v-html="$root.labels.allow_customers_to_pay_total"></div>
717 <i class="el-icon-question am-tooltip-icon"></i>
718 </el-tooltip>
719 </el-checkbox>
720 <hr>
721 </el-col>
722 </el-row>
723
724 <el-row :gutter="24" v-if="depositEnabled" class="am-service-deposit">
725 <el-col :span="24" :style="{display: 'flex'}">
726 <i class="el-icon-warning-outline" :style="{marginRight: '8px'}"></i>
727 <label>
728 {{service.extras.length === 0 ? $root.labels.deposit_info :
729 depositPayment === 'fixed' ? $root.labels.deposit_info_fixed :
730 $root.labels.deposit_info_percentage}}
731 </label>
732 </el-col>
733 </el-row>
734
735 <LicenceBlock/>
736 </div>
737 </el-tab-pane>
738
739 <!-- Gallery -->
740 <el-tab-pane :label="$root.labels.gallery" name="gallery">
741 <div class="am-gallery-images">
742 <el-row :gutter="24">
743 <draggable v-model="service.gallery" :options="draggableOptions" @end="dropGalleryImage">
744 <el-col :sm="12" v-for="(galleryImage, index) in service.gallery" :key="index">
745 <div class="am-gallery-image-wrapper">
746 <div class="am-gallery-image"
747 :style="{'background-image': 'url(' + galleryImage.pictureFullPath + ')'}">
748 <i class="el-icon-delete" @click="deleteGalleryImage(index)"></i>
749 </div>
750 <div class="am-gallery-image-title">
751 <span class="am-drag-handle">
752 <img class="svg-amelia" width="20px" :src="$root.getUrl+'public/img/burger-menu.svg'">
753 </span>
754 <span>{{ galleryImage.pictureFullPath.substring(galleryImage.pictureFullPath.lastIndexOf('/')+1) }}</span>
755 </div>
756 </div>
757 </el-col>
758 </draggable>
759
760 <!-- Add Gallery -->
761 <el-col :sm="12">
762 <div class="am-gallery-image-add">
763
764 <div>
765 <picture-upload
766 :multiple="true"
767 :edited-entity="null"
768 :entity-name="'gallery'"
769 @pictureSelected="galleryPictureSelected"
770 >
771 </picture-upload>
772 </div>
773 </div>
774 </el-col>
775
776 </el-row>
777 </div>
778
779 </el-tab-pane>
780
781 <!-- Extras -->
782 <el-tab-pane v-if="notInLicence('starter') ? licenceVisible() : true" :label="$root.labels.extras" name="extras">
783 <LicenceBlockHeader :licence="'starter'"/>
784
785 <div :class="licenceClassDisabled('starter')" class="am-setting-box am-switch-box" v-if="service.extras.length > 0">
786 <!-- mandatory extras -->
787 <el-row type="flex" align="middle" :gutter="24">
788 <el-col :span="19">
789 {{ $root.labels.mandatory_extra_enable }}
790 </el-col>
791 <el-col :span="5" class="align-right">
792 <el-switch
793 v-model="service.mandatoryExtra"
794 active-text=""
795 inactive-text=""
796 >
797 </el-switch>
798 </el-col>
799 </el-row>
800
801 <el-row :gutter="24" v-if="service.mandatoryExtra" class="am-service-deposit">
802 <el-col :span="24">
803 <el-form-item>
804 <label slot="label">
805 {{ $root.labels.min_required_extras }}:
806 </label>
807 <el-input-number
808 v-model="service.minSelectedExtras"
809 :min="1"
810 :max="service.extras.length"
811 @input="minSelectedExtrasChanged()"
812 >
813 </el-input-number>
814 </el-form-item>
815 </el-col>
816 </el-row>
817 </div>
818
819 <div
820 class="am-extra-list"
821 :class="licenceClassDisabled('starter')"
822 >
823 <el-button
824 :disabled="notInLicence('starter')"
825 @click="addExtra"
826 size="large"
827 type="primary"
828 class="am-dialog-create"
829 >
830 <i class="el-icon-plus"></i> <span class="button-text">{{ $root.labels.add_extra }}</span>
831 </el-button>
832
833 <draggable v-model="service.extras" :options="draggableOptions" @end="dropExtra">
834 <transition-group name="list-complete">
835 <div class="am-extra-item"
836 v-loading="extraToDeleteLoading && index === service.extras.indexOf(extraToDelete)"
837 v-for="(extra, index) in service.extras" :key="index + 1"
838 :id="'extra' + index">
839
840 <!-- Extra Preview -->
841 <el-row type="flex" align="top" class="">
842
843 <el-col :span="2">
844 <span class="am-drag-handle">
845 <img class="svg-amelia" width="20px" :src="$root.getUrl+'public/img/burger-menu.svg'">
846 </span>
847 </el-col>
848
849 <el-col :span="22">
850
851 <el-row type="flex" align="middle">
852 <el-col :span="18">
853 <h3>{{ extra.name }}</h3>
854 </el-col>
855 <el-col v-show="index !== service.extras.indexOf(editedExtra) || editedExtraOld !== null"
856 :span="6" class="extra-item-actions align-right">
857 <span @click="showEditExtraDialog(extra)">
858 <img class="svg-amelia edit" width="16px" :src="$root.getUrl+'public/img/edit.svg'">
859 </span>
860 <span
861 v-if="$root.settings.capabilities.canDelete === true"
862 @click="showDeleteExtraDialog(extra)"
863 >
864 <img class="svg-amelia" width="16px" :src="$root.getUrl+'public/img/delete.svg'">
865 </span>
866 </el-col>
867 </el-row>
868
869 <div class="am-extra-item-data"
870 v-show="(editedExtra === null || index !== service.extras.indexOf(editedExtra)) && (extraToDelete === null || index !== service.extras.indexOf(extraToDelete))">
871 <el-row :gutter="24">
872 <el-col :sm="7">
873 <span class="data-title">{{ $root.labels.duration }}:</span>
874 <span class="data-value">
875 {{ extra.duration ? secondsToNiceDuration(extra.duration) : '/'}}
876 </span>
877 </el-col>
878 <el-col :sm="7">
879 <span class="data-title">{{ $root.labels.price }}:</span>
880 <span class="data-value">{{ getFormattedPrice(extra.price) }}</span>
881 </el-col>
882 <el-col :sm="10">
883 <span class="data-title">{{ $root.labels.maximum_quantity }}:</span>
884 <span class="data-value">{{ extra.maxQuantity }}</span>
885 </el-col>
886 </el-row>
887 </div>
888
889 </el-col>
890 </el-row>
891
892 <!-- Extra Delete -->
893 <el-collapse-transition>
894 <div class="am-confirmation"
895 v-show="extraToDelete !== null && index === service.extras.indexOf(extraToDelete)">
896 <p>{{ $root.labels.delete_extra_confirmation }}?</p>
897 <div class="align-right">
898 <el-button size="small" @click="hideDeleteExtraDialog()">{{ $root.labels.cancel }}</el-button>
899 <el-button size="small" @click="deleteExtra(extra)" type="primary">{{ $root.labels.delete }}
900 </el-button>
901 </div>
902 </div>
903 </el-collapse-transition>
904
905 <!-- Extra Edit -->
906 <el-collapse-transition>
907 <div
908 v-show="extraEditDialog === true && index === service.extras.indexOf(editedExtra)">
909 <el-form :model="extra" ref="extra" :rules="rulesExtra" label-position="top">
910
911 <!-- Extra Name -->
912 <el-form-item prop="name">
913 <label slot="label" class="am-service-name-label">
914 {{ $root.labels.name + ':' }}
915 <div class="am-service-translate" @click="showDialogTranslate('extraName', index)">
916 <img class="am-dialog-translate-svg" width="16px" :src="$root.getUrl+'public/img/translate.svg'">
917 {{ $root.labels.translate }}
918 </div>
919 </label>
920 <el-input v-model="extra.name" auto-complete="off"></el-input>
921 </el-form-item>
922
923 <!-- Duration & Price -->
924 <el-row :gutter="24">
925
926 <!-- Duration -->
927 <el-col :sm="8" :xs="12">
928 <el-form-item :label="$root.labels.duration + ':'">
929 <el-select
930 v-model="extra.duration"
931 clearable
932 placeholder=""
933 >
934 <el-option
935 v-for="item in getPossibleDurationsInSeconds(extra.duration)"
936 :key="item"
937 :label="secondsToNiceDuration(item)"
938 :value="item"
939 >
940 </el-option>
941 </el-select>
942 </el-form-item>
943 </el-col>
944
945 <!-- Price -->
946 <el-col :sm="8" :xs="12">
947 <el-form-item :label="$root.labels.price + ':'" prop="price">
948 <div class="el-input">
949 <money v-model="extra.price" v-bind="moneyComponentData"
950 class="el-input__inner"></money>
951 </div>
952 </el-form-item>
953 </el-col>
954
955 <!-- Maximum Capacity -->
956 <el-col :sm="8" :xs="24">
957 <el-form-item :label="$root.labels.maximum_quantity + ':'">
958 <el-input-number v-model="extra.maxQuantity" :min="1"></el-input-number>
959 </el-form-item>
960 </el-col>
961
962 </el-row>
963
964 <!-- Aggregated Price -->
965 <div class="am-setting-box am-switch-box" v-if="service.maxCapacity > 1 && extra.aggregatedPrice !== null && service.bringingAnyone">
966 <el-row type="flex" align="middle" :gutter="24">
967 <el-col :span="19">
968 {{ $root.labels.aggregated_price }}
969 <el-tooltip placement="top">
970 <div slot="content" v-html="$root.labels.aggregated_price_tooltip"></div>
971 <i class="el-icon-question am-tooltip-icon"></i>
972 </el-tooltip>
973 </el-col>
974 <el-col :span="5" class="align-right">
975 <el-switch
976 v-model="extra.aggregatedPrice"
977 active-text=""
978 inactive-text=""
979 >
980 </el-switch>
981 </el-col>
982 </el-row>
983 </div>
984
985 <!-- Description -->
986 <content-block
987 :label="$root.labels.description"
988 :entity="service.extras[index]"
989 textProperty="description"
990 htmlProperty="descriptionHtml"
991 @showDialogTranslate="showDialogTranslate('extraDescription', index)"
992 :hasTranslation="true"
993 :hasQuill="true"
994 :textModeProp="true"
995 :allowImage="false"
996 >
997 </content-block>
998
999 <!-- Cancel & Save -->
1000 <div class="align-right">
1001 <el-button @click="cancelExtra(extra)" size="small">{{ $root.labels.cancel }}
1002 </el-button>
1003 <el-button size="small" @click="saveExtra(extra)" type="primary">{{ $root.labels.save }}
1004 </el-button>
1005 </div>
1006
1007 </el-form>
1008 </div>
1009 </el-collapse-transition>
1010
1011 </div>
1012 </transition-group>
1013 </draggable>
1014
1015 </div>
1016 </el-tab-pane>
1017
1018 <el-tab-pane :label="$root.labels.settings" name="settings">
1019 <entity-settings
1020 :settings="settings"
1021 :paymentsSettings="service.settings.payments"
1022 :generalSettings="service.settings.general"
1023 :zoomSettings="service.settings.zoom"
1024 :lessonSpaceSettings="service.settings.lessonSpace"
1025 :google-meet-settings="service.settings.googleMeet"
1026 :microsoft-teams-settings="service.settings.microsoftTeams"
1027 :providers="service.providers"
1028 >
1029 </entity-settings>
1030 </el-tab-pane>
1031
1032 </el-tabs>
1033 </el-form>
1034 </div>
1035
1036 <dialog-actions
1037 v-if="!dialogLoading"
1038 formName="service"
1039 urlName="services"
1040 :isNew="service.id === 0"
1041 :entity="service"
1042 :getParsedEntity="getParsedEntity"
1043 @validationTabFailCallback="validationTabFailCallback"
1044 :haveSaveConfirmation="haveSaveConfirmation"
1045 @validationFailCallback="validationFailCallback"
1046 :hasIcons="true"
1047 :updateStash="true"
1048
1049 :status="{
1050 on: 'visible',
1051 off: 'hidden'
1052 }"
1053
1054 :buttonText="{
1055 confirm: {
1056 save: {
1057 yes: $root.labels.update_for_all,
1058 no: $root.labels.no
1059 },
1060 status: {
1061 yes: service.status === 'visible' ? $root.labels.visibility_hide : $root.labels.visibility_show,
1062 no: $root.labels.visibility_show
1063 }
1064 }
1065 }"
1066
1067 :action="{
1068 haveAdd: true,
1069 haveEdit: true,
1070 haveStatus: true,
1071 haveRemove: $root.settings.capabilities.canDelete === true,
1072 haveRemoveEffect: true,
1073 haveDuplicate: true
1074 }"
1075
1076 :message="{
1077 success: {
1078 save: $root.labels.service_saved,
1079 remove: $root.labels.service_deleted,
1080 show: $root.labels.service_visible,
1081 hide: $root.labels.service_hidden
1082 },
1083 confirm: {
1084 save: $root.labels.confirm_global_change_service,
1085 remove: $root.labels.confirm_delete_service,
1086 show: $root.labels.confirm_show_service,
1087 hide: $root.labels.confirm_hide_service,
1088 duplicate: $root.labels.confirm_duplicate_service
1089 },
1090 }"
1091 >
1092 </dialog-actions>
1093
1094 </div>
1095 </template>
1096
1097 <script>
1098 import licenceMixin from '../../../js/common/mixins/licenceMixin'
1099 import imageMixin from '../../../js/common/mixins/imageMixin'
1100 import dateMixin from '../../../js/common/mixins/dateMixin'
1101 import durationMixin from '../../../js/common/mixins/durationMixin'
1102 import priceMixin from '../../../js/common/mixins/priceMixin'
1103 import helperMixin from '../../../js/backend/mixins/helperMixin'
1104 import settingsMixin from '../../../js/common/mixins/settingsMixin'
1105 import serviceMixin from '../../../js/common/mixins/serviceMixin'
1106 import servicePriceMixin from '../../../js/common/mixins/servicePriceMixin'
1107 import PictureUpload from '../parts/PictureUpload.vue'
1108 import Form from 'form-object'
1109 import Draggable from 'vuedraggable'
1110 import { Money } from 'v-money'
1111 import notifyMixin from '../../../js/backend/mixins/notifyMixin'
1112 import DialogActions from '../parts/DialogActions.vue'
1113 import EntitySettings from '../parts/EntitySettings.vue'
1114 import DialogTranslate from '../parts/DialogTranslate'
1115 import ContentBlock from '../parts/ContentBlock'
1116 import CustomDuration from '../../parts/assignedServices/CustomDuration'
1117 import PersonPrice from '../../parts/assignedServices/PersonPrice'
1118
1119 export default {
1120 mixins: [
1121 licenceMixin,
1122 imageMixin,
1123 dateMixin,
1124 durationMixin,
1125 priceMixin,
1126 serviceMixin,
1127 servicePriceMixin,
1128 notifyMixin,
1129 helperMixin,
1130 settingsMixin
1131 ],
1132
1133 props: {
1134 categories: null,
1135 passedService: null,
1136 employees: null,
1137 settings: null,
1138 futureAppointments: null,
1139 newExtraTranslations: null
1140 },
1141
1142 data () {
1143 let validateNonNegativePrice = (rule, price, callback) => {
1144 if (price < 0) {
1145 callback(new Error(this.$root.labels.enter_non_negative_price_warning))
1146 } else {
1147 callback()
1148 }
1149 }
1150
1151 let validatePositiveValue = (rule, price, callback) => {
1152 if (price <= 0) {
1153 this.serviceTabs = 'pricing'
1154
1155 callback(new Error(this.$root.labels.enter_positive_price_warning))
1156 } else {
1157 callback()
1158 }
1159 }
1160
1161 return {
1162 durationPricingEnabled: false,
1163 personPricingEnabled: false,
1164 durations: [],
1165 depositEnabled: false,
1166 mandatoryExtraEnabled: false,
1167 depositPayment: 'fixed',
1168 depositOptions: [
1169 {
1170 value: 'fixed',
1171 label: this.$root.labels.amount_fixed
1172 },
1173 {
1174 value: 'percentage',
1175 label: this.$root.labels.amount_percentage
1176 }
1177 ],
1178 executeUpdate: true,
1179 dialogLoading: true,
1180 appointmentsEmployees: [],
1181 colorPopover: false,
1182 draggableOptions: {
1183 animation: 150,
1184 group: 'people',
1185 handle: '.am-drag-handle'
1186 },
1187 editedExtra: null,
1188 editedExtraOld: null,
1189 extraEditDialog: false,
1190 extraToDelete: null,
1191 extraToDeleteLoading: false,
1192 form: new Form(),
1193 rulesExtra: {
1194 name: [
1195 {required: true, message: this.$root.labels.enter_extra_name_warning, trigger: 'submit'}
1196 ],
1197 price: [
1198 {validator: validateNonNegativePrice, trigger: 'submit'},
1199 {required: true, message: this.$root.labels.enter_extra_price_warning, trigger: 'submit', type: 'number'}
1200 ]
1201 },
1202 rulesService: {
1203 name: [
1204 {required: true, message: this.$root.labels.enter_name_warning, trigger: 'submit'}
1205 ],
1206 categoryId: [
1207 {
1208 required: true,
1209 message: this.$root.labels.select_service_category_warning,
1210 trigger: 'submit',
1211 type: 'number'
1212 }
1213 ],
1214 duration: [
1215 {
1216 required: true,
1217 message: this.$root.labels.select_service_duration_warning,
1218 trigger: 'submit',
1219 type: 'number'
1220 }
1221 ],
1222 price: [
1223 {validator: validateNonNegativePrice, trigger: 'submit'},
1224 {required: true, message: this.$root.labels.enter_service_price_warning, trigger: 'submit', type: 'number'}
1225 ],
1226 deposit: [
1227 {validator: validatePositiveValue, trigger: 'submit'}
1228 ],
1229 providers: [
1230 {required: true, message: this.$root.labels.select_service_employee_warning, trigger: 'submit'}
1231 ]
1232 },
1233 recurringCycles: [
1234 {
1235 label: this.$root.labels.disabled,
1236 value: 'disabled'
1237 },
1238 {
1239 label: this.$root.labels.all,
1240 value: 'all'
1241 },
1242 {
1243 label: this.$root.labels.recurring_type_daily,
1244 value: 'daily'
1245 },
1246 {
1247 label: this.$root.labels.recurring_type_weekly,
1248 value: 'weekly'
1249 },
1250 {
1251 label: this.$root.labels.recurring_type_monthly,
1252 value: 'monthly'
1253 }
1254 ],
1255 recurringSubs: [
1256 {
1257 label: this.$root.labels.disabled,
1258 value: 'disabled'
1259 },
1260 {
1261 label: this.$root.labels.service_recurring_sub_future,
1262 value: 'future'
1263 },
1264 {
1265 label: this.$root.labels.service_recurring_sub_past,
1266 value: 'past'
1267 },
1268 {
1269 label: this.$root.labels.service_recurring_sub_both,
1270 value: 'both'
1271 }
1272 ],
1273 recurringPayments: [
1274 {
1275 label: this.$root.labels.service_recurring_payment_none,
1276 value: 0
1277 },
1278 {
1279 label: this.$root.labels.service_recurring_payment_all,
1280 value: 999
1281 }
1282 ],
1283 limitPerCustomerTimeFrames: [
1284 {
1285 label: this.$root.labels.hour,
1286 value: 'hour'
1287 },
1288 {
1289 label: this.$root.labels.day,
1290 value: 'day'
1291 },
1292 {
1293 label: this.$root.labels.week,
1294 value: 'week'
1295 },
1296 {
1297 label: this.$root.labels.month,
1298 value: 'month'
1299 },
1300 {
1301 label: this.$root.labels.year,
1302 value: 'year'
1303 }
1304 ],
1305 limitPerCustomerFrom: [
1306 {
1307 label: this.$root.labels.limit_app_date_booked,
1308 value: 'dateBooked'
1309 },
1310 {
1311 label: this.$root.labels.limit_app_booking_date,
1312 value: 'bookingDate'
1313 }
1314 ],
1315 service: null,
1316 serviceColors: [
1317 '1788FB',
1318 '4BBEC6',
1319 'FBC22D',
1320 'FA3C52',
1321 'D696B8',
1322 '689BCA',
1323 '26CC2B',
1324 'FD7E35',
1325 'E38587',
1326 '774DFB'
1327 ],
1328 serviceTabs: 'service_details',
1329 style: '',
1330 maxExtraPeopleEnabled: false
1331 }
1332 },
1333
1334 created () {
1335 Form.defaults.axios = this.$http
1336
1337 this.instantiateDialog()
1338 },
1339
1340 updated () {
1341 this.instantiateDialog()
1342 if (this.service && (!this.service.limitPerCustomer || !this.service.limitPerCustomer.period)) {
1343 let element = document.getElementById('am-limit-service-period')
1344 if (element) {
1345 element.getElementsByClassName('el-input__inner')[0].value = ''
1346 }
1347 }
1348 if (this.service && (!this.service.limitPerCustomer || !this.service.limitPerCustomer.numberOfApp)) {
1349 let element = document.getElementById('am-limit-service-number')
1350 if (element) {
1351 element.getElementsByClassName('el-input__inner')[0].value = ''
1352 }
1353 }
1354 },
1355
1356 methods: {
1357 changeMaxExtraPeople () {
1358 if (this.maxExtraPeopleEnabled && this.service.maxExtraPeople === null) {
1359 this.service.maxExtraPeople = this.service.minCapacity - 1
1360 }
1361 },
1362
1363 minSelectedExtrasChanged () {
1364 if (this.service.minSelectedExtras > this.service.extras.length) {
1365 this.service.minSelectedExtras = this.service.extras.length
1366 }
1367 },
1368
1369 toggleDurationPricing () {
1370 this.service.customPricing.enabled = this.durationPricingEnabled ? 'duration' : null
1371
1372 this.personPricingEnabled = false
1373 },
1374
1375 togglePersonPricing () {
1376 this.service.customPricing.enabled = this.personPricingEnabled ? 'person' : null
1377
1378 this.durationPricingEnabled = false
1379 },
1380
1381 changeCapacity () {
1382 if (this.service.customPricing.persons.length) {
1383 this.service.customPricing.persons[this.service.customPricing.persons.length - 1].range = null
1384
1385 let invalidRangeIndex = null
1386
1387 this.service.customPricing.persons.forEach((item, index) => {
1388 if (invalidRangeIndex === null && index > 0) {
1389 if (this.service.customPricing.persons[index - 1].range >= this.service.maxCapacity) {
1390 invalidRangeIndex = index
1391 }
1392 } else if (invalidRangeIndex === null && (this.service.customPricing.persons[index].range ? this.service.customPricing.persons[index].range : this.service.maxCapacity) >= this.service.maxCapacity) {
1393 invalidRangeIndex = 0
1394 }
1395 })
1396
1397 if (invalidRangeIndex !== null && invalidRangeIndex !== 0) {
1398 this.service.customPricing.persons = this.service.customPricing.persons.filter(
1399 (i, index) => index < invalidRangeIndex
1400 )
1401
1402 this.service.customPricing.persons[this.service.customPricing.persons.length - 1].range = null
1403 }
1404
1405 if (this.service.customPricing.persons.length <= 1 || invalidRangeIndex === 0) {
1406 this.service.customPricing.persons = []
1407
1408 this.service.customPricing.enabled = this.service.customPricing.enabled === 'person' ? null : this.service.customPricing.enabled
1409
1410 this.personPricingEnabled = false
1411 }
1412 }
1413
1414 this.clearValidation()
1415 },
1416
1417 depositAvailable () {
1418 let depositAvailable = parseFloat(this.service.price) > 0
1419
1420 if (this.isDurationPricingEnabled(this.service.customPricing)) {
1421 depositAvailable = depositAvailable || this.service.customPricing.durations.filter(i => i.price).length
1422 }
1423
1424 if (this.isPersonPricingEnabled(this.service.customPricing)) {
1425 depositAvailable = depositAvailable || this.service.customPricing.persons.filter(i => i.price).length
1426 }
1427
1428 return depositAvailable
1429 },
1430
1431 depositEnabledChanged () {
1432 if (this.depositEnabled) {
1433 this.service.depositPayment = this.depositPayment
1434 } else {
1435 this.service.depositPayment = 'disabled'
1436 }
1437 },
1438
1439 getMaxPrice () {
1440 let maxPrice = this.service.price
1441
1442 if (this.isDurationPricingEnabled(this.service.customPricing)) {
1443 this.service.customPricing.durations.forEach((item) => {
1444 if (item.price > maxPrice) {
1445 maxPrice = item.price
1446 }
1447 })
1448 }
1449
1450 if (this.isPersonPricingEnabled(this.service.customPricing)) {
1451 this.service.customPricing.persons.forEach((item) => {
1452 if (item.price > maxPrice) {
1453 maxPrice = item.price
1454 }
1455 })
1456 }
1457
1458 return maxPrice
1459 },
1460
1461 depositChanged () {
1462 let maxPrice = this.getMaxPrice()
1463
1464 if (this.service.deposit > maxPrice && this.depositPayment === 'fixed') {
1465 this.service.deposit = maxPrice
1466 }
1467 },
1468
1469 priceChanged () {
1470 let maxPrice = this.getMaxPrice()
1471
1472 if (this.service.deposit > maxPrice && this.depositPayment === 'fixed') {
1473 this.service.deposit = maxPrice
1474 }
1475
1476 if (this.service.customPricing.persons.length) {
1477 this.service.customPricing.persons[0].price = this.service.price
1478 }
1479 },
1480
1481 isDurationSelected (duration) {
1482 return this.isDurationPricingEnabled(this.service.customPricing) &&
1483 this.service.customPricing.durations.map(i => i.duration).includes(duration)
1484 },
1485
1486 instantiateDialog () {
1487 if (this.passedService !== null && this.executeUpdate === true) {
1488 this.service = JSON.parse(JSON.stringify(this.passedService))
1489
1490 this.maxExtraPeopleEnabled = this.service.maxExtraPeople !== null
1491
1492 if (this.service.description && this.service.description.startsWith('<!-- Content -->')) {
1493 this.service.descriptionHtml = this.service.description
1494 }
1495
1496 if (this.service.depositPayment === 'disabled') {
1497 this.depositEnabled = false
1498 } else {
1499 this.depositEnabled = true
1500
1501 this.depositPayment = this.service.depositPayment
1502 }
1503
1504 // If service is duplicated use duplicated service employees
1505 if (this.service.duplicated !== true) {
1506 this.$set(this.service, 'providers', this.selectedVisibleEmployees)
1507 }
1508
1509 if (this.service.limitPerCustomer === null) {
1510 this.service.limitPerCustomer = {enabled: this.$root.settings.roles.limitPerCustomerService.enabled, numberOfApp: null, period: null, timeFrame: null, from: null}
1511 } else if (this.service.id !== 0) {
1512 this.service.limitPerCustomer = JSON.parse(this.service.limitPerCustomer)
1513 }
1514
1515 // Set extra duration to empty string if duration is null, because of element-ui select clearable button
1516 for (let i = 0; i < this.service.extras.length; i++) {
1517 if (this.service.extras[i].duration === null) {
1518 this.service.extras[i].duration = ''
1519 }
1520 }
1521
1522 if (this.service.id in this.futureAppointments) {
1523 this.appointmentsEmployees = this.futureAppointments[this.service.id]
1524 }
1525 this.service.extras.sort((a, b) => (a.position > b.position) ? 1 : -1)
1526
1527 if (this.employees.length === 1) {
1528 this.service.providers = [this.employees[0]]
1529 }
1530
1531 if (!this.service.recurringCycle) {
1532 this.service.recurringCycle = 'disabled'
1533 }
1534
1535 this.durationPricingEnabled = this.isDurationPricingEnabled(this.service.customPricing)
1536
1537 this.personPricingEnabled = this.isPersonPricingEnabled(this.service.customPricing)
1538
1539 this.dialogLoading = false
1540 this.executeUpdate = false
1541 }
1542 },
1543
1544 validationFailCallback () {
1545 if (this.service.providers.length === 0) {
1546 this.serviceTabs = 'service_details'
1547 this.scrollViewInModal('providersElement')
1548 }
1549 },
1550
1551 galleryUpdated (gallery) {
1552 this.service.gallery = gallery
1553 },
1554
1555 closeDialog () {
1556 this.$emit('closeDialog')
1557 },
1558
1559 getParsedEntity (applyGlobally) {
1560 if (typeof this.$refs.extra !== 'undefined') {
1561 for (let i = 0; i < this.$refs.extra.length; i++) {
1562 this.$refs.extra[i].validate((valid) => {
1563 if (valid) {
1564 this.$refs.extra[i].clearValidate()
1565 return true
1566 } else {
1567 this.serviceTabs = 'extras'
1568 return false
1569 }
1570 })
1571 }
1572 }
1573
1574 let serviceSettings = this.prepareBookableEntityPaymentsForSave(this.prepareServiceSettingsForSave(this.service))
1575
1576 this.service.providers = this.service.providers.map(employee => employee.id)
1577 this.service.applyGlobally = applyGlobally
1578
1579 if (!this.depositEnabled) {
1580 this.service.depositPayment = 'disabled'
1581 } else {
1582 this.service.depositPayment = this.depositPayment
1583 }
1584
1585 if (!this.service.aggregatedPrice) {
1586 this.service.depositPerPerson = 0
1587 }
1588
1589 if (!this.maxExtraPeopleEnabled || this.service.maxCapacity <= 1) {
1590 this.service.maxExtraPeople = null
1591 }
1592
1593 return Object.assign(
1594 JSON.parse(JSON.stringify(this.service)),
1595 {
1596 settings: serviceSettings ? JSON.stringify(serviceSettings) : null,
1597 customPricing: this.getJsonCustomPricing(this.service),
1598 limitPerCustomer: JSON.stringify(this.service.limitPerCustomer)
1599 }
1600 )
1601 },
1602
1603 haveSaveConfirmation () {
1604 let customDurationChanged = false
1605
1606 let customPersonsChanged = false
1607
1608 this.service.customPricing.durations = this.service.customPricing.durations.filter(item => item.duration)
1609
1610 this.service.customPricing.durations.forEach((serviceItem) => {
1611 this.passedService.customPricing.durations.forEach((passedServiceItem) => {
1612 if (serviceItem.duration === passedServiceItem.duration && serviceItem.price !== passedServiceItem.price) {
1613 customDurationChanged = true
1614 }
1615 })
1616 })
1617
1618 this.service.customPricing.persons.forEach((serviceItem) => {
1619 this.passedService.customPricing.persons.forEach((passedServiceItem) => {
1620 if (serviceItem.range === passedServiceItem.range && serviceItem.price !== passedServiceItem.price) {
1621 customPersonsChanged = true
1622 }
1623 })
1624 })
1625
1626 return this.service.id && (
1627 this.passedService.price !== this.service.price ||
1628 this.passedService.minCapacity !== this.service.minCapacity ||
1629 this.passedService.maxCapacity !== this.service.maxCapacity ||
1630 customDurationChanged ||
1631 customPersonsChanged
1632 )
1633 },
1634
1635 bgColor (color) {
1636 return {'background-color': color}
1637 },
1638
1639 servicePictureSelected (pictureFullPath, pictureThumbPath) {
1640 this.service.pictureFullPath = pictureFullPath
1641 this.service.pictureThumbPath = pictureThumbPath
1642 },
1643
1644 galleryPictureSelected (pictureFullPath, pictureThumbPath) {
1645 this.clearValidation()
1646 this.service.gallery.push({
1647 id: 0,
1648 pictureFullPath: pictureFullPath,
1649 pictureThumbPath: pictureThumbPath,
1650 position: this.service.gallery.length + 1
1651 })
1652 },
1653
1654 deleteGalleryImage (index) {
1655 this.service.gallery.splice(index, 1)
1656
1657 for (let i = 0; i < this.service.gallery.length; i++) {
1658 this.service.gallery[i].position = i + 1
1659 }
1660 },
1661
1662 dropGalleryImage (e) {
1663 if (e.newIndex !== e.oldIndex) {
1664 let that = this
1665 this.service.gallery.forEach((image) => {
1666 image.position = that.service.gallery.indexOf(image) + 1
1667 })
1668 }
1669 },
1670
1671 changeServiceColor (e) {
1672 let siblings = Array.from(e.target.parentNode.children)
1673 siblings.forEach(function (sib) {
1674 if (sib.className.includes('color-active')) {
1675 sib.classList.remove('color-active')
1676 }
1677 })
1678 e.target.className = e.target.className + ' color-active'
1679 this.service.color = e.target.getAttribute('data-color')
1680 },
1681
1682 checkCapacityLimits () {
1683 this.clearValidation()
1684 if (this.service.minCapacity > this.service.maxCapacity) {
1685 this.service.maxCapacity = this.service.minCapacity
1686 }
1687 if (this.maxExtraPeopleEnabled && this.service.maxExtraPeople < this.service.minCapacity - 1) {
1688 this.service.maxExtraPeople = this.service.minCapacity - 1
1689 }
1690 },
1691
1692 showEditExtraDialog (extra) {
1693 if (extra.aggregatedPrice === null) {
1694 extra.aggregatedPrice = this.service.aggregatedPrice
1695 }
1696
1697 this.handleExtraForm()
1698 this.editedExtra = extra
1699 this.editedExtraOld = Object.assign({}, extra)
1700 this.extraToDelete = null
1701 this.extraEditDialog = true
1702 },
1703
1704 cancelExtra (extra) {
1705 if (this.editedExtraOld !== null) {
1706 extra.name = this.editedExtraOld.name
1707 extra.duration = this.editedExtraOld.duration
1708 extra.price = this.editedExtraOld.price
1709 extra.maxQuantity = this.editedExtraOld.maxQuantity
1710 extra.description = this.editedExtraOld.description
1711 }
1712 this.editedExtra = null
1713 this.editedExtraOld = null
1714 this.extraEditDialog = false
1715
1716 // Remove newly created extra if it is not saved
1717 if (typeof extra.id === 'undefined' && extra.isNew === true) {
1718 this.service.extras.splice(-1, 1)
1719 }
1720 },
1721
1722 addExtra () {
1723 this.handleExtraForm()
1724 this.extraEditDialog = true
1725 this.editedExtra = {
1726 name: '',
1727 duration: '',
1728 price: '',
1729 maxQuantity: 1,
1730 description: '',
1731 serviceId: this.service.id,
1732 position: this.service.extras.length + 1,
1733 isNew: true
1734 }
1735 this.service.extras.push(this.editedExtra)
1736 this.scrollViewInModal('extra' + (this.service.extras.length - 1))
1737 },
1738
1739 saveExtra (extra) {
1740 let index = this.service.extras.indexOf(extra)
1741 if (this.passedService.extras[index]) {
1742 this.service.extras[index].translations = this.passedService.extras[index].translations
1743 } else {
1744 this.service.extras[index].translations = this.newExtraTranslations
1745 }
1746 this.$refs.extra[index].validate((valid) => {
1747 if (valid) {
1748 this.editedExtra = null
1749 this.editedExtraOld = null
1750 this.extraEditDialog = false
1751 this.$refs.extra[index].clearValidate()
1752 } else {
1753 return false
1754 }
1755 })
1756
1757 this.service.extras[index].isNew = false
1758 this.$emit('extraSaved', this.service.extras[index], index)
1759 },
1760
1761 showDeleteExtraDialog (extra) {
1762 this.handleExtraForm()
1763 this.extraToDelete = extra
1764 this.editedExtra = null
1765 this.extraEditDialog = false
1766 },
1767
1768 handleExtraForm () {
1769 // If new extra form is active, remove this extra
1770 if (this.editedExtra !== null && typeof this.editedExtra.id === 'undefined') {
1771 this.service.extras.splice(-1, 1)
1772 }
1773 // If edit extra form is active, revert extra to old state
1774 if (this.editedExtraOld !== null) {
1775 this.cancelExtra(this.editedExtra)
1776 }
1777 },
1778
1779 hideDeleteExtraDialog () {
1780 this.extraToDelete = null
1781 this.extraEditDialog = true
1782 },
1783
1784 deleteExtra (extra) {
1785 if (typeof extra.id === 'undefined') {
1786 let index = this.service.extras.indexOf(extra)
1787 this.service.extras.splice(index, 1)
1788 } else {
1789 this.extraToDeleteLoading = true
1790 this.$http.post(`${this.$root.getAjaxUrl}/extras/delete/` + extra.id)
1791 .then(() => {
1792 let index = this.service.extras.indexOf(extra)
1793 this.service.extras.splice(index, 1)
1794 this.passedService.extras.splice(index, 1)
1795 if (this.passedService.minSelectedExtras > this.passedService.extras.length) {
1796 this.passedService.minSelectedExtras = this.passedService.extras.length
1797 }
1798 if (this.service.minSelectedExtras > this.service.extras.length) {
1799 this.service.minSelectedExtras = this.service.extras.length
1800 }
1801 if (this.service.minSelectedExtras === 0 || this.passedService.minSelectedExtras === 0) {
1802 this.service.mandatoryExtra = false
1803 this.passedService.mandatoryExtra = false
1804 }
1805 this.extraToDeleteLoading = false
1806 })
1807 .catch(() => {
1808 this.extraToDeleteLoading = false
1809 this.notify(this.$root.labels.error, this.$root.labels.extra_delete_fail, 'error')
1810 })
1811 }
1812
1813 for (let i = 0; i < this.service.extras.length; i++) {
1814 this.service.extras[i].position = i + 1
1815 }
1816 },
1817
1818 dropExtra (e) {
1819 if (e.newIndex !== e.oldIndex) {
1820 let that = this
1821 this.service.extras.forEach((extra) => {
1822 extra.position = that.service.extras.indexOf(extra) + 1
1823 })
1824 }
1825 },
1826
1827 clearValidation () {
1828 if (typeof this.$refs.service !== 'undefined') {
1829 this.$refs.service.clearValidate()
1830 }
1831 },
1832
1833 changeEmployees () {
1834 let selectedEmployees = this.service.providers.map(employee => employee.id)
1835
1836 let intersection = this.appointmentsEmployees.filter(x => !selectedEmployees.includes(x))
1837
1838 if (intersection.length) {
1839 let $this = this
1840 let unselectedEmployees = []
1841
1842 intersection.forEach(function (employeeId) {
1843 unselectedEmployees.push($this.visibleEmployees.filter(employee => employee.id === employeeId)[0])
1844 $this.notify($this.$root.labels.error, $this.$root.labels.service_provider_remove_fail, 'error')
1845 })
1846
1847 this.service.providers = this.service.providers.concat(unselectedEmployees).sort(function (a, b) {
1848 return (a.firstName + ' ' + a.lastName).localeCompare((b.firstName + ' ' + b.lastName))
1849 })
1850 }
1851 },
1852
1853 showDialogTranslate (dialogType, extraIndex = 0) {
1854 switch (dialogType) {
1855 case 'serviceName':
1856 this.$emit('showDialogTranslate', 'name', 'service')
1857 break
1858 case 'serviceDescription':
1859 this.$emit('showDialogTranslate', 'description', 'service')
1860 break
1861 case 'extraName':
1862 this.$emit('showDialogTranslate', 'name', 'extra', extraIndex)
1863 break
1864 case 'extraDescription':
1865 this.$emit('showDialogTranslate', 'description', 'extra', extraIndex)
1866 break
1867 }
1868 },
1869
1870 validationTabFailCallback () {
1871 this.serviceTabs = 'pricing'
1872 }
1873 },
1874
1875 computed: {
1876 visibleCategories () {
1877 return this.categories.filter(category => category.status === 'visible')
1878 },
1879
1880 visibleEmployees () {
1881 return this.employees.filter(employee =>
1882 (employee.status === 'visible') ||
1883 (employee.status === 'hidden' && employee.serviceList.map(service => service.id).indexOf(this.service.id) !== -1)
1884 )
1885 },
1886
1887 selectedVisibleEmployees () {
1888 return this.employees.filter(employee =>
1889 employee.serviceList.map(service => service.id).indexOf(this.service.id) !== -1
1890 )
1891 }
1892 },
1893
1894 watch: {
1895 'service.price' () {
1896 this.clearValidation()
1897 },
1898 'passedService.translations' () {
1899 if (this.service) {
1900 this.service.translations = this.passedService.translations
1901 }
1902 }
1903 },
1904
1905 components: {
1906 DialogTranslate,
1907 PictureUpload,
1908 Draggable,
1909 Money,
1910 EntitySettings,
1911 ContentBlock,
1912 DialogActions,
1913 CustomDuration,
1914 PersonPrice
1915 }
1916 }
1917 </script>
1918