3113 lines
104 KiB
Vue
3113 lines
104 KiB
Vue
<template>
|
||
<view>
|
||
<view class="hz-page limo-page-with-tabbar">
|
||
<u-navbar :safeAreaInsetTop="true" :placeholder="true" :title="title" :fixed="true" :autoBack="true"
|
||
leftIconColor="#000000" titleStyle="font-weight:bold;">
|
||
<view style="margin-top:2px;margin-right:3px;" slot="right">
|
||
<view style="border:1rpx solid #ccc;border-radius: 14px;padding:4px 12px;"
|
||
class="hz-flex-row hz-flex-vertical-mid">
|
||
<image @click="navTo('/pages/findPlayer/findPlayer',1,false)"
|
||
src="../../static/images/ico_find_player.png" class="hz-nav-btn"></image>
|
||
<view style="height:20px;border-left:1px solid #ccc;margin-left:8px;margin-right:8px;"></view>
|
||
<image @click="navTo('/booking/bookingRecord/bookingRecord?from=booking',0,false)"
|
||
src="../../static/images/ico_list.png" class="hz-nav-btn"></image>
|
||
</view>
|
||
</view>
|
||
</u-navbar>
|
||
<view class="hz-spacing hz-bg-white limo-content-bottom-pad" style="margin-top:8px;">
|
||
<view class="hz-card-profile hz-mr-b-30">
|
||
<view class="hz-flex-row">
|
||
<view style="width:60%">
|
||
<view class="hz-font-color-light hz-font-sm">{{$t("common.playerId")}}</view>
|
||
<view class="hz-font-bolder hz-font-lg">{{ playerData.playerId }}</view>
|
||
</view>
|
||
<view style="width:40%">
|
||
<view class="hz-font-color-light hz-font-sm">{{$t("common.tier")}}</view>
|
||
<view class="hz-font-bolder hz-font-lg">{{ playerData.tierName }}</view>
|
||
</view>
|
||
</view>
|
||
<view class="hz-flex-row hz-mr-t-30">
|
||
<view style="width:100%">
|
||
<view class="hz-font-color-light hz-font-sm">{{$t("common.name")}}</view>
|
||
<view class="hz-font-bolder hz-font-lg">{{playerData.salutation + " " + playerData.name }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Booking Information -->
|
||
<view class="limo-section">
|
||
<view class="hz-font-lg hz-font-bold">{{$t("newLimo.sectionBookingInformation")}}</view>
|
||
<view class="hz-split"></view>
|
||
<view class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("common.bookingType")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
height="36px"
|
||
placeholder=""
|
||
:options="bookingTypeOptions"
|
||
v-model="bookingType"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
@change="onBookingTypeChange"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.serviceType")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
height="36px"
|
||
placeholder=""
|
||
:options="serviceTypeOptions"
|
||
v-model="serviceType"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
@change="onServiceTypeChange"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.pickUpDate")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap" @click="showDatePicker = true">
|
||
<view class="hz-flex-row hz-flex-vertical-mid limo-tap-field">
|
||
<text class="hz-font-m" :style="{flex: 1, color: pickUpDate ? '#333' : '#999'}">{{ pickUpDate || $t("newLimo.selectDate") }}</text>
|
||
<u-icon name="calendar" size="20" color="#999"></u-icon>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.pickUpTime")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap" @click="showTimePicker = true">
|
||
<view class="hz-flex-row hz-flex-vertical-mid limo-tap-field">
|
||
<text class="hz-font-m" :style="{flex: 1, color: pickUpTime ? '#333' : '#999'}">{{ pickUpTime || $t("newLimo.selectTime") }}</text>
|
||
<u-icon name="clock" size="16" color="#999"></u-icon>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Booking Itinerary -->
|
||
<view class="limo-section">
|
||
<view class="hz-font-lg hz-font-bold">{{$t("newLimo.sectionBookingItinerary")}}</view>
|
||
<view class="hz-split"></view>
|
||
<view class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.carType")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
height="36px"
|
||
placeholder=""
|
||
:options="carTypeOptions"
|
||
v-model="carType"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.noOfPax")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<input
|
||
v-model="numberOfPax"
|
||
class="limo-text-input"
|
||
:class="{ 'limo-text-input--disabled': isEditMode }"
|
||
type="digit"
|
||
:disabled="isEditMode"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-col-full">
|
||
<view class="limo-field-label">{{$t("newLimo.pickUpLocation")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
class="limo-location-select"
|
||
height="36px"
|
||
placeholder=""
|
||
:options="pickUpPointOptions"
|
||
v-model="pickUpPoint"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
@change="onPickUpPointChange"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
<view v-if="shouldShowOtherPickUpPoint" class="limo-form-col-full">
|
||
<view class="limo-field-label">Other Pick Up Location</view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="otherPickUpPoint" class="limo-text-input" type="text" />
|
||
</view>
|
||
</view>
|
||
<view v-if="isAirportPickup" class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.flightDetail")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<input
|
||
v-model="flightDetail"
|
||
class="limo-text-input"
|
||
type="text"
|
||
:disabled="isEditMode"
|
||
@input="onFlightDetailInput"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.eta")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="eta" class="limo-text-input" type="text" :disabled="isEditMode" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="isAirportPickup" class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.flightStatus")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="flightStatus" class="limo-text-input" type="text" :disabled="isEditMode" />
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.greetType")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
class="limo-greet-select"
|
||
height="36px"
|
||
placeholder="Select Greet Type"
|
||
:options="greetTypeOptions"
|
||
v-model="greetType"
|
||
:multiple="true"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="!isAirportPickup && shouldShowGreetLocationFields" class="limo-form-col-full">
|
||
<view class="limo-field-label">{{$t("newLimo.greetType")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
class="limo-greet-select"
|
||
height="36px"
|
||
placeholder=""
|
||
:options="greetTypeOptions"
|
||
v-model="greetType"
|
||
:multiple="true"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
<view v-if="shouldShowGreetLocationFields" class="limo-form-col-full">
|
||
<view class="limo-field-label">{{$t("newLimo.signage")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="signage" class="limo-text-input" type="text" :disabled="isEditMode" />
|
||
</view>
|
||
</view>
|
||
<view v-for="(leg, index) in extraDropOffPoints" :key="'extra-drop-'+index" class="limo-form-col-full limo-dropoff-block">
|
||
<view class="limo-label-row">
|
||
<view class="limo-field-label">{{$t("newLimo.extraDropOffLocation")}}({{index + 1}})</view>
|
||
<text
|
||
class="hz-font-sm hz-font-color-danger"
|
||
@click.stop="onRemoveDropOffStop(index)"
|
||
>{{$t("newLimo.removeStop")}}</text>
|
||
</view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
class="limo-location-select"
|
||
height="36px"
|
||
placeholder=""
|
||
:options="dropOffPointOptions"
|
||
v-model="extraDropOffPoints[index]"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
@change="onExtraDropOffPointChange"
|
||
></lu-data-select>
|
||
</view>
|
||
<view v-if="shouldShowOtherExtraDropOffPoint(index)" class="limo-other-dropoff-field">
|
||
<view class="limo-field-label">{{$t("newLimo.extraDropOffLocation")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="otherExtraDropOffPoints[index]" class="limo-text-input" type="text" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-col-full limo-dropoff-block">
|
||
<view class="limo-label-row">
|
||
<view class="limo-field-label">{{$t("newLimo.dropOffLocation")}}<span class="mandatory">*</span></view>
|
||
<text
|
||
class="hz-font-sm hz-font-color-primary"
|
||
@click.stop="onAddDropOffStop"
|
||
>{{$t("newLimo.addStop")}}</text>
|
||
</view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
class="limo-location-select"
|
||
height="36px"
|
||
placeholder=""
|
||
:options="dropOffPointOptions"
|
||
v-model="dropOffPoint"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
@change="onDropOffPointChange"
|
||
></lu-data-select>
|
||
</view>
|
||
<view v-if="shouldShowOtherDropOffPoint" class="limo-other-dropoff-field">
|
||
<view class="limo-field-label">Other Drop Off Location</view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="otherDropOffPoint" class="limo-text-input" type="text" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="limo-form-col-full limo-dropoff-block">
|
||
<view class="limo-field-label">{{$t("newLimo.bookingContact")}}</view>
|
||
<view
|
||
v-for="(contact, index) in visibleBookingContacts"
|
||
:key="'booking-contact-'+index"
|
||
class="limo-booking-contact-row"
|
||
>
|
||
<view class="limo-booking-contact-label">Booking Contact #{{index + 1}}</view>
|
||
<view class="limo-booking-contact-controls">
|
||
<lu-data-select
|
||
class="limo-booking-contact-code-picker"
|
||
:class="{ 'limo-booking-contact-disabled': isEditMode }"
|
||
height="36px"
|
||
border="1px solid #e5e5e5"
|
||
placeholder=""
|
||
:options="countryCodeOptions"
|
||
v-model="bookingContacts[index].countryCode"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
@change="onContactCountryCodeChange('booking', index, $event)"
|
||
></lu-data-select>
|
||
<input
|
||
class="limo-text-input limo-booking-contact-phone"
|
||
:class="{ 'limo-text-input--disabled': isEditMode }"
|
||
type="number"
|
||
:value="contact.phoneNumber"
|
||
:disabled="isEditMode"
|
||
:placeholder="$t('newLimo.phoneNumber')"
|
||
@input="onContactPhoneInput('booking', index, $event)"
|
||
/>
|
||
<view
|
||
class="limo-booking-contact-btn"
|
||
:class="{ 'limo-booking-contact-disabled': isEditMode }"
|
||
@click.stop="index === 0 ? onAddContact('booking') : onRemoveContact('booking', index)"
|
||
>
|
||
{{ index === 0 ? '+' : '-' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Additional Options -->
|
||
<view class="limo-section">
|
||
<view class="hz-font-lg hz-font-bold">{{$t("newLimo.additionalOptions")}}</view>
|
||
<view class="hz-split"></view>
|
||
<view class="limo-additional-options">
|
||
<block v-for="option in additionalOptionItems" :key="option.name">
|
||
<u-checkbox-group
|
||
:value="option.checked === 1 ? [option.name] : []"
|
||
placement="row"
|
||
@change="onAdditionalOptionChange(option.flagKey, option.name, $event)"
|
||
>
|
||
<u-checkbox
|
||
activeColor="#C39A4B"
|
||
labelColor="#333333"
|
||
shape="circle"
|
||
customStyle="width:100%;"
|
||
:disabled="isEditMode"
|
||
:label="$t(option.labelKey)"
|
||
:name="option.name"
|
||
></u-checkbox>
|
||
</u-checkbox-group>
|
||
<block v-if="option.contactType && option.checked === 1">
|
||
<view
|
||
v-for="(contact, index) in getVisibleContactList(option.contactType)"
|
||
:key="option.contactType + '-contact-' + index"
|
||
class="limo-booking-contact-row"
|
||
>
|
||
<view class="limo-booking-contact-label">{{$t(option.contactLabelKey)}}<span class="mandatory">*</span></view>
|
||
<view class="limo-booking-contact-controls">
|
||
<lu-data-select
|
||
class="limo-booking-contact-code-picker"
|
||
:class="{ 'limo-booking-contact-disabled': isEditMode }"
|
||
height="36px"
|
||
border="1px solid #e5e5e5"
|
||
placeholder=""
|
||
:options="countryCodeOptions"
|
||
:value="contact.countryCode"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:disabled="isEditMode"
|
||
@change="onContactCountryCodeChange(option.contactType, index, $event)"
|
||
></lu-data-select>
|
||
<input
|
||
class="limo-text-input limo-booking-contact-phone"
|
||
:class="{ 'limo-text-input--disabled': isEditMode }"
|
||
type="number"
|
||
:value="contact.phoneNumber"
|
||
:disabled="isEditMode"
|
||
:placeholder="$t('newLimo.phoneNumber')"
|
||
@input="onContactPhoneInput(option.contactType, index, $event)"
|
||
/>
|
||
<!-- <view
|
||
v-if="option."
|
||
class="limo-booking-contact-btn"
|
||
:class="{ 'limo-booking-contact-disabled': isEditMode }"
|
||
@click.stop="index === 0 ? onAddContact(option.contactType) : onRemoveContact(option.contactType, index)"
|
||
>
|
||
{{ index === 0 ? '+' : '-' }}
|
||
</view> -->
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</block>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Approval Details -->
|
||
<view class="limo-section">
|
||
<view class="hz-font-lg hz-font-bold">{{$t("newLimo.sectionApprovalDetails")}}</view>
|
||
<view class="hz-split"></view>
|
||
<view class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.approvedBy")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<view
|
||
v-if="isEditMode"
|
||
class="limo-text-input limo-text-input--disabled limo-readonly-field"
|
||
>{{ approvedBy }}</view>
|
||
<lu-data-select
|
||
v-else
|
||
height="36px"
|
||
placeholder=""
|
||
:options="approverOptions"
|
||
v-model="approvedBy"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
@change="onApprovedByChange"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.paymentMethod")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<lu-data-select
|
||
height="36px"
|
||
placeholder=""
|
||
:options="paymentMethodOptionList"
|
||
v-model="paymentMethod"
|
||
:multiple="false"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:allowCustomValue="true"
|
||
changeType="objectValue"
|
||
:disabled="isEditMode"
|
||
></lu-data-select>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-row">
|
||
<view class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.tripContact")}}</view>
|
||
<view class="limo-control-wrap">
|
||
<input
|
||
v-model="tripContact"
|
||
class="limo-text-input"
|
||
:class="{ 'limo-text-input--disabled': isEditMode }"
|
||
type="digit"
|
||
:disabled="isEditMode"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view v-if="isOnHireServiceType" class="limo-form-cell">
|
||
<view class="limo-field-label">{{$t("newLimo.hour")}}<span class="mandatory">*</span></view>
|
||
<view class="limo-control-wrap">
|
||
<input v-model="hour" class="limo-text-input" type="digit" :disabled="isEditMode" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="limo-form-col-full">
|
||
<view class="limo-field-label">{{$t("newLimo.remarks")}}</view>
|
||
<view class="limo-remark-wrap">
|
||
<u--textarea v-model="remarks" :maxlength="500" :placeholder="$t('newLimo.remarksPlaceholder')" :count="false" autoHeight></u--textarea>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
|
||
<u-calendar
|
||
:show="showDatePicker"
|
||
:showTitle="false"
|
||
mode="single"
|
||
color="#C39A4B"
|
||
@confirm="onDateConfirm"
|
||
@close="showDatePicker = false"
|
||
confirm-text="CONFIRM"
|
||
text
|
||
placeholder=""
|
||
></u-calendar>
|
||
<u-datetime-picker
|
||
:show="showTimePicker"
|
||
v-model="pickUpTimeValue"
|
||
mode="time"
|
||
:confirmColor="'#C39A4B'"
|
||
:cancelColor="'#999999'"
|
||
:cancelText="'Cancel'"
|
||
:confirmText="'Confirm'"
|
||
@confirm="onTimeConfirm"
|
||
@cancel="showTimePicker = false"
|
||
@close="showTimePicker = false"
|
||
placeholder=""
|
||
></u-datetime-picker>
|
||
|
||
<u-tabbar
|
||
:fixed="true"
|
||
:placeholder="true"
|
||
:safeAreaInsetBottom="true"
|
||
customStyle="z-index:100;height:58px;"
|
||
>
|
||
<view @click="saveLimo()" class="book hz-flex-row hz-spacing"
|
||
:style="submitting ? 'opacity:0.55;pointer-events:none;' : ''">
|
||
<view class="hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center">
|
||
{{ submitting ? 'Submitting…' : $t("common.confirm") }}
|
||
</view>
|
||
</view>
|
||
</u-tabbar>
|
||
|
||
<u-popup :safeAreaInsetBottom="true" :safeAreaInsetTop="true" :mode="'center'" :round="12"
|
||
:show="showCriteriaNotQualifiedPopup">
|
||
<view class="booking-popup-card booking-popup-card--criteria"
|
||
:style="'width:' + windowWidth + 'px;'">
|
||
<view class="booking-popup-icon-row">
|
||
<image src="../../static/images/ico_err.png" style="width:64px;height:64px;"></image>
|
||
</view>
|
||
<view class="booking-popup-title booking-popup-title--criteria hz-font-md hz-font-bold hz-text-center">
|
||
Booking Criteria is not qualified
|
||
</view>
|
||
<view class="booking-popup-note booking-popup-note--criteria hz-text-center">
|
||
<view class="booking-popup-note-text">Player does not met criteria for</view>
|
||
<view class="booking-popup-note-text">trip "{{ currentBookingTypeLabel }}"</view>
|
||
<view class="booking-popup-note-text">Please obtain approval from</view>
|
||
<view class="booking-popup-note-text">your manager</view>
|
||
</view>
|
||
<view class="booking-popup-override booking-popup-override--criteria hz-text-center">
|
||
<view class="booking-popup-field-label hz-font-bold" style="margin-bottom: 8px;">Input Override Name or Employee ID</view>
|
||
<lu-data-select
|
||
class="booking-popup-override-select"
|
||
height="36px"
|
||
v-model="overrideName"
|
||
:options="overrideOptions"
|
||
placeholder="Select name or ID"
|
||
:filterable="true"
|
||
:clearable="false"
|
||
:allowCustomValue="true"
|
||
labelKey="label"
|
||
valueKey="value"
|
||
@inputChange="onOverrideInputChange"
|
||
@change="onOverrideChange"
|
||
></lu-data-select>
|
||
</view>
|
||
<view
|
||
@click="!submitting && confirmOverride()"
|
||
class="booking-popup-button booking-popup-button--primary booking-popup-button--criteria hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center"
|
||
>
|
||
{{ submitting ? 'Submitting…' : 'CONFIRM OVERRIDE' }}
|
||
</view>
|
||
<view
|
||
@click="closeCriteriaNotQualifiedPopup"
|
||
class="booking-popup-button booking-popup-button--secondary booking-popup-button--criteria hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center"
|
||
>
|
||
CANCEL
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
|
||
<u-popup :safeAreaInsetBottom="true" :safeAreaInsetTop="true" :mode="'center'" :round="12"
|
||
:show="showBookingCreatedPopup">
|
||
<view class="booking-popup-card booking-popup-card--criteria"
|
||
:style="'width:' + windowWidth + 'px;'">
|
||
<view class="booking-popup-icon-row">
|
||
<image src="../../static/images/warn.png" style="width:64px;height:64px;"></image>
|
||
</view>
|
||
<view class="booking-popup-title booking-popup-title--criteria hz-font-md hz-font-bold hz-text-center">
|
||
Booking Created
|
||
</view>
|
||
<view class="booking-popup-note booking-popup-note--criteria hz-text-center">
|
||
<view v-if="bookingSubmitServerMessage" class="booking-popup-note-text hz-font-bold">
|
||
{{ bookingSubmitServerMessage }}
|
||
</view>
|
||
<view class="booking-popup-note-text">Your booking is created.</view>
|
||
<view class="booking-popup-note-text">Transportation will confirm the</view>
|
||
<view class="booking-popup-note-text">booking shortly.</view>
|
||
</view>
|
||
<view
|
||
@click="closeBookingCreatedPopup"
|
||
class="booking-popup-button booking-popup-button--primary booking-popup-button--criteria hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center"
|
||
>
|
||
OK
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
<u-popup :safeAreaInsetBottom="true" :safeAreaInsetTop="true" :mode="'center'" :round="12"
|
||
:show="showBookingCreatedNoConfirmPopup">
|
||
<view class="booking-popup-card booking-popup-card--criteria"
|
||
:style="'width:' + windowWidth + 'px;'">
|
||
<view class="booking-popup-icon-row">
|
||
<image src="../../static/images/ico_suc.png" style="width:64px;height:64px;"></image>
|
||
</view>
|
||
<view class="booking-popup-title booking-popup-title--criteria hz-font-md hz-font-bold hz-text-center">
|
||
Booking Confirmed
|
||
</view>
|
||
<view
|
||
@click="closeBookingCreatedPopup"
|
||
class="booking-popup-button booking-popup-button--primary booking-popup-button--criteria hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center"
|
||
>
|
||
OK
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
<u-popup :show="showEditBookingSuccessPopup" mode="center" round="16px" :closeable="false" @close="closeEditBookingSuccessPopup">
|
||
<view class="booking-popup-card booking-popup-card--success"
|
||
:style="'width:' + windowWidth + 'px;'">
|
||
<view class="booking-popup-icon-row">
|
||
<image src="../../static/images/ico_suc.png" style="width:64px;height:64px;"></image>
|
||
</view>
|
||
<view class="booking-popup-title booking-popup-title--success hz-font-md hz-font-bold hz-text-center">
|
||
Edit Booking Successfully
|
||
</view>
|
||
<view @click="closeEditBookingSuccessPopup" class="booking-popup-button booking-popup-button--primary booking-popup-button--success hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center">
|
||
OK
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
<u-modal :content="msgStr" :show="showMsg" :confirmText="yesText" :showCancelButton="false" closeOnClickOverlay @confirm="showMsg=false" confirmColor="#C39A4B" @close="showMsg=false"></u-modal>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
createLimoBooking,
|
||
prepareBooking,
|
||
getLimoCarType,
|
||
getLimoGreetType,
|
||
getLimoLocation,
|
||
editLimoBooking,
|
||
getQualification,
|
||
getLimoOverrider,
|
||
getLimoApprover,
|
||
getLimoFlightDetails
|
||
} from '../../api/limoApi';
|
||
import { getCountryDialCodeOptions } from '../../common/countryDialCodes.js';
|
||
|
||
const DEFAULT_COUNTRY_CODE = '+853';
|
||
const FREE_OF_CHARGE_OPTION = {
|
||
value: 'Free of Charge',
|
||
label: 'Free of Charge'
|
||
};
|
||
const CONTACT_CONFIG = {
|
||
booking: {
|
||
listKey: 'bookingContacts',
|
||
visibleCountKey: 'visibleBookingContactCount',
|
||
maxCount: 3
|
||
},
|
||
tenOut: {
|
||
listKey: 'tenOutContacts',
|
||
visibleCountKey: 'visibleTenOutContactCount',
|
||
maxCount: 1
|
||
},
|
||
guest: {
|
||
listKey: 'guestContacts',
|
||
visibleCountKey: 'visibleGuestContactCount',
|
||
maxCount: 1
|
||
}
|
||
};
|
||
const BOOKING_TYPE_API_BY_CODE = {
|
||
'0': 'Local',
|
||
'1': 'Hong Kong',
|
||
'2': 'China',
|
||
'3': 'Bus',
|
||
'4': 'Independent VIP M&G'
|
||
};
|
||
const BOOKING_TYPE_ALIASES = {
|
||
local: 'Local',
|
||
'local booking': 'Local',
|
||
'local limo': 'Local',
|
||
hk: 'Hong Kong',
|
||
'hong kong': 'Hong Kong',
|
||
'hong kong booking': 'Hong Kong',
|
||
'hong kong limo': 'Hong Kong',
|
||
china: 'China',
|
||
'china booking': 'China',
|
||
'china limo': 'China',
|
||
bus: 'Bus',
|
||
'bus booking': 'Bus',
|
||
'independent vip m&g': 'Independent VIP M&G',
|
||
'independent vip m&g booking': 'Independent VIP M&G'
|
||
};
|
||
const QUALIFICATION_BOOKING_TYPE_BY_CODE = {
|
||
...BOOKING_TYPE_API_BY_CODE,
|
||
'0': 'Local Limo',
|
||
'1': 'Hong Kong Limo',
|
||
'2': 'China Limo',
|
||
'3': 'Bus Limo'
|
||
};
|
||
const BOOKING_TYPE_FIELDS = [
|
||
'bookingType',
|
||
'bookingTypes',
|
||
'bookingTypeId',
|
||
'bookingTypeIds',
|
||
'bookingTypeCodes',
|
||
'bookingTypeCode',
|
||
'supportedBookingTypes',
|
||
'limoBookingType',
|
||
'BookingType',
|
||
'BookingTypes',
|
||
'BookingTypeId',
|
||
'BookingTypeIds'
|
||
];
|
||
|
||
const isBlank = (value) => value == null || String(value).trim() === '';
|
||
const toStringOrEmpty = (value) => (value == null ? '' : String(value));
|
||
const toTrimmedString = (value) => (isBlank(value) ? '' : String(value).trim());
|
||
const formatEmployeeNameAndId = (name, id) => {
|
||
const employeeName = toTrimmedString(name);
|
||
const employeeId = toTrimmedString(id);
|
||
if (!employeeName) return employeeId;
|
||
if (
|
||
!employeeId ||
|
||
employeeName === employeeId ||
|
||
employeeName.endsWith(`(${employeeId})`)
|
||
) return employeeName;
|
||
return `${employeeName}(${employeeId})`;
|
||
};
|
||
const createEmptyContact = () => ({
|
||
countryCode: DEFAULT_COUNTRY_CODE,
|
||
phoneNumber: ''
|
||
});
|
||
const getFirstText = (data, keys) => {
|
||
for (const key of keys) {
|
||
if (data && !isBlank(data[key])) {
|
||
return toTrimmedString(data[key]);
|
||
}
|
||
}
|
||
return '';
|
||
};
|
||
const getResponseRecords = (response) => {
|
||
if (!response || response.success === false) {
|
||
return [];
|
||
}
|
||
if (Array.isArray(response)) {
|
||
return response;
|
||
}
|
||
if (Array.isArray(response.Records)) {
|
||
return response.Records;
|
||
}
|
||
if (Array.isArray(response.records)) {
|
||
return response.records;
|
||
}
|
||
return response.data && typeof response.data === 'object'
|
||
? getResponseRecords(response.data)
|
||
: [];
|
||
};
|
||
const isResponseStatusOk = (response) => {
|
||
const statusCode = response && response.StatusCode != null
|
||
? response.StatusCode
|
||
: response && response.statusCode;
|
||
return statusCode == null || String(statusCode).toUpperCase() === 'OK';
|
||
};
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
title: "",
|
||
showMsg: false,
|
||
msgStr: '',
|
||
yesText: this.$t("common.ok"),
|
||
playerData:'',
|
||
userData: null,
|
||
deviceData: null,
|
||
showDatePicker: false,
|
||
pickUpDate: '',
|
||
showTimePicker: false,
|
||
pickUpTime: '',
|
||
showCriteriaNotQualifiedPopup: false,
|
||
showBookingCreatedNoConfirmPopup:false,
|
||
windowWidth: 0,
|
||
overrideName: '',
|
||
overrideInputText: '',
|
||
overrideOptions: [],
|
||
pickUpTimeValue: "",
|
||
hour:"",
|
||
priceTypeArr: [],
|
||
showEditBookingSuccessPopup: false,
|
||
showBookingCreatedPopup: false,
|
||
editId: null,
|
||
confirmationNo: '',
|
||
bookingStatus: '',
|
||
createdAt: '',
|
||
createdBy: '',
|
||
overrideBy: '',
|
||
submitting: false,
|
||
bookingSubmitServerMessage: '',
|
||
submitLoading: false,
|
||
mode: 'create',
|
||
serviceType: '',
|
||
serviceTypeOptions: [
|
||
{ value: '0', label: 'Single Trip' },
|
||
{ value: '1', label: 'On Hire' }
|
||
],
|
||
approvedBy: '',
|
||
approverOptions: [],
|
||
editApproverIdSnapshot: '',
|
||
editApproverTextSnapshot: '',
|
||
paymentMethod: '',
|
||
paymentMethodOptionList: [],
|
||
hourlyRate: '',
|
||
hourlyRateOptions: [
|
||
{ value: '1_hour', label: '1 hour' },
|
||
{ value: '2_hours', label: '2 hours' },
|
||
{ value: '3_hours', label: '3 hours' }
|
||
],
|
||
tripPrice: '',
|
||
tripContact: '',
|
||
bookingContacts: [createEmptyContact(), createEmptyContact(), createEmptyContact()],
|
||
visibleBookingContactCount: 1,
|
||
isHighImportance: 0,
|
||
isTenOut: 0,
|
||
isContactGuestDirectly: 0,
|
||
tenOutContacts: [createEmptyContact(), createEmptyContact()],
|
||
guestContacts: [createEmptyContact(), createEmptyContact()],
|
||
visibleTenOutContactCount: 1,
|
||
visibleGuestContactCount: 1,
|
||
countryCodeOptions: getCountryDialCodeOptions(),
|
||
remarks: '',
|
||
greetType: [],
|
||
greetTypeItemsAll: [],
|
||
flightDetail: '',
|
||
flightDetailDebounceTimer: null,
|
||
eta: '',
|
||
flightStatus: '',
|
||
signage: '',
|
||
numberOfPax: '2',
|
||
bookingType: '',
|
||
bookingTypeOptions: [
|
||
{ value: '0', label: 'Local Booking' },
|
||
{ value: '1', label: 'Hong Kong Booking' },
|
||
{ value: '2', label: 'China Booking' }
|
||
],
|
||
carType: '',
|
||
carTypeOptionsAll: [],
|
||
pickUpPoint: '',
|
||
pickupPointId: '',
|
||
pickUpPointOptions: [],
|
||
otherPickUpPoint: '',
|
||
dropOffPoint: '',
|
||
dropOffPointId: '',
|
||
otherDropOffPoint: '',
|
||
extraDropOffPoints: [],
|
||
otherExtraDropOffPoints: [],
|
||
extraOffPoint: '',
|
||
extraOffPointId: '',
|
||
dropOffPointOptions: [],
|
||
}
|
||
},
|
||
computed: {
|
||
isEditMode() {
|
||
return this.mode === 'edit';
|
||
},
|
||
visibleBookingContacts() {
|
||
return this.getVisibleContactList('booking');
|
||
},
|
||
additionalOptionItems() {
|
||
return [
|
||
{
|
||
flagKey: 'isHighImportance',
|
||
name: 'highImportance',
|
||
labelKey: 'newLimo.highImportance',
|
||
checked: this.isHighImportance
|
||
},
|
||
{
|
||
flagKey: 'isTenOut',
|
||
name: 'tenOut',
|
||
labelKey: 'newLimo.tenOut',
|
||
checked: this.isTenOut,
|
||
contactType: 'tenOut',
|
||
contactLabelKey: 'newLimo.tenOutContact'
|
||
},
|
||
{
|
||
flagKey: 'isContactGuestDirectly',
|
||
name: 'contactGuestDirectly',
|
||
labelKey: 'newLimo.contactGuestDirectly',
|
||
checked: this.isContactGuestDirectly,
|
||
contactType: 'guest',
|
||
contactLabelKey: 'newLimo.guestContact'
|
||
}
|
||
];
|
||
},
|
||
isAirportPickup() {
|
||
const opt = this.selectedPickUpLocationOption;
|
||
return Boolean(opt && !isBlank(opt.airportCode) && Number(opt.airportCode) !== 0);
|
||
},
|
||
isHourlyRateSelected() {
|
||
return this.isOnHireServiceType || (Array.isArray(this.priceTypeArr) && this.priceTypeArr.includes('hour_rate'));
|
||
},
|
||
isOnHireServiceType() {
|
||
return this.getSelectValue(this.serviceType) === '1';
|
||
},
|
||
selectedPickUpLocationOption() {
|
||
const code = this.getSelectValue(this.pickUpPoint);
|
||
return (
|
||
(this.pickUpPointOptions || []).find((x) => String(x.value) === String(code)) || null
|
||
);
|
||
},
|
||
selectedPickUpTripPriceId() {
|
||
const opt = this.selectedPickUpLocationOption;
|
||
return opt ? this.parseNumber(opt.tripPriceId) : null;
|
||
},
|
||
shouldShowOtherPickUpPoint() {
|
||
return this.isOtherLocationSelection(this.pickUpPoint, this.pickUpPointOptions);
|
||
},
|
||
shouldShowOtherDropOffPoint() {
|
||
return this.isOtherLocationSelection(this.dropOffPoint, this.dropOffPointOptions);
|
||
},
|
||
pickUpPointRegionRaw() {
|
||
const opt = this.selectedPickUpLocationOption;
|
||
return opt ? toTrimmedString(opt.region) : '';
|
||
},
|
||
dropoffRegionRaw() {
|
||
const opt = this.getLocationOptionFromSelection(
|
||
this.dropOffPoint,
|
||
this.dropOffPointOptions
|
||
);
|
||
return opt ? toTrimmedString(opt.region) : '';
|
||
},
|
||
carTypeId() {
|
||
const raw = this.getSelectValue(this.carType);
|
||
if (raw === '' || raw == null) {
|
||
return 0;
|
||
}
|
||
const n = Number(raw);
|
||
return Number.isFinite(n) ? n : 0;
|
||
},
|
||
greetTypeId() {
|
||
if (!this.shouldShowGreetLocationFields) {
|
||
return '';
|
||
}
|
||
return this.getMultiSelectValues(this.greetType).join(',');
|
||
},
|
||
shouldShowGreetLocationFields() {
|
||
const opt = this.selectedPickUpLocationOption;
|
||
return Boolean(opt && toTrimmedString(opt.greetLocation) === '1');
|
||
},
|
||
ranking() {
|
||
const p = this.playerData;
|
||
if (!p) return '';
|
||
return String(p.tierName || p.tier || p.ranking || '').trim();
|
||
},
|
||
salutation() {
|
||
const p = this.playerData;
|
||
if (!p) return '';
|
||
return String(p.salutation || p.salution || '').trim();
|
||
},
|
||
lastName() {
|
||
const p = this.playerData;
|
||
if (!p) return '';
|
||
const fullName = String(p.name || '').trim();
|
||
if (!fullName) return '';
|
||
const parts = fullName.split(/\s+/);
|
||
return parts.length > 1 ? parts[parts.length - 1] : fullName;
|
||
},
|
||
employeeId() {
|
||
const u = this.userData;
|
||
if (!u) return '';
|
||
return String(u.userId || u.employeeId || '').trim();
|
||
},
|
||
shouldShowTripPriceField() {
|
||
if (this.isHourlyRateSelected) {
|
||
return false;
|
||
}
|
||
if (!/^other$/i.test(this.pickUpPointRegionRaw)) {
|
||
return false;
|
||
}
|
||
const opt = this.selectedPickUpLocationOption;
|
||
if (
|
||
opt &&
|
||
opt.freeOfCharge != null &&
|
||
!(Number.isNaN(Number(opt.freeOfCharge))) &&
|
||
Number(opt.freeOfCharge) === 1
|
||
) {
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
carTypeOptions() {
|
||
const bt = String(this.bookingType ?? '').trim();
|
||
const all = this.carTypeOptionsAll || [];
|
||
if (!bt) {
|
||
return all;
|
||
}
|
||
return all.filter((opt) => this.carTypeOptionMatchesBookingType(opt, bt));
|
||
},
|
||
greetTypeOptions() {
|
||
if (!Array.isArray(this.greetTypeItemsAll)) {
|
||
return [];
|
||
}
|
||
const bt = String(this.bookingType ?? '').trim();
|
||
const rows = !bt
|
||
? this.greetTypeItemsAll
|
||
: this.greetTypeItemsAll.filter((row) => this.rowMatchesGreetTypeBookingType(row, bt));
|
||
return rows.map((row) => ({
|
||
value: String(row.id),
|
||
label:
|
||
row.greetType != null && String(row.greetType).trim() !== ''
|
||
? String(row.greetType).trim()
|
||
: `GreetType ${row.id}`
|
||
}));
|
||
},
|
||
currentBookingTypeLabel() {
|
||
return this.getSelectLabel(this.bookingTypeOptions, this.bookingType) || '—';
|
||
},
|
||
/**
|
||
* 与后端 CreateLimoBookingRequest 字段对齐的请求体快照(可直接 JSON 序列化提交)
|
||
*/
|
||
limoBookingData() {
|
||
const p = this.playerData && typeof this.playerData === 'object' ? this.playerData : null;
|
||
const playerId = p ? toStringOrEmpty(p.playerId) : '';
|
||
const sal = p ? toTrimmedString(p.salutation) : '';
|
||
const nm = p ? toTrimmedString(p.name) : '';
|
||
const playerName = [sal, nm].filter(Boolean).join(' ').trim();
|
||
const pickUpPointLabel = this.getSelectLabel(this.pickUpPointOptions, this.pickUpPoint);
|
||
const dropOffPointLabel = this.getSelectLabel(this.dropOffPointOptions, this.dropOffPoint);
|
||
const carTypeLabel = this.getSelectLabel(this.carTypeOptions, this.carType);
|
||
const greetTypeText = this.shouldShowGreetLocationFields
|
||
? this.getMultiSelectLabels(this.greetTypeOptions, this.greetType).join(';')
|
||
: '';
|
||
const pax = this.parsePositiveInteger(this.numberOfPax);
|
||
const approverText = this.getApproverDisplayText();
|
||
const approverId = this.getApproverIdForSubmit();
|
||
const pickUpDatetime = this.normalizePickUpDatetime('T', null);
|
||
const bookingContactText = this.resolveContactText('booking');
|
||
const tenOutContactText = this.isTenOut === 1 ? this.resolveContactText('tenOut') : '';
|
||
const guestContactText = this.isContactGuestDirectly === 1 ? this.resolveContactText('guest') : '';
|
||
const tenOutContact1 = this.isTenOut === 1 ? this.getContactTextAt('tenOut', 0) : '';
|
||
const tenOutContact2 = this.isTenOut === 1 ? this.getContactTextAt('tenOut', 1) : '';
|
||
const guestContact1 = this.isContactGuestDirectly === 1 ? this.getContactTextAt('guest', 0) : '';
|
||
const guestContact2 = this.isContactGuestDirectly === 1 ? this.getContactTextAt('guest', 1) : '';
|
||
return {
|
||
playerId,
|
||
player: playerName,
|
||
confirmationNo: '',
|
||
bookingType: toStringOrEmpty(this.bookingType),
|
||
pickUpPoint: pickUpPointLabel,
|
||
pickupPointId: this.pickupPointId,
|
||
tripPriceId: this.selectedPickUpTripPriceId,
|
||
otherPickUpPoint: this.shouldShowOtherPickUpPoint
|
||
? String(this.otherPickUpPoint || '').trim()
|
||
: '',
|
||
pickUpDatetime,
|
||
dropOffPoint: dropOffPointLabel,
|
||
dropOffPointId: this.dropOffPointId,
|
||
dropOffPointIds: this.dropOffPointId,
|
||
extraOffPoint: this.resolveExtraDropOffValues('label'),
|
||
extraOffPointId: this.resolveExtraDropOffValues('id'),
|
||
otherDropOffPoint: this.resolveOtherDropOffPointsJoined(),
|
||
bookingStatus: '',
|
||
serviceType: toStringOrEmpty(this.serviceType),
|
||
carType: carTypeLabel,
|
||
noOfPax: pax,
|
||
greetType: greetTypeText,
|
||
signage: this.shouldShowGreetLocationFields ? toStringOrEmpty(this.signage) : '',
|
||
approver: approverText,
|
||
approverId: approverId,
|
||
paymentMethod: toStringOrEmpty(this.getSelectValue(this.paymentMethod)),
|
||
bookingContact: bookingContactText,
|
||
isHighImportance: this.isHighImportance,
|
||
isTenOut: this.isTenOut,
|
||
tenOutContact: tenOutContactText,
|
||
tenOutContact1,
|
||
tenOutContact2,
|
||
isContactGuestDirectly: this.isContactGuestDirectly,
|
||
guestContact: guestContactText,
|
||
tripPrice: this.shouldShowTripPriceField ? toStringOrEmpty(this.tripPrice) : '',
|
||
tripContact: toStringOrEmpty(this.tripContact),
|
||
isHourlyRate: this.isHourlyRateSelected ? 1 : 0,
|
||
remarks: toStringOrEmpty(this.remarks),
|
||
flightDetail: this.isAirportPickup ? toStringOrEmpty(this.flightDetail) : '',
|
||
flightEta: this.isAirportPickup ? toStringOrEmpty(this.eta) : '',
|
||
flightStatus: this.isAirportPickup ? toStringOrEmpty(this.flightStatus) : ''
|
||
};
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
const mode = options && options.mode ? String(options.mode) : '';
|
||
this.mode = mode === 'edit' ? 'edit' : 'create';
|
||
this.title = this.mode === 'edit'
|
||
? this.$t("newLimo.editTitle")
|
||
: this.$t("newLimo.createTitle");
|
||
this.playerData = this.getCachedPlayerData();
|
||
this.userData = uni.getStorageSync("userData");
|
||
this.deviceData = uni.getStorageSync("deviceData");
|
||
this.windowWidth = this.deviceData.windowWidth - 70;
|
||
this.applyPaymentMethodFromUserData();
|
||
const limoEditData = this.mode === 'edit' ? uni.getStorageSync('limoEditData') : null;
|
||
this.getLocation().then(() => {
|
||
if (limoEditData) {
|
||
this.populateEditForm(limoEditData);
|
||
}
|
||
});
|
||
},
|
||
onShow() {
|
||
this.userData = uni.getStorageSync('userData');
|
||
this.playerData = this.getCachedPlayerData();
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
if (!this.isEditMode) {
|
||
this.fetchLimoApprovers();
|
||
}
|
||
},
|
||
beforeDestroy() {
|
||
this.clearFlightDetailDebounceTimer();
|
||
},
|
||
methods: {
|
||
getCachedPlayerData() {
|
||
const cachedPlayerData =
|
||
uni.getStorageSync('playerData') || uni.getStorageSync('PlayerData') || '';
|
||
if (typeof cachedPlayerData !== 'string') {
|
||
return cachedPlayerData;
|
||
}
|
||
const trimmedPlayerData = cachedPlayerData.trim();
|
||
if (!trimmedPlayerData) {
|
||
return '';
|
||
}
|
||
try {
|
||
return JSON.parse(trimmedPlayerData);
|
||
} catch (e) {
|
||
return '';
|
||
}
|
||
},
|
||
goToBookingRecord() {
|
||
this.navTo('/booking/bookingRecord/bookingRecord?from=booking',0,false);
|
||
},
|
||
closeBookingCreatedPopup() {
|
||
this.showBookingCreatedNoConfirmPopup = false;
|
||
this.showBookingCreatedPopup = false;
|
||
this.bookingSubmitServerMessage = '';
|
||
this.goToBookingRecord();
|
||
},
|
||
closeCriteriaNotQualifiedPopup() {
|
||
this.showCriteriaNotQualifiedPopup = false;
|
||
},
|
||
closeEditBookingSuccessPopup() {
|
||
this.showEditBookingSuccessPopup = false;
|
||
this.goToBookingRecord();
|
||
},
|
||
getContactList(type) {
|
||
const config = CONTACT_CONFIG[type] || CONTACT_CONFIG.booking;
|
||
return this[config.listKey] || [];
|
||
},
|
||
getVisibleContactList(type) {
|
||
const config = CONTACT_CONFIG[type] || CONTACT_CONFIG.booking;
|
||
const contacts = this.getContactList(type);
|
||
const visibleCount = Math.min(
|
||
Math.max(Number(this[config.visibleCountKey]) || 1, 1),
|
||
config.maxCount
|
||
);
|
||
return contacts.slice(0, visibleCount);
|
||
},
|
||
onContactCountryCodeChange(type, index, e) {
|
||
if (this.isEditMode) return;
|
||
const contacts = this.getContactList(type);
|
||
if (!contacts[index]) return;
|
||
this.$set(contacts[index], 'countryCode', e || DEFAULT_COUNTRY_CODE);
|
||
},
|
||
onContactPhoneInput(type, index, e) {
|
||
if (this.isEditMode) return;
|
||
const value = e && e.detail ? e.detail.value : e;
|
||
const phoneNumber = this.normalizePhoneNumber(value);
|
||
const contacts = this.getContactList(type);
|
||
if (!contacts[index]) return phoneNumber;
|
||
this.$set(contacts[index], 'phoneNumber', phoneNumber);
|
||
return phoneNumber;
|
||
},
|
||
onAddContact(type) {
|
||
if (this.isEditMode) return;
|
||
const config = CONTACT_CONFIG[type] || CONTACT_CONFIG.booking;
|
||
const currentCount = Number(this[config.visibleCountKey]) || 1;
|
||
if (currentCount >= config.maxCount) return;
|
||
const contacts = this.getContactList(type);
|
||
if (!contacts[currentCount]) {
|
||
this.$set(contacts, currentCount, createEmptyContact());
|
||
}
|
||
this[config.visibleCountKey] = currentCount + 1;
|
||
},
|
||
onRemoveContact(type, index) {
|
||
if (this.isEditMode) return;
|
||
const config = CONTACT_CONFIG[type] || CONTACT_CONFIG.booking;
|
||
const contacts = this.getContactList(type);
|
||
const currentCount = Number(this[config.visibleCountKey]) || 1;
|
||
if (index < 1 || index >= currentCount) return;
|
||
contacts.splice(index, 1);
|
||
contacts.push(createEmptyContact());
|
||
this[config.visibleCountKey] = Math.max(1, currentCount - 1);
|
||
},
|
||
normalizePhoneNumber(value) {
|
||
return String(value == null ? '' : value).replace(/\D/g, '');
|
||
},
|
||
formatBookingContact(contact) {
|
||
if (!contact || !contact.phoneNumber) return '';
|
||
return `${contact.countryCode || DEFAULT_COUNTRY_CODE} ${contact.phoneNumber}`;
|
||
},
|
||
resolveContactText(type) {
|
||
return this.getVisibleContactList(type)
|
||
.map(contact => this.formatBookingContact(contact))
|
||
.filter(item => item)
|
||
.join(',');
|
||
},
|
||
getContactTextAt(type, index) {
|
||
const contacts = this.getVisibleContactList(type);
|
||
return this.formatBookingContact(contacts[index]);
|
||
},
|
||
hasEmptyVisibleContact(type) {
|
||
return this.getVisibleContactList(type).some(contact => !contact.phoneNumber);
|
||
},
|
||
onAdditionalOptionChange(flagKey, optionName, values) {
|
||
if (this.isEditMode) return;
|
||
const selectedValues = Array.isArray(values) ? values : [];
|
||
this[flagKey] = selectedValues.includes(optionName) ? 1 : 0;
|
||
},
|
||
onBookingTypeChange() {
|
||
if (this.isEditMode) return;
|
||
this.$nextTick(() => {
|
||
this.pruneCarAndGreetIfNotAllowedForBookingType();
|
||
});
|
||
},
|
||
onApprovedByChange() {
|
||
if (this.isEditMode) return;
|
||
this.$nextTick(() => {
|
||
this.applyTripContactFromSelectedApprover();
|
||
});
|
||
},
|
||
getSelectedApproverOption() {
|
||
const selectedValue = this.getSelectValue(this.approvedBy);
|
||
return (
|
||
(this.approverOptions || []).find(
|
||
(item) => String(item.value) === String(selectedValue)
|
||
) || null
|
||
);
|
||
},
|
||
getApproverDisplayText() {
|
||
const option = this.getSelectedApproverOption();
|
||
if (option) {
|
||
return formatEmployeeNameAndId(
|
||
option.rawEmployeeName || option.label || option.value,
|
||
option.rawEmployeeId
|
||
);
|
||
}
|
||
return formatEmployeeNameAndId(
|
||
this.approvedBy,
|
||
this.editApproverIdSnapshot
|
||
);
|
||
},
|
||
getApproverIdForSubmit() {
|
||
const option = this.getSelectedApproverOption();
|
||
if (option && option.rawEmployeeId) {
|
||
return String(option.rawEmployeeId).trim();
|
||
}
|
||
const approverText = String(this.approvedBy ?? '').trim();
|
||
const snapText = String(this.editApproverTextSnapshot ?? '').trim();
|
||
const snapId = String(this.editApproverIdSnapshot ?? '').trim();
|
||
if (approverText === snapText && snapId) {
|
||
return snapId;
|
||
}
|
||
return this.parseEmployeeIdFromOverriderDisplayText(approverText);
|
||
},
|
||
applyTripContactFromSelectedApprover() {
|
||
const option = this.getSelectedApproverOption();
|
||
const phone = this.getApproverPhone(option);
|
||
if (phone) {
|
||
this.tripContact = phone;
|
||
}
|
||
},
|
||
getApproverPhone(source) {
|
||
return getFirstText(source, [
|
||
'Phone',
|
||
'phone',
|
||
'PhoneNumber',
|
||
'phoneNumber'
|
||
]);
|
||
},
|
||
buildApproverOptionsFromRecords(approverRecords) {
|
||
const baseOptions = this.buildOverrideOptionsFromOverriderRecords(approverRecords);
|
||
if (!baseOptions.length) {
|
||
return [];
|
||
}
|
||
const approverByEmployeeId = {};
|
||
(approverRecords || []).forEach((record) => {
|
||
const employeeId = getFirstText(record, ['EmployeeId', 'employeeId']);
|
||
if (!employeeId) {
|
||
return;
|
||
}
|
||
approverByEmployeeId[employeeId] = {
|
||
employeeName: getFirstText(record, ['EmployeeName', 'employeeName']),
|
||
phone: this.getApproverPhone(record)
|
||
};
|
||
});
|
||
return baseOptions.map((option) => {
|
||
const approver = approverByEmployeeId[option.rawEmployeeId] || {};
|
||
const employeeName = approver.employeeName || option.rawEmployeeName;
|
||
const displayText = formatEmployeeNameAndId(employeeName, option.rawEmployeeId);
|
||
return {
|
||
...option,
|
||
value: displayText,
|
||
label: displayText,
|
||
rawEmployeeName: employeeName,
|
||
phone: approver.phone || ''
|
||
};
|
||
});
|
||
},
|
||
buildCurrentUserApproverOption(approverRecord = null) {
|
||
const userId = getFirstText(this.userData, ['userId']);
|
||
if (!userId) {
|
||
return null;
|
||
}
|
||
const userName =
|
||
getFirstText(approverRecord, ['EmployeeName', 'employeeName']) ||
|
||
getFirstText(this.userData, ['userName']) ||
|
||
userId;
|
||
const label = formatEmployeeNameAndId(userName, userId);
|
||
const phone =
|
||
this.getApproverPhone(approverRecord) ||
|
||
this.getApproverPhone(this.userData);
|
||
return {
|
||
value: label,
|
||
label,
|
||
rawEmployeeId: userId,
|
||
rawEmployeeName: userName,
|
||
phone
|
||
};
|
||
},
|
||
shouldUseCurrentUserAsApprover(approverRecords) {
|
||
if (!Array.isArray(approverRecords) || approverRecords.length === 0) {
|
||
return true;
|
||
}
|
||
if (approverRecords.length !== 1) {
|
||
return false;
|
||
}
|
||
const userId = getFirstText(this.userData, ['userId']);
|
||
const employeeId = getFirstText(approverRecords[0], [
|
||
'EmployeeId',
|
||
'employeeId',
|
||
'UserId',
|
||
'userId'
|
||
]);
|
||
return userId !== '' && employeeId !== '' && String(employeeId) === String(userId);
|
||
},
|
||
applyCurrentUserApproverFallback(approverRecord = null) {
|
||
const currentUserOption = this.buildCurrentUserApproverOption(approverRecord);
|
||
if (!currentUserOption) {
|
||
this.approverOptions = [];
|
||
return;
|
||
}
|
||
this.approverOptions = [currentUserOption];
|
||
this.approvedBy = currentUserOption.value;
|
||
this.applyTripContactFromSelectedApprover();
|
||
},
|
||
syncApprovedBySelectionAfterOptionsLoaded() {
|
||
const current = String(this.getSelectValue(this.approvedBy) ?? '').trim();
|
||
const snapId = String(this.editApproverIdSnapshot ?? '').trim();
|
||
const snapText = String(this.editApproverTextSnapshot ?? '').trim();
|
||
if (snapId) {
|
||
const byId = (this.approverOptions || []).find(
|
||
(option) => String(option.rawEmployeeId) === snapId
|
||
);
|
||
if (byId) {
|
||
this.approvedBy = byId.value;
|
||
this.editApproverTextSnapshot = byId.label;
|
||
return;
|
||
}
|
||
}
|
||
if (current) {
|
||
const byValue = (this.approverOptions || []).find(
|
||
(option) => String(option.value) === current
|
||
);
|
||
if (byValue) {
|
||
this.approvedBy = byValue.value;
|
||
return;
|
||
}
|
||
const byLabel = (this.approverOptions || []).find(
|
||
(option) =>
|
||
String(option.label).trim() === current ||
|
||
String(option.rawEmployeeName).trim() === current ||
|
||
String(option.rawEmployeeName).trim() === snapText
|
||
);
|
||
if (byLabel) {
|
||
this.approvedBy = byLabel.value;
|
||
return;
|
||
}
|
||
}
|
||
if (!current && !snapId) {
|
||
const userId = getFirstText(this.userData, ['userId']);
|
||
const currentUserOption = (this.approverOptions || []).find(
|
||
(option) => String(option.rawEmployeeId) === userId
|
||
);
|
||
if (currentUserOption) {
|
||
this.approvedBy = currentUserOption.value;
|
||
}
|
||
}
|
||
},
|
||
async fetchLimoApprovers() {
|
||
const userId = getFirstText(this.userData, ['userId']);
|
||
const playerId = getFirstText(this.playerData, ['playerId', 'PlayerId']);
|
||
if (!userId || !playerId) {
|
||
if (userId) {
|
||
this.applyCurrentUserApproverFallback();
|
||
} else {
|
||
this.approverOptions = [];
|
||
}
|
||
return;
|
||
}
|
||
try {
|
||
const httpResponse = await getLimoApprover({
|
||
UserId: userId,
|
||
PlayerId: playerId
|
||
});
|
||
if (httpResponse && httpResponse.success === false) {
|
||
this.applyCurrentUserApproverFallback();
|
||
return;
|
||
}
|
||
const records = getResponseRecords(httpResponse);
|
||
if (this.shouldUseCurrentUserAsApprover(records)) {
|
||
this.applyCurrentUserApproverFallback(records && records.length === 1 ? records[0] : null);
|
||
return;
|
||
}
|
||
const approverOptions = this.buildApproverOptionsFromRecords(records);
|
||
if (!approverOptions.length) {
|
||
this.applyCurrentUserApproverFallback();
|
||
return;
|
||
}
|
||
this.approverOptions = approverOptions;
|
||
this.syncApprovedBySelectionAfterOptionsLoaded();
|
||
this.applyTripContactFromSelectedApprover();
|
||
} catch (error) {
|
||
console.error('[NewLimo] getLimoApprover failed', error);
|
||
this.approverOptions = [];
|
||
}
|
||
},
|
||
extractBookingTypeKeysFromRow(row) {
|
||
if (!row || typeof row !== 'object') {
|
||
return [];
|
||
}
|
||
const keys = [];
|
||
const add = (v) => {
|
||
if (v === null || v === undefined || v === '') {
|
||
return;
|
||
}
|
||
if (Array.isArray(v)) {
|
||
v.forEach(add);
|
||
return;
|
||
}
|
||
const raw = typeof v === 'string' ? v.trim() : v;
|
||
if (raw === '') {
|
||
return;
|
||
}
|
||
const str = typeof raw === 'string' ? raw : String(raw);
|
||
if (str.includes(',')) {
|
||
str.split(',').forEach((p) => {
|
||
const q = String(p).trim();
|
||
if (q && !keys.includes(q)) {
|
||
keys.push(q);
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
if (!keys.includes(str)) {
|
||
keys.push(str);
|
||
}
|
||
};
|
||
BOOKING_TYPE_FIELDS.forEach((field) => add(row[field]));
|
||
return keys;
|
||
},
|
||
normalizeBookingTypeToken(value) {
|
||
return String(value != null ? value : '').trim().toLowerCase();
|
||
},
|
||
/**
|
||
* 表单 bookingType(0/1/2)映射为接口 bookingType 文案(Local/HK/China)
|
||
*/
|
||
resolveBookingTypeApiFilter(bookingTypeSelected) {
|
||
const bt = toTrimmedString(bookingTypeSelected);
|
||
if (!bt) {
|
||
return '';
|
||
}
|
||
return BOOKING_TYPE_API_BY_CODE[bt] || BOOKING_TYPE_ALIASES[bt.toLowerCase()] || bt;
|
||
},
|
||
resolveBookingTypeApiFilters(bookingTypeSelected) {
|
||
const selected =
|
||
bookingTypeSelected != null ? String(bookingTypeSelected).trim() : '';
|
||
if (!selected) {
|
||
return [];
|
||
}
|
||
const filters = [];
|
||
const add = (value) => {
|
||
const text = value != null ? String(value).trim() : '';
|
||
if (
|
||
text &&
|
||
!filters.some(
|
||
(item) =>
|
||
this.normalizeBookingTypeToken(item) ===
|
||
this.normalizeBookingTypeToken(text)
|
||
)
|
||
) {
|
||
filters.push(text);
|
||
}
|
||
};
|
||
const primary = this.resolveBookingTypeApiFilter(selected);
|
||
add(primary);
|
||
add(selected);
|
||
const selectedOption = (this.bookingTypeOptions || []).find(
|
||
(option) => String(option.value) === selected
|
||
);
|
||
if (selectedOption) {
|
||
add(selectedOption.label);
|
||
}
|
||
const primaryLower = this.normalizeBookingTypeToken(primary);
|
||
if (primaryLower === 'hong kong') {
|
||
add('HK');
|
||
}
|
||
return filters;
|
||
},
|
||
rowMatchesGreetTypeBookingType(row, bookingTypeSelected) {
|
||
const filters = this.resolveBookingTypeApiFilters(bookingTypeSelected);
|
||
if (!filters.length) {
|
||
return false;
|
||
}
|
||
const keys = this.extractBookingTypeKeysFromRow(row);
|
||
if (!keys.length) {
|
||
return false;
|
||
}
|
||
const normalizedFilters = filters.map((item) => this.normalizeBookingTypeToken(item));
|
||
return keys.some((key) =>
|
||
normalizedFilters.includes(this.normalizeBookingTypeToken(key))
|
||
);
|
||
},
|
||
/**
|
||
* 接口可能返回迎宾方式文案(如 "Limo Greet")或 id,统一为下拉框使用的 value(row.id)
|
||
*/
|
||
resolveGreetTypeSelectValueFromApi(raw) {
|
||
if (raw == null || String(raw).trim() === '' || String(raw).trim().toUpperCase() === 'N/A') {
|
||
return [];
|
||
}
|
||
const rawItems = String(raw)
|
||
.split(/[;;,]/)
|
||
.map((item) => String(item).trim())
|
||
.filter((item) => item !== '');
|
||
const list = Array.isArray(this.greetTypeItemsAll) ? this.greetTypeItemsAll : [];
|
||
return rawItems.map((s) => {
|
||
const byId = list.find((r) => String(r.id) === s);
|
||
if (byId) {
|
||
return String(byId.id);
|
||
}
|
||
const lower = s.toLowerCase();
|
||
const byLabel = list.find((r) => {
|
||
const gt = r.greetType != null ? String(r.greetType).trim() : '';
|
||
return gt && gt.toLowerCase() === lower;
|
||
});
|
||
if (byLabel) {
|
||
return String(byLabel.id);
|
||
}
|
||
return s;
|
||
});
|
||
},
|
||
/**
|
||
* 编辑回显时:若接口返回的迎宾方式在列表中存在,但与当前 bookingType 过滤不匹配,
|
||
* 则合并 bookingTypes,使该项出现在 greetTypeOptions 中,避免 prune 清空且下拉无选项。
|
||
*/
|
||
ensureGreetTypeItemMatchesBookingType(greetId, bookingTypeStr) {
|
||
const bt = String(bookingTypeStr ?? '').trim();
|
||
const gid = String(greetId ?? '').trim();
|
||
if (!bt || !gid) {
|
||
return;
|
||
}
|
||
const list = this.greetTypeItemsAll;
|
||
if (!Array.isArray(list)) {
|
||
return;
|
||
}
|
||
const ix = list.findIndex((r) => String(r.id) === gid);
|
||
if (ix < 0) {
|
||
return;
|
||
}
|
||
const row = list[ix];
|
||
if (this.rowMatchesGreetTypeBookingType(row, bt)) {
|
||
return;
|
||
}
|
||
const apiBookingType = this.resolveBookingTypeApiFilter(bt);
|
||
if (!apiBookingType) {
|
||
return;
|
||
}
|
||
const merged = { ...row, bookingType: apiBookingType };
|
||
list.splice(ix, 1, merged);
|
||
},
|
||
carTypeOptionMatchesBookingType(option, bookingTypeSelected) {
|
||
const filters = this.resolveBookingTypeApiFilters(bookingTypeSelected);
|
||
if (!filters.length || !option) {
|
||
return false;
|
||
}
|
||
const keys = Array.isArray(option.bookingKeys) ? option.bookingKeys : [];
|
||
if (!keys.length) {
|
||
return true;
|
||
}
|
||
const normalizedFilters = filters.map((item) => this.normalizeBookingTypeToken(item));
|
||
return keys.some((key) =>
|
||
normalizedFilters.includes(this.normalizeBookingTypeToken(key))
|
||
);
|
||
},
|
||
/**
|
||
* 编辑回显时:若车型在列表中存在,但与当前 bookingType 过滤不匹配,
|
||
* 则补充 bookingKeys,使该项出现在 carTypeOptions 中。
|
||
*/
|
||
ensureCarTypeOptionMatchesBookingType(carTypeValue, bookingTypeStr) {
|
||
const bt = String(bookingTypeStr ?? '').trim();
|
||
const carVal = String(carTypeValue ?? '').trim();
|
||
if (!bt || !carVal) {
|
||
return;
|
||
}
|
||
const list = this.carTypeOptionsAll;
|
||
if (!Array.isArray(list)) {
|
||
return;
|
||
}
|
||
const ix = list.findIndex((o) => String(o.value) === carVal);
|
||
if (ix < 0) {
|
||
return;
|
||
}
|
||
const option = list[ix];
|
||
if (this.carTypeOptionMatchesBookingType(option, bt)) {
|
||
return;
|
||
}
|
||
const apiBookingType = this.resolveBookingTypeApiFilter(bt);
|
||
if (!apiBookingType) {
|
||
return;
|
||
}
|
||
const keys = Array.isArray(option.bookingKeys) ? [...option.bookingKeys] : [];
|
||
const filterLower = apiBookingType.toLowerCase();
|
||
if (!keys.some((key) => String(key).trim().toLowerCase() === filterLower)) {
|
||
keys.push(apiBookingType);
|
||
}
|
||
list.splice(ix, 1, { ...option, bookingKeys: keys });
|
||
},
|
||
pruneCarAndGreetIfNotAllowedForBookingType() {
|
||
if (this.isEditMode) return;
|
||
const carVal = this.getSelectValue(this.carType);
|
||
if (
|
||
carVal !== '' &&
|
||
carVal != null &&
|
||
!(this.carTypeOptions || []).some((o) => String(o.value) === String(carVal))
|
||
) {
|
||
this.carType = '';
|
||
}
|
||
const allowedGreetValues = (this.greetTypeOptions || []).map((o) => String(o.value));
|
||
const nextGreetType = this.getMultiSelectValues(this.greetType).filter((value) =>
|
||
allowedGreetValues.includes(String(value))
|
||
);
|
||
if (nextGreetType.length !== this.getMultiSelectValues(this.greetType).length) {
|
||
this.greetType = nextGreetType;
|
||
}
|
||
},
|
||
onServiceTypeChange() {
|
||
if (this.isEditMode) return;
|
||
this.$nextTick(() => {
|
||
if (!this.isOnHireServiceType) {
|
||
this.hourlyRate = '';
|
||
}
|
||
});
|
||
},
|
||
onPickUpPointChange() {
|
||
this.$nextTick(() => {
|
||
this.syncPickupPointId();
|
||
if (!this.shouldShowOtherPickUpPoint) {
|
||
this.otherPickUpPoint = '';
|
||
}
|
||
this.syncGreetLocationFieldsVisibility();
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
});
|
||
},
|
||
syncGreetLocationFieldsVisibility() {
|
||
if (this.isEditMode) return;
|
||
if (this.shouldShowGreetLocationFields) {
|
||
return;
|
||
}
|
||
this.greetType = [];
|
||
this.signage = '';
|
||
},
|
||
syncPickupPointId() {
|
||
if (!this.pickUpPoint) {
|
||
this.pickupPointId = '';
|
||
return;
|
||
}
|
||
this.pickupPointId = this.getLocationIdFromPointSelection(
|
||
this.pickUpPoint,
|
||
this.pickUpPointOptions
|
||
);
|
||
},
|
||
clearFlightDetailDebounceTimer() {
|
||
if (this.flightDetailDebounceTimer != null) {
|
||
clearTimeout(this.flightDetailDebounceTimer);
|
||
this.flightDetailDebounceTimer = null;
|
||
}
|
||
},
|
||
onFlightDetailInput() {
|
||
if (this.isEditMode) return;
|
||
this.clearFlightDetailDebounceTimer();
|
||
this.flightDetailDebounceTimer = setTimeout(() => {
|
||
this.flightDetailDebounceTimer = null;
|
||
this.fetchLimoFlightDetails();
|
||
}, 2000);
|
||
},
|
||
buildFlightDetailsQueryParams() {
|
||
const flightNo = String(this.flightDetail ?? '').trim();
|
||
const pickupDateTime = this.buildPickUpDatetime();
|
||
const pickUpOpt = this.selectedPickUpLocationOption;
|
||
const pickupAirportCode =
|
||
pickUpOpt && pickUpOpt.airportCode != null && pickUpOpt.airportCode !== ''
|
||
? String(pickUpOpt.airportCode)
|
||
: '';
|
||
return {
|
||
flightNo,
|
||
pickupDateTime,
|
||
pickupAirportCode
|
||
};
|
||
},
|
||
applyFlightDetailsFromApiResponse(res) {
|
||
const list = getResponseRecords(res);
|
||
if (!list.length) {
|
||
return;
|
||
}
|
||
const row = list[0];
|
||
const eta = getFirstText(row, ['eta', 'Eta']);
|
||
const flightStatus = getFirstText(row, ['flightStatus', 'FlightStatus']);
|
||
if (eta) {
|
||
this.eta = eta;
|
||
}
|
||
if (flightStatus) {
|
||
this.flightStatus = flightStatus;
|
||
}
|
||
},
|
||
async fetchLimoFlightDetails() {
|
||
if (!this.isAirportPickup) {
|
||
return;
|
||
}
|
||
const params = this.buildFlightDetailsQueryParams();
|
||
if ([params.flightNo, params.pickupDateTime, params.pickupAirportCode].some(isBlank)) {
|
||
return;
|
||
}
|
||
try {
|
||
const res = await getLimoFlightDetails(params);
|
||
this.applyFlightDetailsFromApiResponse(res);
|
||
} catch (error) {
|
||
console.error('[NewLimo] getLimoFlightDetails failed', error);
|
||
}
|
||
},
|
||
onDropOffPointChange() {
|
||
this.$nextTick(() => {
|
||
this.syncDropOffPointId();
|
||
if (!this.shouldShowOtherDropOffPoint) {
|
||
this.otherDropOffPoint = '';
|
||
}
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
});
|
||
},
|
||
onExtraDropOffPointChange() {
|
||
if (this.isEditMode) return;
|
||
this.$nextTick(() => {
|
||
this.syncExtraDropOffPointData();
|
||
this.syncOtherExtraDropOffPointInputs();
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
});
|
||
},
|
||
syncDropOffPointId() {
|
||
this.dropOffPointId = this.getLocationIdFromPointSelection(
|
||
this.dropOffPoint,
|
||
this.dropOffPointOptions
|
||
);
|
||
},
|
||
syncExtraDropOffPointData() {
|
||
this.extraOffPoint = this.resolveExtraDropOffValues('label');
|
||
this.extraOffPointId = this.resolveExtraDropOffValues('id');
|
||
},
|
||
syncOtherExtraDropOffPointInputs() {
|
||
if (!Array.isArray(this.otherExtraDropOffPoints)) {
|
||
this.otherExtraDropOffPoints = [];
|
||
}
|
||
this.extraDropOffPoints.forEach((leg, index) => {
|
||
if (!this.shouldShowOtherExtraDropOffPoint(index)) {
|
||
this.$set(this.otherExtraDropOffPoints, index, '');
|
||
} else if (this.otherExtraDropOffPoints[index] == null) {
|
||
this.$set(this.otherExtraDropOffPoints, index, '');
|
||
}
|
||
});
|
||
if (this.otherExtraDropOffPoints.length > this.extraDropOffPoints.length) {
|
||
this.otherExtraDropOffPoints.splice(this.extraDropOffPoints.length);
|
||
}
|
||
},
|
||
applyPaymentMethodFromUserData() {
|
||
const parts = this.getUserPaymentMethodLabels();
|
||
this.paymentMethodOptionList = parts.map((label) => ({
|
||
value: label,
|
||
label
|
||
}));
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
if (
|
||
this.mode === 'create' &&
|
||
(!this.getSelectValue(this.paymentMethod) ||
|
||
String(this.getSelectValue(this.paymentMethod)).trim() === '') &&
|
||
parts.length
|
||
) {
|
||
this.paymentMethod = parts[0];
|
||
}
|
||
},
|
||
isFreeOfChargePaymentMethod(value) {
|
||
return String(value || '').trim().toLowerCase() === 'free of charge';
|
||
},
|
||
isFreeOfChargeEligibleTier() {
|
||
const tierName =
|
||
this.playerData && this.playerData.tierName != null
|
||
? String(this.playerData.tierName).trim()
|
||
: '';
|
||
const normalizedTierName = tierName.toLowerCase();
|
||
return ['supreme', 'supreme 88', 'alpha'].includes(normalizedTierName);
|
||
},
|
||
isFreeOfChargeLocationSelection(pointVal, options) {
|
||
const opt = this.getLocationOptionFromSelection(pointVal, options);
|
||
return (
|
||
opt &&
|
||
opt.freeOfCharge != null &&
|
||
!Number.isNaN(Number(opt.freeOfCharge)) &&
|
||
Number(opt.freeOfCharge) === 1
|
||
);
|
||
},
|
||
getSelectedFreeOfChargeLocationCount() {
|
||
let count = 0;
|
||
if (this.isFreeOfChargeLocationSelection(this.pickUpPoint, this.pickUpPointOptions)) {
|
||
count += 1;
|
||
}
|
||
if (this.isFreeOfChargeLocationSelection(this.dropOffPoint, this.dropOffPointOptions)) {
|
||
count += 1;
|
||
}
|
||
(this.extraDropOffPoints || []).forEach((pointVal) => {
|
||
if (this.isFreeOfChargeLocationSelection(pointVal, this.dropOffPointOptions)) {
|
||
count += 1;
|
||
}
|
||
});
|
||
return count;
|
||
},
|
||
syncFreeOfChargePaymentMethodOption() {
|
||
if (this.isEditMode) return;
|
||
const shouldShowFreeOfCharge =
|
||
this.isFreeOfChargeEligibleTier() &&
|
||
this.getSelectedFreeOfChargeLocationCount() >= 2;
|
||
const nextOptions = (this.paymentMethodOptionList || []).filter(
|
||
(option) =>
|
||
!this.isFreeOfChargePaymentMethod(option && option.value) &&
|
||
!this.isFreeOfChargePaymentMethod(option && option.label)
|
||
);
|
||
if (shouldShowFreeOfCharge) {
|
||
nextOptions.push(FREE_OF_CHARGE_OPTION);
|
||
}
|
||
this.paymentMethodOptionList = nextOptions;
|
||
if (
|
||
!shouldShowFreeOfCharge &&
|
||
this.isFreeOfChargePaymentMethod(this.getSelectValue(this.paymentMethod))
|
||
) {
|
||
this.paymentMethod = nextOptions.length ? nextOptions[0].value : '';
|
||
}
|
||
},
|
||
getUserPaymentMethodLabels() {
|
||
const raw = getFirstText(this.userData, ['paymentMethod', 'PaymentMethod']);
|
||
if (!raw) {
|
||
return [];
|
||
}
|
||
return String(raw)
|
||
.split(',')
|
||
.map((s) => s.trim())
|
||
.filter((s) => s !== '');
|
||
},
|
||
getSelectValue(value) {
|
||
if(Array.isArray(value)){
|
||
return value.length ? this.getSelectValue(value[0]) : '';
|
||
}
|
||
if(value && typeof value === 'object'){
|
||
return value.value != null ? value.value
|
||
: (value.id != null ? value.id
|
||
: (value.code != null ? value.code : ''));
|
||
}
|
||
return value != null ? value : '';
|
||
},
|
||
getSelectLabel(options, value) {
|
||
if(Array.isArray(value)){
|
||
const labels = this.getMultiSelectLabels(options, value);
|
||
return labels.join(';');
|
||
}
|
||
if(value && typeof value === 'object'){
|
||
return value.label != null ? value.label : String(this.getSelectValue(value) || '');
|
||
}
|
||
const selectedValue = this.getSelectValue(value);
|
||
const found = (options || []).find(
|
||
(item) => String(item.value) === String(selectedValue)
|
||
);
|
||
return found ? found.label : String(selectedValue || '');
|
||
},
|
||
getMultiSelectSource(value) {
|
||
if (Array.isArray(value)) {
|
||
return value;
|
||
}
|
||
if (isBlank(value)) {
|
||
return [];
|
||
}
|
||
if (typeof value === 'string' && /[;;,]/.test(value)) {
|
||
return value
|
||
.split(/[;;,]/)
|
||
.map((item) => String(item).trim())
|
||
.filter((item) => item !== '');
|
||
}
|
||
return [value];
|
||
},
|
||
getMultiSelectValues(value) {
|
||
const source = this.getMultiSelectSource(value);
|
||
return source
|
||
.map((item) => this.getSelectValue(item))
|
||
.map((item) => String(item != null ? item : '').trim())
|
||
.filter((item) => item !== '');
|
||
},
|
||
getMultiSelectLabels(options, value) {
|
||
const source = this.getMultiSelectSource(value);
|
||
return source
|
||
.map((item) => this.getSelectLabel(options || [], item))
|
||
.map((item) => String(item != null ? item : '').trim())
|
||
.filter((item) => item !== '');
|
||
},
|
||
parseNumber(value) {
|
||
if (isBlank(value)) {
|
||
return null;
|
||
}
|
||
const numberValue = Number(value);
|
||
return Number.isNaN(numberValue) ? null : numberValue;
|
||
},
|
||
buildPickUpDatetime() {
|
||
if(this.pickUpDate && this.pickUpTime){
|
||
return `${this.pickUpDate} ${this.pickUpTime}`;
|
||
}
|
||
return this.pickUpDate || this.pickUpTime || '';
|
||
},
|
||
buildLimoEditPayload() {
|
||
const extraDropOffPoints = (this.extraDropOffPoints || [])
|
||
.filter((item) => !isBlank(item))
|
||
.map((item, index) => ({
|
||
sequence: index + 1,
|
||
pointCode: this.getLocationIdFromPointSelection(item, this.dropOffPointOptions),
|
||
pointName: this.getSelectLabel(this.dropOffPointOptions, item)
|
||
}));
|
||
const primaryDropOff = {
|
||
pointCode: this.dropOffPointId,
|
||
pointName: this.getSelectLabel(this.dropOffPointOptions, this.dropOffPoint)
|
||
};
|
||
const dropOffPoints = [
|
||
...extraDropOffPoints,
|
||
...(primaryDropOff.pointName || primaryDropOff.pointCode
|
||
? [{
|
||
sequence: extraDropOffPoints.length + 1,
|
||
pointCode: primaryDropOff.pointCode,
|
||
pointName: primaryDropOff.pointName
|
||
}]
|
||
: [])
|
||
];
|
||
const isHourlyRate = this.isHourlyRateSelected ? 1 : 0;
|
||
const approverText = this.getApproverDisplayText();
|
||
const approverIdForSubmit = this.getApproverIdForSubmit();
|
||
const bookingContactText = this.resolveContactText('booking');
|
||
const tenOutContactText = this.isTenOut === 1 ? this.resolveContactText('tenOut') : '';
|
||
const guestContactText = this.isContactGuestDirectly === 1 ? this.resolveContactText('guest') : '';
|
||
const bookingContact1 = this.getContactTextAt('booking', 0);
|
||
const bookingContact2 = this.getContactTextAt('booking', 1);
|
||
const bookingContact3 = this.getContactTextAt('booking', 2);
|
||
const tenOutContact1 = this.isTenOut === 1 ? this.getContactTextAt('tenOut', 0) : '';
|
||
const tenOutContact2 = this.isTenOut === 1 ? this.getContactTextAt('tenOut', 1) : '';
|
||
const guestContact1 = this.isContactGuestDirectly === 1 ? this.getContactTextAt('guest', 0) : '';
|
||
const guestContact2 = this.isContactGuestDirectly === 1 ? this.getContactTextAt('guest', 1) : '';
|
||
return {
|
||
id: this.editId,
|
||
confirmationNo: this.confirmationNo,
|
||
bookingStatus: this.bookingStatus,
|
||
playerId: this.playerData && this.playerData.playerId,
|
||
playerName: this.playerData && this.playerData.name,
|
||
tierName: this.playerData && this.playerData.tierName,
|
||
bookingType: this.getSelectValue(this.bookingType),
|
||
bookingTypeName: this.getSelectLabel(this.bookingTypeOptions, this.bookingType),
|
||
serviceType: this.getSelectValue(this.serviceType),
|
||
serviceTypeName: this.getSelectLabel(this.serviceTypeOptions, this.serviceType),
|
||
pickUpDate: this.pickUpDate,
|
||
pickUpTime: this.pickUpTime,
|
||
pickUpDatetime: this.buildPickUpDatetime(),
|
||
carTypeCode: this.getSelectValue(this.carType),
|
||
carType: this.getSelectLabel(this.carTypeOptions, this.carType),
|
||
noOfPax: this.parseNumber(this.numberOfPax),
|
||
pickUpPointCode: this.pickupPointId,
|
||
pickupPointId: this.pickupPointId,
|
||
pickUpPoint: this.getSelectLabel(this.pickUpPointOptions, this.pickUpPoint),
|
||
tripPriceId: this.selectedPickUpTripPriceId,
|
||
otherPickUpPoint: this.shouldShowOtherPickUpPoint
|
||
? String(this.otherPickUpPoint || '').trim()
|
||
: '',
|
||
dropOffPointCode: primaryDropOff.pointCode,
|
||
dropOffPoint: primaryDropOff.pointName,
|
||
dropOffPointId: this.dropOffPointId,
|
||
dropOffPointIds: this.dropOffPointId,
|
||
extraOffPoint: this.extraOffPoint,
|
||
extraOffPointId: this.extraOffPointId,
|
||
otherDropOffPoint: this.resolveOtherDropOffPointsJoined(),
|
||
dropOffPoints,
|
||
flightDetail: this.isAirportPickup ? this.flightDetail : '',
|
||
flightEta: this.isAirportPickup ? this.eta : '',
|
||
flightStatus: this.isAirportPickup ? this.flightStatus : '',
|
||
greetType: this.shouldShowGreetLocationFields
|
||
? (
|
||
this.getSelectLabel(this.greetTypeOptions, this.greetType) ||
|
||
String(this.greetType ?? '').trim()
|
||
)
|
||
: '',
|
||
greetTypeId: this.greetTypeId,
|
||
signage: this.shouldShowGreetLocationFields ? this.signage : '',
|
||
approverId: approverIdForSubmit,
|
||
approver: approverText || approverIdForSubmit,
|
||
paymentMethodCode: this.getSelectValue(this.paymentMethod),
|
||
paymentMethod: this.getSelectLabel(this.paymentMethodOptionList, this.paymentMethod),
|
||
isHourlyRate,
|
||
tripPrice: isHourlyRate ? null : this.shouldShowTripPriceField ? this.parseNumber(this.tripPrice) : null,
|
||
bookerContact: this.tripContact,
|
||
bookingContact: bookingContactText,
|
||
bookingContact1,
|
||
bookingContact2,
|
||
bookingContact3,
|
||
isHighImportance: this.isHighImportance,
|
||
isTenOut: this.isTenOut,
|
||
tenOutContact: tenOutContactText,
|
||
tenOutContact1,
|
||
tenOutContact2,
|
||
isContactGuestDirectly: this.isContactGuestDirectly,
|
||
guestContact: guestContactText,
|
||
guestContact1,
|
||
guestContact2,
|
||
tripContact: this.tripContact,
|
||
remarks: this.remarks,
|
||
createdAt: this.createdAt,
|
||
createdBy: this.createdBy,
|
||
overrideBy: this.overrideBy,
|
||
updatedBy: getFirstText(this.userData, ['userId']),
|
||
updatedByName: getFirstText(this.userData, ['userName'])
|
||
};
|
||
},
|
||
normalizePickUpDatetime(separator = 'T', emptyValue = null) {
|
||
const d = (this.pickUpDate || '').trim();
|
||
const t = (this.pickUpTime || '').trim();
|
||
if (!d || !t) {
|
||
return emptyValue;
|
||
}
|
||
let timeNorm = t;
|
||
if (/^\d{2}:\d{2}$/.test(t)) {
|
||
timeNorm = `${t}:00`;
|
||
}
|
||
return `${d}${separator}${timeNorm}`;
|
||
},
|
||
getLocationIdFromPointSelection(pointVal, options) {
|
||
const text = toTrimmedString(this.getSelectValue(pointVal));
|
||
if (!text) {
|
||
return '';
|
||
}
|
||
const opt = this.getLocationOptionFromSelection(pointVal, options);
|
||
if (opt) {
|
||
return toTrimmedString(opt.locationId) || '0';
|
||
}
|
||
return /^\d+$/.test(text) ? text : '0';
|
||
},
|
||
resolveExtraDropOffValues(valueType) {
|
||
return (this.extraDropOffPoints || [])
|
||
.map((pointVal) => valueType === 'id'
|
||
? this.getLocationIdFromPointSelection(pointVal, this.dropOffPointOptions)
|
||
: this.getSelectLabel(this.dropOffPointOptions, pointVal))
|
||
.map(toTrimmedString)
|
||
.filter(Boolean)
|
||
.join(';');
|
||
},
|
||
getLocationOptionFromSelection(pointVal, options) {
|
||
if (isBlank(pointVal)) {
|
||
return null;
|
||
}
|
||
const optionList = options || [];
|
||
const raw = this.getSelectValue(pointVal);
|
||
const text = String(raw).trim();
|
||
if (!text) {
|
||
return null;
|
||
}
|
||
let opt = optionList.find((o) => String(o.value) === text);
|
||
if (!opt) {
|
||
opt = optionList.find((o) => String(o.label).trim() === text);
|
||
}
|
||
if (!opt && pointVal && typeof pointVal === 'object' && pointVal.label != null) {
|
||
opt = optionList.find(
|
||
(o) => String(o.label).trim() === String(pointVal.label).trim()
|
||
);
|
||
}
|
||
return opt || null;
|
||
},
|
||
isOtherLocationOption(option) {
|
||
if (!option) {
|
||
return false;
|
||
}
|
||
const label = String(option.label || '').trim().toLowerCase();
|
||
const englishName = String(option.englishName || '').trim().toLowerCase();
|
||
return (
|
||
label === 'others' ||
|
||
label === 'other' ||
|
||
label.endsWith('/others') ||
|
||
label.endsWith('/other') ||
|
||
englishName === 'others' ||
|
||
englishName === 'other'
|
||
);
|
||
},
|
||
isOtherLocationSelection(pointVal, options) {
|
||
return this.isOtherLocationOption(this.getLocationOptionFromSelection(pointVal, options));
|
||
},
|
||
shouldShowOtherExtraDropOffPoint(index) {
|
||
if (!Array.isArray(this.extraDropOffPoints)) {
|
||
return false;
|
||
}
|
||
return this.isOtherLocationSelection(this.extraDropOffPoints[index], this.dropOffPointOptions);
|
||
},
|
||
resolveOtherDropOffPointsJoined() {
|
||
const parts = [];
|
||
(this.extraDropOffPoints || []).forEach((pointVal, index) => {
|
||
if (!this.isOtherLocationSelection(pointVal, this.dropOffPointOptions)) {
|
||
return;
|
||
}
|
||
const value =
|
||
this.otherExtraDropOffPoints && this.otherExtraDropOffPoints[index] != null
|
||
? String(this.otherExtraDropOffPoints[index]).trim()
|
||
: '';
|
||
if (value) {
|
||
parts.push(value);
|
||
}
|
||
});
|
||
if (this.shouldShowOtherDropOffPoint) {
|
||
const value = String(this.otherDropOffPoint || '').trim();
|
||
if (value) {
|
||
parts.push(value);
|
||
}
|
||
}
|
||
return parts.join('; ');
|
||
},
|
||
parsePositiveInteger(raw) {
|
||
if (isBlank(raw)) {
|
||
return null;
|
||
}
|
||
const n = parseInt(String(raw).trim(), 10);
|
||
return Number.isFinite(n) ? n : null;
|
||
},
|
||
ensureOption(options, label, fallbackValue) {
|
||
if(!label){
|
||
return fallbackValue || '';
|
||
}
|
||
const normalizedLabel = String(label).trim();
|
||
const found = options.find(item => String(item.label).trim() === normalizedLabel);
|
||
if(found){
|
||
return found.value;
|
||
}
|
||
const value = fallbackValue || normalizedLabel;
|
||
const patch =
|
||
options === this.carTypeOptionsAll ? { bookingKeys: [] } : {};
|
||
options.push({
|
||
value,
|
||
label: normalizedLabel,
|
||
...patch
|
||
});
|
||
return value;
|
||
},
|
||
parsePickupDatetime(str){
|
||
const result = {
|
||
date: '',
|
||
time: ''
|
||
};
|
||
if(!str){
|
||
return result;
|
||
}
|
||
const parts = String(str).trim().split(/\s+/);
|
||
const datePart = parts[0] || '';
|
||
result.time = parts[1] || '';
|
||
const dateTokens = datePart.split('/');
|
||
if(dateTokens.length === 3){
|
||
result.date = `${dateTokens[2]}-${dateTokens[1]}-${dateTokens[0]}`;
|
||
}else{
|
||
result.date = datePart;
|
||
}
|
||
return result;
|
||
},
|
||
parseDelimitedValues(value, separator = ';') {
|
||
if (isBlank(value)) {
|
||
return [];
|
||
}
|
||
return String(value)
|
||
.split(separator)
|
||
.map(toTrimmedString)
|
||
.filter(Boolean);
|
||
},
|
||
normalizeFlagValue(value) {
|
||
return value === 1 || value === true || String(value).trim() === '1' ? 1 : 0;
|
||
},
|
||
getContactTextForEdit(data, joinedKeys, contactKeys) {
|
||
const joinedValue = getFirstText(data, joinedKeys);
|
||
if (joinedValue) {
|
||
return joinedValue;
|
||
}
|
||
return (contactKeys || [])
|
||
.map((key) => getFirstText(data, [key]))
|
||
.filter(Boolean)
|
||
.join(',');
|
||
},
|
||
parseContactTextItem(value) {
|
||
const text = String(value || '').trim();
|
||
if (!text) {
|
||
return createEmptyContact();
|
||
}
|
||
const match = text.match(/^(\+\d+)\s*(.*)$/);
|
||
if (match) {
|
||
return {
|
||
countryCode: match[1],
|
||
phoneNumber: this.normalizePhoneNumber(match[2])
|
||
};
|
||
}
|
||
return {
|
||
countryCode: DEFAULT_COUNTRY_CODE,
|
||
phoneNumber: this.normalizePhoneNumber(text)
|
||
};
|
||
},
|
||
applyContactText(type, value) {
|
||
const config = CONTACT_CONFIG[type] || CONTACT_CONFIG.booking;
|
||
const parsedContacts = this.parseDelimitedValues(value, /[;,]/)
|
||
.slice(0, config.maxCount)
|
||
.map((item) => this.parseContactTextItem(item));
|
||
const contacts = Array.from(
|
||
{ length: config.maxCount },
|
||
(_, index) => parsedContacts[index] || createEmptyContact()
|
||
);
|
||
this[config.listKey] = contacts;
|
||
this[config.visibleCountKey] = Math.max(1, parsedContacts.length);
|
||
},
|
||
resolvePointSelectionsFromJoinedFields(labelText, idText) {
|
||
const labels = this.parseDelimitedValues(labelText);
|
||
const ids = this.parseDelimitedValues(idText);
|
||
const length = Math.max(labels.length, ids.length);
|
||
const selections = [];
|
||
for (let index = 0; index < length; index += 1) {
|
||
const label = labels[index] || ids[index] || '';
|
||
const id = ids[index] || label;
|
||
const selection = this.ensureOption(this.dropOffPointOptions, label, id);
|
||
if (selection !== '') {
|
||
selections.push(selection);
|
||
}
|
||
}
|
||
return selections;
|
||
},
|
||
populateEditForm(data){
|
||
if(!data) return;
|
||
const pickupInfo = this.parsePickupDatetime(data.pickUpDate);
|
||
this.editId = data.id || null;
|
||
this.confirmationNo = data.confirmationNo || '';
|
||
this.bookingStatus = data.status || '';
|
||
this.createdAt = data.createdAt || '';
|
||
this.createdBy = data.createdBy || '';
|
||
this.overrideBy = data.overrideBy || '';
|
||
this.bookingType = data.bookingType || 'local';
|
||
this.serviceType = data.serviceType || '0';
|
||
this.pickUpDate = pickupInfo.date;
|
||
this.pickUpTime = pickupInfo.time;
|
||
this.carType = this.ensureOption(this.carTypeOptionsAll, data.carType, data.carType);
|
||
this.ensureCarTypeOptionMatchesBookingType(this.carType, this.bookingType);
|
||
this.numberOfPax = data.noOfPax != null ? String(data.noOfPax) : '2';
|
||
this.pickUpPoint = this.ensureOption(this.pickUpPointOptions, data.pickUpPoint, data.pickUpPoint);
|
||
this.otherPickUpPoint = data.otherPickUpPoint || '';
|
||
if (!isBlank(data.pickupPointId)) {
|
||
this.pickupPointId = toTrimmedString(data.pickupPointId);
|
||
} else {
|
||
this.syncPickupPointId();
|
||
}
|
||
const hasExtraDropOffForEdit = [
|
||
data.extraOffPoint,
|
||
data.extraOffPoints,
|
||
data.extraOffPointId,
|
||
data.extraOffPointIds
|
||
].some((value) => !isBlank(value));
|
||
const legacyDropOffLabels = hasExtraDropOffForEdit
|
||
? []
|
||
: this.parseDelimitedValues(data.dropOffPoint || '');
|
||
const legacyDropOffIds = hasExtraDropOffForEdit
|
||
? []
|
||
: this.parseDelimitedValues(data.dropOffPointIds || data.dropOffPointId || '');
|
||
const dropOffPointLabelForEdit =
|
||
legacyDropOffLabels.length > 1 ? legacyDropOffLabels[0] : data.dropOffPoint;
|
||
const dropOffPointIdForEdit =
|
||
getFirstText(data, ['dropOffPointId']) ||
|
||
(legacyDropOffIds.length > 1 ? legacyDropOffIds[0] : '') ||
|
||
getFirstText(data, ['dropOffPointIds']).split(';')[0] ||
|
||
getFirstText(data, ['dropOffPointCode']) ||
|
||
dropOffPointLabelForEdit;
|
||
this.dropOffPoint = this.ensureOption(
|
||
this.dropOffPointOptions,
|
||
dropOffPointLabelForEdit,
|
||
dropOffPointIdForEdit
|
||
);
|
||
this.syncDropOffPointId();
|
||
const extraOffPointTextForEdit = hasExtraDropOffForEdit
|
||
? data.extraOffPoint || data.extraOffPoints || ''
|
||
: legacyDropOffLabels.slice(1).join(';');
|
||
const extraOffPointIdTextForEdit = hasExtraDropOffForEdit
|
||
? data.extraOffPointId || data.extraOffPointIds || ''
|
||
: legacyDropOffIds.slice(1).join(';');
|
||
this.extraDropOffPoints = this.resolvePointSelectionsFromJoinedFields(
|
||
extraOffPointTextForEdit,
|
||
extraOffPointIdTextForEdit
|
||
);
|
||
const otherDropOffValues = this.parseDelimitedValues(data.otherDropOffPoint || '');
|
||
let otherDropOffValueIndex = 0;
|
||
this.otherDropOffPoint = this.shouldShowOtherDropOffPoint
|
||
? otherDropOffValues[otherDropOffValueIndex++] || ''
|
||
: '';
|
||
this.otherExtraDropOffPoints = this.extraDropOffPoints.map((pointVal) => {
|
||
if (!this.isOtherLocationSelection(pointVal, this.dropOffPointOptions)) {
|
||
return '';
|
||
}
|
||
return otherDropOffValues[otherDropOffValueIndex++] || '';
|
||
});
|
||
this.syncOtherExtraDropOffPointInputs();
|
||
this.syncExtraDropOffPointData();
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
const resolvedGreet = this.resolveGreetTypeSelectValueFromApi(data.greetTypeId || data.greetType);
|
||
resolvedGreet.forEach((greetId) => {
|
||
this.ensureGreetTypeItemMatchesBookingType(greetId, this.bookingType);
|
||
});
|
||
this.greetType = resolvedGreet;
|
||
this.signage = data.signage || '';
|
||
this.syncGreetLocationFieldsVisibility();
|
||
const approver = toTrimmedString(data.approver);
|
||
const approverId = toTrimmedString(data.approverId);
|
||
this.editApproverIdSnapshot = approverId;
|
||
this.approvedBy = approver;
|
||
this.editApproverTextSnapshot = String(this.approvedBy).trim();
|
||
this.paymentMethod = data.paymentMethod
|
||
? this.ensureOption(
|
||
this.paymentMethodOptionList,
|
||
data.paymentMethod,
|
||
data.paymentMethod
|
||
)
|
||
: this.paymentMethodOptionList.length
|
||
? this.paymentMethodOptionList[0].value
|
||
: '';
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
if(data.isHourlyRate === 1){
|
||
this.priceTypeArr = ['hour_rate'];
|
||
this.hourlyRate = toTrimmedString(data.hourlyRate);
|
||
this.tripPrice = '';
|
||
}else{
|
||
this.priceTypeArr = [];
|
||
this.hourlyRate = getFirstText(data, ['hourlyRate', 'hour']);
|
||
this.tripPrice = data.tripPrice || '';
|
||
}
|
||
this.tripContact = data.tripContact || '';
|
||
this.applyContactText(
|
||
'booking',
|
||
this.getContactTextForEdit(
|
||
data,
|
||
['bookingContact'],
|
||
['bookingContact1', 'bookingContact2', 'bookingContact3']
|
||
)
|
||
);
|
||
this.isHighImportance = this.normalizeFlagValue(getFirstText(data, ['isHighImportance', 'highImportance']));
|
||
this.isTenOut = this.normalizeFlagValue(getFirstText(data, ['isTenOut', 'tenOut']));
|
||
this.applyContactText(
|
||
'tenOut',
|
||
this.getContactTextForEdit(
|
||
data,
|
||
['tenOutContact', 'tenOutContacts'],
|
||
['tenOutContact1', 'tenOutContact2']
|
||
)
|
||
);
|
||
this.isContactGuestDirectly = this.normalizeFlagValue(data.isConnectGuestDirectly);
|
||
this.applyContactText(
|
||
'guest',
|
||
this.getContactTextForEdit(
|
||
data,
|
||
['guestContact', 'guestContacts'],
|
||
['guestContact1', 'guestContact2']
|
||
)
|
||
);
|
||
this.remarks = data.remarks || '';
|
||
this.flightDetail = data.flightDetail || '';
|
||
this.eta = data.flightEta || '';
|
||
this.flightStatus = data.flightStatus || '';
|
||
this.$nextTick(() => {
|
||
this.pruneCarAndGreetIfNotAllowedForBookingType();
|
||
});
|
||
},
|
||
onAddDropOffStop() {
|
||
this.extraDropOffPoints.push('');
|
||
this.otherExtraDropOffPoints.push('');
|
||
this.syncExtraDropOffPointData();
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
},
|
||
onRemoveDropOffStop(index) {
|
||
if (index < 0 || index >= this.extraDropOffPoints.length) return;
|
||
this.extraDropOffPoints.splice(index, 1);
|
||
this.otherExtraDropOffPoints.splice(index, 1);
|
||
this.syncExtraDropOffPointData();
|
||
this.syncOtherExtraDropOffPointInputs();
|
||
this.syncFreeOfChargePaymentMethodOption();
|
||
},
|
||
onDateConfirm(e) {
|
||
const raw = (e[0] && (e[0].value != null ? e[0].value : e[0])) || e[0];
|
||
this.pickUpDate = this.formatDateDisplay(raw);
|
||
this.showDatePicker = false;
|
||
},
|
||
onTimeConfirm(e) {
|
||
this.pickUpTime = e.value;
|
||
this.showTimePicker = false;
|
||
},
|
||
formatDateDisplay(v) {
|
||
if (isBlank(v)) return '';
|
||
if (typeof v === 'string') return v.length >= 10 ? v.substring(0, 10) : v;
|
||
const d = v instanceof Date ? v : new Date(v);
|
||
if (Number.isNaN(d.getTime())) return String(v);
|
||
const y = d.getFullYear();
|
||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||
const day = String(d.getDate()).padStart(2, '0');
|
||
return y + '-' + m + '-' + day;
|
||
},
|
||
async submitEditLimo() {
|
||
if(this.submitLoading){
|
||
return;
|
||
}
|
||
this.submitLoading = true;
|
||
this.syncPickupPointId();
|
||
this.syncDropOffPointId();
|
||
this.syncExtraDropOffPointData();
|
||
this.syncOtherExtraDropOffPointInputs();
|
||
const params = this.buildLimoEditPayload();
|
||
uni.showLoading({
|
||
title: this.$t("common.loading")
|
||
});
|
||
try{
|
||
const res = await editLimoBooking(params);
|
||
if(res && res.success === true){
|
||
uni.removeStorageSync('limoEditData');
|
||
this.showEditBookingSuccessPopup = true;
|
||
}else{
|
||
const message = res && (res.errorCode || res.message) ? (res.errorCode || res.message) : '';
|
||
this.msgStr = this.$t("common.saveFail") + (message ? '. ' + this.$t("common.errorMsg") + ': ' + message : '');
|
||
this.showMsg = true;
|
||
}
|
||
}catch(e){
|
||
console.error(e);
|
||
this.msgStr = e && e.errMsg ? e.errMsg : this.$t("common.saveFail");
|
||
this.showMsg = true;
|
||
}finally{
|
||
uni.hideLoading();
|
||
this.submitLoading = false;
|
||
}
|
||
},
|
||
validateLimoBookingForSubmit() {
|
||
const data = this.limoBookingData;
|
||
if (isBlank(data.playerId)) {
|
||
return 'Player information is missing.';
|
||
}
|
||
if (!data.bookingType) {
|
||
return 'Please select booking type.';
|
||
}
|
||
if (!data.serviceType) {
|
||
return 'Please select service type.';
|
||
}
|
||
if (!data.pickUpDatetime) {
|
||
return 'Please select pick-up date and time.';
|
||
}
|
||
if (isBlank(data.pickUpPoint)) {
|
||
return 'Please select pick-up location.';
|
||
}
|
||
if (isBlank(data.dropOffPoint)) {
|
||
return 'Please select drop-off location.';
|
||
}
|
||
if (
|
||
this.shouldShowOtherPickUpPoint &&
|
||
isBlank(data.otherPickUpPoint)
|
||
) {
|
||
return 'Please enter other pick-up location.';
|
||
}
|
||
if (
|
||
this.shouldShowOtherDropOffPoint &&
|
||
isBlank(this.otherDropOffPoint)
|
||
) {
|
||
return 'Please enter other drop-off location.';
|
||
}
|
||
if (
|
||
this.extraDropOffPoints.some((pointVal, index) =>
|
||
this.isOtherLocationSelection(pointVal, this.dropOffPointOptions) &&
|
||
isBlank(this.otherExtraDropOffPoints && this.otherExtraDropOffPoints[index])
|
||
)
|
||
) {
|
||
return 'Please enter other extra drop-off location.';
|
||
}
|
||
// if (this.hasEmptyVisibleContact('booking')) {
|
||
// return 'Please enter booking contact.';
|
||
// }
|
||
// if (this.isTenOut === 1 && this.hasEmptyVisibleContact('tenOut')) {
|
||
// return 'Please enter 10 out contact.';
|
||
// }
|
||
// if (this.isContactGuestDirectly === 1 && this.hasEmptyVisibleContact('guest')) {
|
||
// return 'Please enter guest contact.';
|
||
// }
|
||
if (isBlank(data.carType)) {
|
||
return 'Please select car type.';
|
||
}
|
||
if (data.noOfPax == null || data.noOfPax < 1) {
|
||
return 'Please enter a valid number of passengers.';
|
||
}
|
||
if (isBlank(data.approverId)) {
|
||
return 'Please select approver.';
|
||
}
|
||
if (!data.paymentMethod) {
|
||
return 'Please select payment method.';
|
||
}
|
||
if (this.shouldShowTripPriceField && isBlank(data.tripPrice)) {
|
||
return 'Please enter trip price.';
|
||
}
|
||
return '';
|
||
},
|
||
getBookingTypeForQualificationApi() {
|
||
const code = toTrimmedString(this.bookingType);
|
||
if (QUALIFICATION_BOOKING_TYPE_BY_CODE[code]) {
|
||
return QUALIFICATION_BOOKING_TYPE_BY_CODE[code];
|
||
}
|
||
return this.getSelectLabel(this.bookingTypeOptions, this.bookingType);
|
||
},
|
||
buildQualificationQueryParams() {
|
||
return {
|
||
PlayerId: getFirstText(this.playerData, ['playerId', 'PlayerId']),
|
||
BookingType: this.getBookingTypeForQualificationApi(),
|
||
BookingDate: this.normalizePickUpDatetime(' ', '')
|
||
};
|
||
},
|
||
unwrapQualificationBody(res) {
|
||
if (!res || typeof res !== 'object') {
|
||
return null;
|
||
}
|
||
if (res.success === false) {
|
||
return null;
|
||
}
|
||
const inner = res.data;
|
||
if (inner != null && typeof inner === 'object' && !Array.isArray(inner)) {
|
||
if (
|
||
inner.QualificationFlag !== undefined ||
|
||
inner.qualificationFlag !== undefined ||
|
||
inner.StatusCode !== undefined ||
|
||
inner.statusCode !== undefined
|
||
) {
|
||
return inner;
|
||
}
|
||
}
|
||
if (
|
||
res.QualificationFlag !== undefined ||
|
||
res.qualificationFlag !== undefined ||
|
||
res.StatusCode !== undefined ||
|
||
res.statusCode !== undefined
|
||
) {
|
||
return res;
|
||
}
|
||
return inner != null && typeof inner === 'object' ? inner : null;
|
||
},
|
||
parseQualificationFlag(body) {
|
||
if (!body || typeof body !== 'object') {
|
||
return null;
|
||
}
|
||
const v =
|
||
body.QualificationFlag !== undefined
|
||
? body.QualificationFlag
|
||
: body.qualificationFlag;
|
||
if (v === undefined || v === null) {
|
||
return null;
|
||
}
|
||
const n = Number(v);
|
||
return Number.isFinite(n) ? n : null;
|
||
},
|
||
pickQualificationErrorMessage(res, body) {
|
||
return (
|
||
getFirstText(res, ['message']) ||
|
||
getFirstText(body, ['StatusDescription', 'statusDescription']) ||
|
||
'Qualification check failed.'
|
||
);
|
||
},
|
||
buildCreateLimoBookingPayload(overrideExtra = {}) {
|
||
const raw = this.limoBookingData;
|
||
const base = {
|
||
playerId: raw.playerId,
|
||
player: raw.player,
|
||
confirmationNo: raw.confirmationNo || '',
|
||
bookingType: raw.bookingType,
|
||
pickUpPoint: raw.pickUpPoint,
|
||
pickUpPointId: raw.pickupPointId,
|
||
tripPriceId: raw.tripPriceId,
|
||
otherPickUpPoint: raw.otherPickUpPoint,
|
||
pickUpDatetime: raw.pickUpDatetime,
|
||
dropOffPoint: raw.dropOffPoint,
|
||
dropOffPointId: raw.dropOffPointId,
|
||
dropOffPointIds: raw.dropOffPointIds,
|
||
extraOffPoint: raw.extraOffPoint,
|
||
extraOffPointId: raw.extraOffPointId,
|
||
otherDropOffPoint: raw.otherDropOffPoint,
|
||
bookingStatus: raw.bookingStatus || '',
|
||
serviceType: raw.serviceType,
|
||
tripType: raw.serviceType,
|
||
carType: raw.carType,
|
||
carTypeId: this.carTypeId,
|
||
noOfPax: raw.noOfPax,
|
||
greetType: raw.greetType,
|
||
greetTypeId: this.greetTypeId,
|
||
signage: raw.signage,
|
||
approver: raw.approver,
|
||
approverId: raw.approverId,
|
||
paymentMethod: raw.paymentMethod,
|
||
tripPrice: raw.tripPrice,
|
||
tripContact: raw.tripContact,
|
||
bookingContact: raw.bookingContact,
|
||
isHighImportance: raw.isHighImportance,
|
||
isTenOut: raw.isTenOut,
|
||
tenOutContact: raw.tenOutContact,
|
||
isContactGuestDirectly: raw.isContactGuestDirectly,
|
||
guestContact: raw.guestContact,
|
||
isHourlyRate: raw.isHourlyRate,
|
||
remarks: raw.remarks,
|
||
flightDetail: raw.flightDetail,
|
||
flightEta: raw.flightEta,
|
||
flightStatus: raw.flightStatus,
|
||
createdBy: formatEmployeeNameAndId(
|
||
getFirstText(this.userData, ['userName']),
|
||
getFirstText(this.userData, ['userId'])
|
||
),
|
||
guestType: '0',
|
||
pickupRegion: this.pickUpPointRegionRaw,
|
||
dropoffRegion: this.dropoffRegionRaw,
|
||
ranking: this.ranking,
|
||
salution: this.salutation,
|
||
lastName: this.lastName,
|
||
bookerContact: this.tripContact,
|
||
employeeId: this.employeeId,
|
||
hour: this.hour
|
||
};
|
||
if (!isBlank(overrideExtra.overriderId)) {
|
||
base.overriderId = toTrimmedString(overrideExtra.overriderId);
|
||
}
|
||
if (!isBlank(overrideExtra.overriderName)) {
|
||
base.overriderName = toTrimmedString(overrideExtra.overriderName);
|
||
}
|
||
return base;
|
||
},
|
||
isCreateBookingResponseSuccess(res) {
|
||
if (!res || typeof res !== 'object' || res.success === false) return false;
|
||
if (res.success === true) return true;
|
||
const code = res.code;
|
||
return code === 200 || code === '200' || code === 0 || code === '0';
|
||
},
|
||
isPrepareBookingResponseSuccess(res) {
|
||
if (!res || typeof res !== 'object') return false;
|
||
return res.code === 200 || res.code === '200';
|
||
},
|
||
pickSuccessMessageFromResponse(res) {
|
||
let msg = getFirstText(res, ['message']);
|
||
const data = res.data;
|
||
if (!msg && data != null) {
|
||
if (typeof data === 'string' && !isBlank(data)) {
|
||
msg = toTrimmedString(data);
|
||
} else if (typeof data === 'object' && data.confirmationNo != null) {
|
||
msg = `Confirmation No.: ${data.confirmationNo}`;
|
||
}
|
||
}
|
||
return msg;
|
||
},
|
||
pickErrorMessageFromResponse(res) {
|
||
if (!res || typeof res !== 'object') {
|
||
return 'Request failed.';
|
||
}
|
||
return getFirstText(res, ['message', 'error']) || 'Request failed.';
|
||
},
|
||
pickErrorMessageFromHttpError(e) {
|
||
if (!e) {
|
||
return 'Network error.';
|
||
}
|
||
const body = e.data;
|
||
if (body != null && typeof body === 'object') {
|
||
const message = getFirstText(body, ['message', 'error']);
|
||
if (message) {
|
||
return message;
|
||
}
|
||
}
|
||
if (typeof body === 'string' && body.trim() !== '') {
|
||
return body.trim();
|
||
}
|
||
if (e.errMsg && String(e.errMsg).indexOf('request:fail') !== -1) {
|
||
return 'Network error. Please try again.';
|
||
}
|
||
if (e.statusCode) {
|
||
return `Request failed (${e.statusCode}).`;
|
||
}
|
||
return 'Request failed.';
|
||
},
|
||
async executeCreateLimoBooking(overrideExtra = {}) {
|
||
if (this.submitting) {
|
||
return;
|
||
}
|
||
this.submitting = true;
|
||
uni.showLoading({ title: '', mask: true });
|
||
try {
|
||
this.syncPickupPointId();
|
||
this.syncDropOffPointId();
|
||
this.syncExtraDropOffPointData();
|
||
this.syncOtherExtraDropOffPointInputs();
|
||
const payload = this.buildCreateLimoBookingPayload(overrideExtra);
|
||
const prepareRes = await prepareBooking({
|
||
playerId: payload.playerId,
|
||
approverId: payload.approverId
|
||
});
|
||
if (!this.isPrepareBookingResponseSuccess(prepareRes)) {
|
||
this.msgStr = this.pickErrorMessageFromResponse(prepareRes);
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
payload.draftId = prepareRes.data.draftId;
|
||
const res = await createLimoBooking(payload);
|
||
if (this.isCreateBookingResponseSuccess(res)) {
|
||
if(res.autoConfirm ===1){
|
||
this.bookingSubmitServerMessage = this.pickSuccessMessageFromResponse(res);
|
||
this.showBookingCreatedNoConfirmPopup = true;
|
||
}else{
|
||
this.bookingSubmitServerMessage = this.pickSuccessMessageFromResponse(res);
|
||
this.showBookingCreatedPopup = true;
|
||
}
|
||
} else {
|
||
this.msgStr = this.pickErrorMessageFromResponse(res);
|
||
this.showMsg = true;
|
||
}
|
||
} catch (e) {
|
||
console.log(e);
|
||
this.msgStr = this.pickErrorMessageFromHttpError(e);
|
||
this.showMsg = true;
|
||
} finally {
|
||
this.submitting = false;
|
||
uni.hideLoading();
|
||
}
|
||
},
|
||
async saveLimo(){
|
||
if(this.mode === 'edit'){
|
||
await this.submitEditLimo();
|
||
return;
|
||
}
|
||
if (this.submitting) {
|
||
return;
|
||
}
|
||
const validationError = this.validateLimoBookingForSubmit();
|
||
if (validationError) {
|
||
this.msgStr = validationError;
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
const qParams = this.buildQualificationQueryParams();
|
||
if (!qParams.PlayerId || !qParams.BookingType || !qParams.BookingDate) {
|
||
this.msgStr = 'Missing data for qualification check.';
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
this.submitting = true;
|
||
uni.showLoading({ title: '', mask: true });
|
||
try {
|
||
const res = await getQualification(qParams);
|
||
const body = this.unwrapQualificationBody(res);
|
||
if (res && res.success === false) {
|
||
this.msgStr = this.pickQualificationErrorMessage(res, body);
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
if (!body) {
|
||
this.msgStr = 'Qualification check returned no data.';
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
if (!isResponseStatusOk(body)) {
|
||
this.msgStr =
|
||
getFirstText(body, ['StatusDescription', 'statusDescription']) ||
|
||
'Qualification check failed.';
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
const flag = this.parseQualificationFlag(body);
|
||
if (flag === null) {
|
||
this.msgStr = 'Invalid qualification response.';
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
if (flag === 1) {
|
||
uni.hideLoading();
|
||
this.submitting = false;
|
||
await this.executeCreateLimoBooking();
|
||
return;
|
||
}
|
||
if (flag === 0) {
|
||
uni.hideLoading();
|
||
await this.fetchOverridersAndOpenCriteriaPopup();
|
||
return;
|
||
}
|
||
this.msgStr = 'Unexpected qualification result.';
|
||
this.showMsg = true;
|
||
} catch (e) {
|
||
console.log(e);
|
||
this.msgStr = this.pickErrorMessageFromHttpError(e);
|
||
this.showMsg = true;
|
||
} finally {
|
||
this.submitting = false;
|
||
uni.hideLoading();
|
||
}
|
||
},
|
||
unwrapOverriderResponse(httpResponse) {
|
||
if (!httpResponse || typeof httpResponse !== 'object') {
|
||
return null;
|
||
}
|
||
const responseData = httpResponse.data;
|
||
if (
|
||
responseData != null &&
|
||
typeof responseData === 'object' &&
|
||
(Array.isArray(responseData.Records) || Array.isArray(responseData.records))
|
||
) {
|
||
return responseData;
|
||
}
|
||
if (Array.isArray(httpResponse.Records) || Array.isArray(httpResponse.records)) {
|
||
return httpResponse;
|
||
}
|
||
return responseData != null && typeof responseData === 'object' ? responseData : httpResponse;
|
||
},
|
||
buildOverrideOptionsFromOverriderRecords(overriderRecords) {
|
||
if (!Array.isArray(overriderRecords)) {
|
||
return [];
|
||
}
|
||
return overriderRecords
|
||
.map((employeeRecord) => {
|
||
const rawEmployeeId = getFirstText(employeeRecord, ['EmployeeId', 'employeeId']);
|
||
const displayName = getFirstText(employeeRecord, ['EmployeeName', 'employeeName']);
|
||
const employeeIdAndNameText =
|
||
getFirstText(employeeRecord, ['EmployeeIdAndName', 'employeeIdAndName']) ||
|
||
(displayName && rawEmployeeId ? `${displayName}(${rawEmployeeId})` : '');
|
||
const employeeNameOnly =
|
||
displayName || this.parseEmployeeNameFromIdAndNameText(employeeIdAndNameText);
|
||
if ([rawEmployeeId, employeeIdAndNameText, employeeNameOnly].some(isBlank)) {
|
||
return null;
|
||
}
|
||
return {
|
||
value: employeeIdAndNameText,
|
||
label: employeeIdAndNameText,
|
||
rawEmployeeId,
|
||
rawEmployeeName: employeeNameOnly
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
},
|
||
parseEmployeeNameFromIdAndNameText(combinedDisplayText) {
|
||
const trimmed = String(combinedDisplayText || '').trim();
|
||
const match = trimmed.match(/^(.+?)\s*\((\d+)\)\s*$/);
|
||
return match ? String(match[1]).trim() : '';
|
||
},
|
||
parseEmployeeIdFromOverriderDisplayText(displayText) {
|
||
const trimmed = String(displayText || '').trim();
|
||
const match = trimmed.match(/\((\d+)\)\s*$/);
|
||
return match ? match[1] : trimmed;
|
||
},
|
||
onOverrideInputChange(value) {
|
||
this.overrideInputText = toTrimmedString(value);
|
||
},
|
||
onOverrideChange(value) {
|
||
const selectedValue = this.getSelectValue(value);
|
||
this.overrideName = selectedValue;
|
||
this.overrideInputText = toTrimmedString(selectedValue);
|
||
},
|
||
getOverrideInputValue() {
|
||
const currentValue = toTrimmedString(this.overrideName);
|
||
const typedValue = toTrimmedString(this.overrideInputText);
|
||
return typedValue || currentValue;
|
||
},
|
||
pickOverriderStatusMessage(overriderResponseBody, fallbackMessage) {
|
||
return getFirstText(
|
||
overriderResponseBody,
|
||
['StatusDescription', 'statusDescription']
|
||
) || fallbackMessage;
|
||
},
|
||
findOverriderOptionByEmployeeId(overriderOptions, employeeId) {
|
||
const id = toTrimmedString(employeeId);
|
||
if (!id) {
|
||
return null;
|
||
}
|
||
return (
|
||
(overriderOptions || []).find(
|
||
(option) => String(option.rawEmployeeId || '').trim() === id
|
||
) || null
|
||
);
|
||
},
|
||
async validateOverrider(overriderId) {
|
||
const id = toTrimmedString(overriderId);
|
||
if (!id) {
|
||
return null;
|
||
}
|
||
uni.showLoading({ title: '', mask: true });
|
||
try {
|
||
const overriderHttpResponse = await getLimoOverrider({
|
||
OverriderInfo: id
|
||
});
|
||
if (overriderHttpResponse && overriderHttpResponse.success === false) {
|
||
this.msgStr = this.pickErrorMessageFromResponse(overriderHttpResponse);
|
||
this.showMsg = true;
|
||
return null;
|
||
}
|
||
const overriderResponseBody = this.unwrapOverriderResponse(overriderHttpResponse);
|
||
if (!overriderResponseBody || typeof overriderResponseBody !== 'object') {
|
||
this.msgStr = 'Invalid overrider response.';
|
||
this.showMsg = true;
|
||
return null;
|
||
}
|
||
if (!isResponseStatusOk(overriderResponseBody)) {
|
||
this.msgStr = this.pickOverriderStatusMessage(
|
||
overriderResponseBody,
|
||
'Cannot Override.'
|
||
);
|
||
this.showMsg = true;
|
||
return null;
|
||
}
|
||
const overriderOptions = this.buildOverrideOptionsFromOverriderRecords(
|
||
getResponseRecords(overriderResponseBody)
|
||
);
|
||
const matchedOption = this.findOverriderOptionByEmployeeId(overriderOptions, id);
|
||
if (!matchedOption) {
|
||
this.msgStr = 'Cannot Override.';
|
||
this.showMsg = true;
|
||
return null;
|
||
}
|
||
return matchedOption;
|
||
} catch (httpError) {
|
||
console.log(httpError);
|
||
this.msgStr = this.pickErrorMessageFromHttpError(httpError);
|
||
this.showMsg = true;
|
||
return null;
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
},
|
||
async fetchOverridersAndOpenCriteriaPopup() {
|
||
const overriderInfoParam = getFirstText(this.userData, ['userId']);
|
||
if (!overriderInfoParam) {
|
||
this.msgStr = 'User ID is missing for overrider list.';
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
uni.showLoading({ title: '', mask: true });
|
||
try {
|
||
const overriderHttpResponse = await getLimoOverrider({
|
||
OverriderInfo: overriderInfoParam
|
||
});
|
||
if (overriderHttpResponse && overriderHttpResponse.success === false) {
|
||
this.msgStr = this.pickErrorMessageFromResponse(overriderHttpResponse);
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
const overriderResponseBody = this.unwrapOverriderResponse(overriderHttpResponse);
|
||
if (!overriderResponseBody || typeof overriderResponseBody !== 'object') {
|
||
this.msgStr = 'Invalid overrider response.';
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
if (!isResponseStatusOk(overriderResponseBody)) {
|
||
this.msgStr = this.pickOverriderStatusMessage(
|
||
overriderResponseBody,
|
||
'Failed to load overriders.'
|
||
);
|
||
this.showMsg = true;
|
||
return;
|
||
}
|
||
const employeeRecords = getResponseRecords(overriderResponseBody);
|
||
this.overrideOptions =
|
||
this.buildOverrideOptionsFromOverriderRecords(employeeRecords);
|
||
this.overrideName = '';
|
||
this.overrideInputText = '';
|
||
|
||
this.showCriteriaNotQualifiedPopup = true;
|
||
|
||
} catch (httpError) {
|
||
console.log(httpError);
|
||
this.msgStr = this.pickErrorMessageFromHttpError(httpError);
|
||
this.showMsg = true;
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
},
|
||
async confirmOverride(){
|
||
const overrideInputValue = this.getOverrideInputValue();
|
||
if (isBlank(overrideInputValue)) {
|
||
uni.showToast({
|
||
title:'Please select overrider',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return;
|
||
}
|
||
const selectedOverrideOption = this.overrideOptions.find(
|
||
(option) => String(option.value) === String(overrideInputValue)
|
||
);
|
||
const overriderId = selectedOverrideOption && selectedOverrideOption.rawEmployeeId
|
||
? String(selectedOverrideOption.rawEmployeeId).trim()
|
||
: this.parseEmployeeIdFromOverriderDisplayText(String(overrideInputValue));
|
||
const validatedOverrideOption = await this.validateOverrider(overriderId);
|
||
if (!validatedOverrideOption) {
|
||
return;
|
||
}
|
||
const overriderName =
|
||
validatedOverrideOption && validatedOverrideOption.rawEmployeeName
|
||
? String(validatedOverrideOption.rawEmployeeName).trim()
|
||
: this.parseEmployeeNameFromIdAndNameText(String(overrideInputValue));
|
||
this.closeCriteriaNotQualifiedPopup();
|
||
await this.executeCreateLimoBooking({
|
||
overriderId,
|
||
overriderName
|
||
});
|
||
this.overrideName = '';
|
||
this.overrideInputText = '';
|
||
},
|
||
buildLimoCarTypeOptionsFromApi(items) {
|
||
if (!Array.isArray(items)) {
|
||
return [];
|
||
}
|
||
return items.map((item) => {
|
||
const label = toTrimmedString(item.carType);
|
||
const value = item.id != null ? String(item.id) : label;
|
||
const bookingKeys = this.extractBookingTypeKeysFromRow(item);
|
||
return { value, label, bookingKeys };
|
||
});
|
||
},
|
||
buildLimoPointOptionsFromApi(items) {
|
||
if (!Array.isArray(items)) {
|
||
return [];
|
||
}
|
||
return items.map((item) => {
|
||
const trad = toTrimmedString(item.chineseTradName);
|
||
const eng = toTrimmedString(item.englishName);
|
||
let label = '';
|
||
if (trad && eng) {
|
||
label = `${trad}/${eng}`;
|
||
} else {
|
||
label = trad || eng;
|
||
}
|
||
const locationId = toTrimmedString(item.id);
|
||
const value = locationId || label;
|
||
const region = toTrimmedString(item.region);
|
||
const tripPriceId = this.parseNumber(item.tripPriceId);
|
||
const freeOfCharge = this.parseNumber(item.freeOfCharge);
|
||
const greetLocation =
|
||
getFirstText(item, ['greetLocation', 'GreetLocation']) || null;
|
||
const rawAirportCode = getFirstText(item, [
|
||
'airportCode',
|
||
'AirportCode',
|
||
'AirPortCode'
|
||
]);
|
||
return {
|
||
value,
|
||
label,
|
||
locationId: locationId || '0',
|
||
region,
|
||
tripPriceId,
|
||
freeOfCharge,
|
||
greetLocation,
|
||
airportCode: rawAirportCode,
|
||
englishName: eng
|
||
};
|
||
});
|
||
},
|
||
async getLocation(){
|
||
const param = { location: '%' };
|
||
try {
|
||
const res = await getLimoLocation(param);
|
||
const list =
|
||
res && res.success == true && Array.isArray(res.data)
|
||
? res.data
|
||
: [];
|
||
const options = this.buildLimoPointOptionsFromApi(list);
|
||
this.pickUpPointOptions = options;
|
||
this.dropOffPointOptions = options;
|
||
} catch (e) {
|
||
console.log(e);
|
||
this.pickUpPointOptions = [];
|
||
this.dropOffPointOptions = [];
|
||
} finally {
|
||
await this.getLimoCarType();
|
||
}
|
||
},
|
||
async getLimoCarType(){
|
||
const param = {};
|
||
try {
|
||
const res = await getLimoCarType(param);
|
||
const list =
|
||
res && res.success !== false && Array.isArray(res.data)
|
||
? res.data
|
||
: [];
|
||
this.carTypeOptionsAll = this.buildLimoCarTypeOptionsFromApi(list);
|
||
} catch (e) {
|
||
console.log(e);
|
||
this.carTypeOptionsAll = [];
|
||
} finally {
|
||
await this.getGreetType();
|
||
}
|
||
},
|
||
async getGreetType(){
|
||
const param = {};
|
||
try {
|
||
const res = await getLimoGreetType(param);
|
||
const list =
|
||
res && res.success !== false && Array.isArray(res.data)
|
||
? res.data
|
||
: [];
|
||
this.greetTypeItemsAll = Array.isArray(list) ? list : [];
|
||
} catch (e) {
|
||
console.log(e);
|
||
this.greetTypeItemsAll = [];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" src="../../static/css/limo.scss"></style>
|