Files
mgm-daylight-mcrm-mobile-ios/limo/newLimo/newLimo.vue
T
2026-09-22 16:37:00 +08:00

3113 lines
104 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>