掛單管理

This commit is contained in:
minom committed 2026-11-06 18:06:50 +08:00
1 parent 8db6019ba3
commit 9e251fc7d4
12 files changed
+2197 -116

No files matched your search

+921
View File
@@ -0,0 +1,921 @@
<template>
<div class="container-fluid">
<div>
<v-tabs>
<v-tab value="tab-1" @click="tab='tab-1'">
掛單資訊
</v-tab>
<v-tab value="tab-2" @click="tab='tab-2'">
掛單明細
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<!-- ================== Tab 1: 掛單資訊 ================== -->
<v-tab-item value="tab-1">
<div class="border rounded p-3 bg-white shadow-sm mt-3">
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">掛單單號(不可修改)</label>
<div class="col-sm-4 text-left">
<input class="form-control" v-model="guadanorder.order_form.orderNo" readonly />
</div>
<label class="col-sm-2 col-form-label text-center">關聯活動</label>
<div class="col-sm-4">
<select class="form-control" v-model="activityNum">
<option :value="null">未關聯</option>
<option v-for="activity in activityList" :key="activity.num" :value="activity.num">
{{activity.subject}}
</option>
</select>
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">預約開始日期</label>
<div class="col-sm-4 text-left">
<input class="form-control" type="date" disabled v-model="guadanorder.order_form.startdate" />
</div>
<label class="col-sm-2 col-form-label text-center">預約結束日期</label>
<div class="col-sm-4">
<input class="form-control" type="date" disabled v-model="guadanorder.order_form.enddate" />
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">預定人姓名</label>
<div class="col-sm-4">
<input class="form-control" v-model="guadanorder.order_form.bookerName" />
</div>
<label class="col-sm-2 col-form-label text-center">預定人電話</label>
<div class="col-sm-4">
<input class="form-control" v-model="guadanorder.order_form.bookerPhone" />
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">備註</label>
<div class="col-sm-4">
<textarea class="form-control" v-model="guadanorder.order_form.note"></textarea>
</div>
</div>
</div>
<div class="d-flex mb-3">
<!--v-if="guadanorder.order_form.uuid"-->
<button type="button" class="btn btn-primary mx-auto" style="width: 120px;" @click="updateGuadanOrder">
存檔
</button>
</div>
</v-tab-item>
<!-- ================== Tab 2: 掛單明細 ================== -->
<v-tab-item value="tab-2">
<v-row class="mt-2">
<!-- 左側:掛單明細列表 -->
<v-col cols="12" md="8">
<div class="d-flex align-items-center mb-3">
<v-btn color="primary" @click="showCheckInModal" class="me-3">
<v-icon left>mdi-plus</v-icon>設定人數
</v-btn>
<v-btn @click="showAutomaticBedAllocation">
自動分配床位
</v-btn>
</div>
<v-data-table :headers="guadanguest.headers" :items="guadanguest.items" class="elevation-1 rounded" dense>
<template #item.checkinat="{item}">
{{item.checkinat |timeString('YYYY-MM-DD')}}
</template>
<template #item.checkoutat="{item}">
{{item.checkoutat |timeString('YYYY-MM-DD')}}
</template>
<!--<template v-slot:item.name="{item}">-->
<!--</template>-->
<!-- 姓名欄位改為輸入框:未輸入時預設顯示當前順位編號 -->
<template v-slot:item.name="{item}">
<v-text-field v-model="item.fullName"
dense
hide-details
outlined
class="my-1"
:placeholder="item.fullName==null?item.name:(String(guadanguest.items.indexOf(item) + 1))"
style="min-width: 50px; font-size: 14px;"></v-text-field>
</template>
<template v-slot:item.sex="{item}">
{{item.gender}}
</template>
<template v-slot:item.room="{item}">
<span v-if="item.bedName || item.room">
{{ item.room ? item.room.resion.join("/") : '' }}
{{ item.room ? item.room.name : '' }}
{{ item.bedName || '' }}
</span>
<span v-else class="text-muted">
未分配床位
</span>
</template>
<template v-slot:item.indicators="{item}">
<div class="d-flex justify-center align-center">
<v-chip small :color="getStatusColor(item.statuscode)" text-color="white" class="me-2" style="font-size: 12px; height: 20px;">
{{ item.statusName || '待入住' }}
</v-chip>
<v-tooltip bottom v-if="item.note">
<template v-slot:activator="{ on, attrs }">
<v-icon color="warning" v-bind="attrs" v-on="on" size="20" class="me-1">mdi-note-text</v-icon>
</template>
<span>備註: {{ item.note }}</span>
</v-tooltip>
<v-icon v-else color="grey lighten-3" size="20" class="me-1">mdi-note-text-outline</v-icon>
<v-tooltip bottom v-if="item.specialNeeds && item.specialNeeds !== '無特殊需求'">
<template v-slot:activator="{ on, attrs }">
<v-icon color="error" v-bind="attrs" v-on="on" size="20">mdi-alert-circle</v-icon>
</template>
<span>特殊需求: {{ getSpecialNeedsText(item.specialNeeds) }}</span>
</v-tooltip>
<v-icon v-else color="grey lighten-3" size="20">mdi-alert-circle-outline</v-icon>
</div>
</template>
<template #item.actions="{ item }">
<div class="d-flex align-center">
<v-btn color="success" outlined small class="me-2" @click="selectGuestForBed(item)">
<v-icon left size="16">mdi-bed</v-icon>選床
</v-btn>
<v-menu offset-y transition="slide-y-transition">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" text small v-bind="attrs" v-on="on">
更多 <v-icon right size="18">mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list dense>
<v-list-item :disabled="item.statuscode !== '401'" @click="changeGuadanGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-cancel</v-icon></v-list-item-icon>
<v-list-item-title class="error--text">變更</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '401'" @click="confirmDeleteGuadanOrderGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-cancel</v-icon></v-list-item-icon>
<v-list-item-title class="error--text">取消預訂</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '401'" @click="checkinGuadanGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-login</v-icon></v-list-item-icon>
<v-list-item-title class="error--text">入住</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '402'" @click="showXuzhuGuestModalMethod(item)">
<v-list-item-icon class="me-2"><v-icon color="warning" size="20">mdi-calendar-plus</v-icon></v-list-item-icon>
<v-list-item-title class="warning--text">續住</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '402'" @click="checkoutGuadanOrderGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="primary" size="20">mdi-exit-run</v-icon></v-list-item-icon>
<v-list-item-title class="primary--text">退房</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</template>
</v-data-table>
</v-col>
<!-- 右側:可選的大樓與房間床位 -->
<v-col cols="12" md="4">
<v-card class="elevation-1 h-100" style="min-height: 400px;">
<v-card-title class="bg-light">
分配床位
</v-card-title>
<v-divider></v-divider>
<v-card-text v-if="selectedGuestForBed">
<div class="mb-3 text-primary font-weight-bold">
目前選擇:{{ selectedGuestForBed.fullName || (guadanguest.items.indexOf(selectedGuestForBed) + 1) }} ({{ selectedGuestForBed.gender }})
</div>
<!-- 最外層:大樓 -->
<v-expansion-panels v-model="activeBuildingPanel" accordion>
<v-expansion-panel v-for="building in filteredMockBuildings" :key="building.id">
<v-expansion-panel-header class="font-weight-bold">
<div class="d-flex align-center">
<v-icon color="indigo" class="me-2" size="20">mdi-office-building-marker</v-icon>
{{ building.name }} (剩餘: {{ buildingAvailableBedsCount(building) }})
</div>
</v-expansion-panel-header>
<v-expansion-panel-content>
<!-- 內層:房間與床位 (直接以列表呈現,避免雙層手風琴太雜亂) -->
<div v-for="(room, index) in building.rooms" :key="room.id" :class="{'mt-4': index > 0}">
<!-- 房間標題 -->
<div class="text-subtitle-2 mb-1 text-indigo">
<v-icon small color="indigo" class="me-1">mdi-door-closed</v-icon>
{{ room.name }} (剩餘: {{ roomAvailableBedsCount(room) }})
</div>
<!-- 床位列表 (加入 Tooltip 提示特殊條件) -->
<v-chip-group column>
<template v-for="bed in room.beds">
<!-- 封裝 Tooltip:有條件時提示內容,沒條件時提示無特殊限制 -->
<v-tooltip bottom :key="bed.id">
<template v-slot:activator="{ on, attrs }">
<v-chip :color="bed.id === selectedGuestForBed.bedUuid ? 'success' : (bed.isAvailableNow ? 'primary' : 'grey')"
:text-color="bed.id === selectedGuestForBed.bedUuid || bed.isAvailableNow ? 'white' : 'black'"
:disabled="!bed.isAvailableNow && bed.id !== selectedGuestForBed.bedUuid"
@click="assignBedToGuest(building, room, bed)"
v-bind="attrs"
v-on="on"
small>
{{ bed.name }}
<v-icon v-if="bed.id === selectedGuestForBed.bedUuid" right size="14">mdi-check-circle</v-icon>
<!-- 若床位有特殊條件,加上小圖示標示 -->
<v-icon v-if="bed.limits && bed.limits.length > 0" right size="12" color="amber lighten-3">mdi-star</v-icon>
</v-chip>
</template>
<span>
{{ bed.name }}
<template v-if="bed.limits && bed.limits.length > 0">
<br>【具備條件】:{{ getSpecialNeedsText(Array.isArray(bed.limits) ? bed.limits.join(',') : String(bed.limits)) }}
</template>
<template v-else>
<br>(無特殊限制床位)
</template>
</span>
</v-tooltip>
</template>
</v-chip-group>
<v-divider v-if="index < building.rooms.length - 1" class="mt-2"></v-divider>
</div>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<div v-if="filteredMockBuildings.length === 0" class="text-danger mt-3">
沒有符合該性別條件的大樓與房間。
</div>
</v-card-text>
<v-card-text v-else class="text-center mt-10 text-muted">
<v-icon size="64" color="grey lighten-2">mdi-hand-pointing-left</v-icon>
<div class="mt-4">請點選左側名單的「選床」按鈕<br>開始分配床位</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-tab-item>
</v-tabs-items>
</div>
<v-dialog v-model="message_dialog" max-width="500">
<v-card>
<v-card-title class="text-h6">系統提示</v-card-title>
<v-card-text class="pt-4">{{message}}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="message_dialog = false">確認</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="checkInGuest.showSelectGuadanOrderGuest" max-width="600">
<v-card>
<v-card-title>設定人數條件</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12"><v-text-field label="人數" type="number" v-model="e4"></v-text-field></v-col>
<v-col cols="12">
<v-menu v-model="menu2" :close-on-content-click="false" transition="scale-transition" offset-y min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-model="s_date" label="開始日期" prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="s_date" @input="menu2 = false"></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12">
<v-menu v-model="menu3" :close-on-content-click="false" transition="scale-transition" offset-y min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-model="e_date" label="結束日期" prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="e_date" @input="menu3 = false"></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12">
<v-select :items="e2" v-model="e5" label="性別"></v-select>
</v-col>
<v-col cols="12">
<v-select v-model="e6" :items="states" item-text="text" item-value="value" label="特殊需求" multiple hint="選擇條件" persistent-hint></v-select>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="checkInGuest.showSelectGuadanOrderGuest = false">關閉</v-btn>
<v-btn color="primary" @click.prevent="genGuestList">產生</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
module.exports = {
name: "edit-component",
props: {
formData: {
type: String,
default: "",
}
},
data() {
return {
tab: "tab-1",
e1: null,
e2: ['請選擇', '男眾', '女眾'],
e4: "",
e5: "",
e6: [],
s_date: null,
menu2: false,
menu3: false,
e_date: null,
states: [
//'無特殊需求', '需要輪椅', '需要拐杖', '需要陪同人員', '需要特別飲食', '其他需求'
],
message_dialog: false,
message: "",
activityList: [],
activityNum: null,
availableBedCount: { male: 0, female: 0 },
guadanorder: {
order_form: {
uuid: null,
startdate: null,
enddate: null,
note: null,
orderNo: null,
bookerName: null,
bookerPhone: null,
bookerFollowerNum: null,
activityNum: null,
},
},
guadanguest: {
headers: [
{ text: '標記/狀態', value: 'indicators', align: 'center', sortable: false, width: '130px' },
{ text: '姓名', value: 'name' },
{ text: '性別', value: 'sex' },
{ text: '掛單開始時間', value: 'checkinat' },
{ text: '掛單結束時間', value: 'checkoutat' },
{ text: '床位', value: 'room' },
{ text: '操作', value: 'actions', sortable: false },
],
items: [],
},
checkInGuest: {
showSelectGuadanOrderGuest: false,
inGuest: {
checkInAt: null,
checkOutAt: null,
orderNo: null,
}
},
automaticBedAllocation: {
showModal: false,
},
selectedGuestForBed: null,
activeBuildingPanel: null, // 控制大樓的展開面板
// 模擬資料層級: 大樓 -> 房間 -> 床位
mockBuildings: [],
}
},
watch: {
activityNum: {
handler(newVal) {
let act = this.activityList.find(activity => activity.num == newVal);
if (act) {
this.guadanorder.order_form.startdate = act.startDate;
this.guadanorder.order_form.enddate = act.endDate;
}
},
deep: true,
immediate: true
},
formData: {
handler(newVal) {
//用掛單的uuid去抓掛單資料跟明細檔
console.log('掛單uuid', newVal);
if (newVal != "") {
this.getGuadanData();
}
},
deep: true,
immediate: true
}
},
mounted() {
this.getActivityList()
this.getGuadanLimits()
},
computed: {
assignedBedUuids() {
return this.guadanguest.items
.map(item => item.bedUuid)
.filter(uuid => uuid);
},
filteredMockBuildings() {
if (!this.selectedGuestForBed) return [];
const guestGender = this.selectedGuestForBed.gender;
const currentGuestBedUuid = this.selectedGuestForBed.bedUuid;
const guestNeeds = (this.selectedGuestForBed.specialNeeds || '').split(',').filter(c => c);
return this.mockBuildings.map(building => {
// 過濾房間並處理床位動態狀態
const filteredRooms = building.rooms.map(room => {
return {
...room,
beds: room.beds.map(bed => {
const isTakenByOthers = this.assignedBedUuids.includes(bed.id) && bed.id !== currentGuestBedUuid;
// 檢查條件是否完全滿足
const bedConditions = bed.limits || [];
const isMatchConditions = guestNeeds.every(need => bedConditions.includes(need));
return {
...bed,
isAvailableNow: bed.isAvailable && !isTakenByOthers && isMatchConditions,
isMatchConditions: isMatchConditions
};
})
};
}).filter(room =>
!guestGender || guestGender === '請選擇' || room.genderLimit === guestGender
);
return {
...building,
rooms: filteredRooms
};
}).filter(building => building.rooms.length > 0); // 隱藏完全沒有符合條件房間的大樓
}
},
methods: {
getGuadanData() {//抓出掛單資料根明細檔
let self = this;
axios.post(HTTP_HOST + 'api/guadan/getorderbyuuid', { uuid: self.formData }).then((res) => {
if (res.status == 200) {
console.log('掛單資料', res.data);
self.guadanorder.order_form = res.data.order_form;
self.guadanguest.items = res.data.details;
}
})
},
getStatusColor(code) {
switch (code) {
case '401': return 'blue';
case '402': return 'green';
default: return 'grey';
}
},
// 算整棟大樓的可用床位總和
buildingAvailableBedsCount(building) {
let count = 0;
building.rooms.forEach(room => {
count += room.beds.filter(b => b.isAvailableNow).length;
});
return count;
},
// 算單一房間的可用床位
roomAvailableBedsCount(room) {
return room.beds.filter(b => b.isAvailableNow).length;
},
getSpecialNeedsText(specialNeedsCodes) {
if (!specialNeedsCodes) return '';
const codes = specialNeedsCodes.split(',');
let self = this;
const names = codes.map(code => {
const target = self.states.find(s => s.value === code);
return target ? target.text : code;
});
return names.join('、');
},
getGuadanLimits() {//抓掛單條件
this.states.length = 0; // 清空陣列
let self = this;
let data = [];
axios.post(HTTP_HOST + 'api/guadan/getGuadanLimit').then((res) => {
if (res.status == 200) {
res.data.forEach(item => {
self.states.push({
text: item.paramName,
value: item.paramNo
});
})
}
})
},
genGuestList() {
if (!this.e4) { console.log('請輸入人數'); return; }
if (!this.e5 || this.e5 == 0) { console.log("請選擇男眾或女眾"); return; }
if (!this.s_date) { console.log('請選擇開始日期'); return; }
if (!this.e_date) { console.log('請選擇結束日期'); return; }
const today = moment().format('YYYY-MM-DD');
if (this.s_date < today) {
this.message = '開始日期不可小於今天';
this.message_dialog = true;
return;
}
// 檢查 2:結束日期不可小於開始日期
if (this.e_date < this.s_date) {
this.message = '結束日期不可小於開始日期';
this.message_dialog = true;
return;
}
for (let i = 0; i < this.e4; i++) {
const tempUuid = 'temp-' + Date.now() + '-' + i;
this.guadanguest.items.push({
uuid: tempUuid,
fullName: '',
gender: this.e5,
checkinat: this.s_date,
checkoutat: this.e_date,
statuscode: '401',
statusName: '已預訂',
note: '',
specialNeeds: this.e6.join(",")
});
}
this.s_date = null;
this.e_date = null;
this.e5 = "";
this.e4 = null;
this.e6 = [];
this.checkInGuest.showSelectGuadanOrderGuest = false;
},
getActivityList() {
const params = { page: '1', pageSize: 10000 };
axios.post(HTTP_HOST + 'api/activity/GetListNewYet', {
up_time1: moment(new Date()).format('YYYY-MM-DD')
}, { params: params }).then((res) => {
if (res.status == 200 && res.data && res.data.list) {
this.activityList = res.data.list;
}
})
},
validateOrderForm() {
if (!this.guadanorder.order_form.startdate) { this.message = '請輸入開始日期'; this.message_dialog = true; return false; }
if (!this.guadanorder.order_form.enddate) { this.message = '請輸入結束日期'; this.message_dialog = true; return false; }
const startDate = this.guadanorder.order_form.startdate;
const endDate = this.guadanorder.order_form.enddate;
// 取得今天日期字串 (YYYY-MM-DD)
const today = moment().format('YYYY-MM-DD');
// 檢查 1:預約開始日期不可小於今天
if (startDate < today) {
this.message = '預約開始日期不可小於今天';
this.message_dialog = true;
return false;
}
// 檢查 2:預約結束日期不可小於預約開始日期
if (endDate < startDate) {
this.message = '預約結束日期不可小於預約開始日期';
this.message_dialog = true;
return false;
}
if (!this.guadanorder.order_form.bookerName) { this.message = '請輸入姓名'; this.message_dialog = true; return false; }
if (!this.guadanorder.order_form.bookerPhone) { this.message = '請輸入電話'; this.message_dialog = true; return false; }
// 檢查 3:若有掛單明細,檢查是否有「未分配床位」的人員
if (this.guadanguest.items && this.guadanguest.items.length > 0) {
// 找出沒有 bedUuid 的人員
const unassignedGuests = this.guadanguest.items.filter(item => !item.bedUuid);
if (unassignedGuests.length > 0) {
// 找出第一個未分配人員的顯示名稱或順位號碼
const firstUnassignedIdx = this.guadanguest.items.indexOf(unassignedGuests[0]) + 1;
const guestIdentifier = unassignedGuests[0].fullName || `第 ${firstUnassignedIdx} 位預訂人`;
this.message = `無法存檔!尚有 ${unassignedGuests.length} 位人員未分配床位(例如:${guestIdentifier})。請先完成床位分配再存檔!`;
this.message_dialog = true;
return false;
}
}
return true;
},
createGuadanOrder() {
if (!this.validateOrderForm()) return;
axios.post(HTTP_HOST + 'api/guadan/create', this.guadanorder.order_form)
.then((res => {
this.message_dialog = true
this.message = '掛單資料建立成功'
this.guadanorder.order_form.uuid = res.data.uuid;
this.guadanorder.order_form.orderNo = res.data.guaDanOrderNo;
const currentUrl = window.location.origin + window.location.pathname;
window.location.href = `${currentUrl}?orderId=${res.data.guaDanOrderNo}`;
})).catch((error) => {
this.message_dialog = true
this.message = '建立掛單資料失敗:' + (error.response?.data?.message || error.message)
});
},
updateGuadanOrder() {
if (!this.validateOrderForm()) return;
axios.post(HTTP_HOST + 'api/guadan/updateGandan', { order: this.guadanorder.order_form })
.then((res => {
this.message_dialog = true
this.message = '掛單資料更新成功'
this.guadanorder.order_form.uuid = res.data.uuid;
})).catch((error) => {
this.message_dialog = true
this.message = '掛單資料更新失敗:' + (error.response?.data?.message || error.message)
});
},
showCheckInModal() {
this.checkInGuest.inGuest.checkInAt = this.guadanorder.order_form.startdate || null;
this.checkInGuest.inGuest.checkOutAt = this.guadanorder.order_form.enddate || null;
this.checkInGuest.inGuest.orderNo = this.guadanorder.order_form.orderNo;
this.s_date = this.guadanorder.order_form.startdate || null;
this.e_date = this.guadanorder.order_form.enddate || null;
this.checkInGuest.showSelectGuadanOrderGuest = true;
},
async showAutomaticBedAllocation() {
console.log(this.guadanguest.items);
// 1. 篩選出尚未分配床位的人員
const unassignedGuests = this.guadanguest.items.filter(item => !item.bedUuid);
if (unassignedGuests.length === 0) {
this.message = '目前所有人員皆已完成床位分配!';
this.message_dialog = true;
return;
}
unassignedGuests.sort((a, b) => {
const needsA = (a.specialNeeds || '').split(',').filter(c => c && c !== '無特殊需求').length;
const needsB = (b.specialNeeds || '').split(',').filter(c => c && c !== '無特殊需求').length;
return needsB - needsA; // 數量多的排在前面 (降冪)
});
let successCount = 0;
let failCount = 0;
// 2. 逐一處理未分配床位的人員
for (const guest of unassignedGuests) {
try {
// 取得該人員入住時間區間的可分配床位
const resp = await axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: guest.checkinat,
endTime: guest.checkoutat
});
const availableBeds = resp.data?.availableBeds || [];
// 過濾出真正的特殊需求 (排除「無特殊需求」與空字串)
const rawNeeds = (guest.specialNeeds || '').split(',');
const guestNeeds = rawNeeds.filter(need => need && need !== '無特殊需求');
// 3. 搜尋合適的床位
const matchedBed = availableBeds.find(bed => {
// A. 床位狀態需為可用 ('101')
if (bed.statusCode !== '101') return false;
// B. 不能已被目前列表中的其他人佔用
if (this.assignedBedUuids.includes(bed.uuid)) return false;
// C. 性別限制過濾
const regionName = bed.room?.regionName || '';
let roomGenderLimit = null;
if (regionName.includes('男眾')) roomGenderLimit = '男眾';
else if (regionName.includes('女眾')) roomGenderLimit = '女眾';
if (guest.gender && guest.gender !== '請選擇' && roomGenderLimit) {
if (guest.gender !== roomGenderLimit) return false;
}
// D. 特殊需求條件檢查 (人員需要的,床位必須具備)
const bedConditions = bed.limits || [];
const isMatchConditions = guestNeeds.every(need => bedConditions.includes(need));
if (!isMatchConditions) return false;
return true;
});
// 4. 若找到合適床位,進行分配
if (matchedBed) {
const buildingName = matchedBed.room?.regionName || '';
const roomName = matchedBed.room?.name || '';
const bedName = matchedBed.name || '';
this.$set(guest, 'bedName', `${buildingName} - ${roomName} - ${bedName}`);
this.$set(guest, 'bedUuid', matchedBed.uuid);
this.$set(guest, 'roomUuid', matchedBed.roomUuid);
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error('自動分配床位發生錯誤:', error);
failCount++;
}
}
// 5. 分配完成提示
this.message = `自動分配完成!成功分配 ${successCount} 位,失敗/無合適床位 ${failCount} 位。`;
this.message_dialog = true;
//if (!this.guadanorder.order_form.uuid) {
// this.message_dialog = true;
// this.message = '請先儲存掛單資訊';
// return;
//}
//this.automaticBedAllocation.showModal = true;
//this.getavailablebedcountbytime(this.guadanorder.order_form.startdate, this.guadanorder.order_form.enddate);
},
getavailablebedcountbytime(startTime, endTime) {
axios.get(HTTP_HOST + 'api/region/bed/getavailablebedcountbytime', {
params: { startTime: startTime, endTime: endTime }
}).then((res) => {
this.availableBedCount = res.data;
})
},
selectGuestForBed(item) {
this.selectedGuestForBed = item;
this.activeBuildingPanel = null;
},
// 參數加入 building 用來串接完整名稱
assignBedToGuest(building, room, bed) {
if (!bed.isAvailableNow && bed.id !== this.selectedGuestForBed.bedUuid) return;
// 1. 安全轉換函數:強制將各種型別轉換為標準陣列
const ensureArray = (val) => {
if (!val) return [];
if (Array.isArray(val)) return val;
if (typeof val === 'string') return val.split(',').filter(c => c && c.trim() !== '');
if (typeof val === 'object') return Object.values(val).filter(c => c);
return [String(val)];
};
// 取出安全的床位條件陣列
const bedConditions = ensureArray(bed.limits);
// 2. 條件比對檢查 (檢核特殊需求)
const guestNeedsStr = this.selectedGuestForBed.specialNeeds || '';
// 將人員需求轉換為代碼陣列 (過濾空值)
const guestNeeds = guestNeedsStr ? guestNeedsStr.split(',').filter(c => c) : [];
// 床位條件 (若後端未傳回則預設為空陣列)
//const bedConditions = bed.limits || [];
// 檢查人員有的需求,床位是否未滿足 (找出不合適的條件代碼)
const unmatchedNeeds = guestNeeds.filter(need => !bedConditions.includes(need));
if (unmatchedNeeds.length > 0) {
// 將未滿足的需求代碼轉換為中文名稱
const unmatchedNames = unmatchedNeeds.map(code => {
const target = this.states.find(s => s.value === code);
return target ? target.text : code;
}).join('、');
// 跳出提示阻擋分配(或改為 confirm 提醒)
this.message = `不符合分配條件!該人員需要「${unmatchedNames}」,但此床位未具備對應條件。`;
this.message_dialog = true;
return; // 中斷分配
}
// 將大樓名稱也組進去顯示
// 2. 取得床位具備的特殊條件文字
let bedConditionNotice = '';
if (bedConditions.length > 0) {
const bedConditionNames = bedConditions.map(code => {
const target = this.states.find(s => s.value === code);
return target ? target.text : code;
}).join('、');
bedConditionNotice = `\n【床位具備條件】:${bedConditionNames}`;
} else {
bedConditionNotice = `\n【床位具備條件】:無特殊限制`;
}
// 3. 設定床位並跳出點擊後的二次提示
this.$set(this.selectedGuestForBed, 'bedName', `${building.name} - ${room.name} - ${bed.name}`);
this.$set(this.selectedGuestForBed, 'bedUuid', bed.id);
this.message = `已將「${building.name} ${room.name} ${bed.name}」分配給該明細!`;
this.message_dialog = true;
},
async getavailablebedbytime(startTime, endTime) {
axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: startTime, endTime: endTime
}).then((res) => {
//this.availableBedCount = res.data;
return res.data;
})
},
async selectGuestForBed(item) {
axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: item.checkinat, endTime: item.checkoutat
}).then((resp) => {
const rawData = resp.data;
const transformData = (data) => {
// 1. 使用 reduce 依據 regionUuid 與 roomUuid 進行分群
const grouped = data.availableBeds.reduce((acc, bed) => {
const regionId = bed.room.regionUuid;
const regionName = bed.room.regionName;
const roomId = bed.roomUuid;
const roomName = bed.room.name;
// 自動判斷性別限制 (若區域名稱包含"男眾"則為"男眾",反之女眾)
const genderLimit = regionName.includes('男眾') ? '男眾' : '女眾';
// 假設 statusCode '101' 代表可用 (true),其他代表不可用 (false)
const isAvailable = bed.statusCode === '101';
// 如果大樓/區域還不存在,建立新大樓
if (!acc[regionId]) {
acc[regionId] = {
id: regionId,
name: regionName,
roomsMap: {} // 使用 Map 方便後續依 roomId 查找
};
}
// 如果該大樓下的客房還不存在,建立新客房
if (!acc[regionId].roomsMap[roomId]) {
acc[regionId].roomsMap[roomId] = {
id: roomId,
name: `${roomName} (${genderLimit})`, // 組合區分名稱,如:客房1 (男眾)
genderLimit: genderLimit,
beds: []
};
}
// 將床位資料推入該客房的 beds 陣列中
acc[regionId].roomsMap[roomId].beds.push({
id: bed.uuid,
name: bed.name,
isAvailable: isAvailable,
limits: bed.limits || [],
});
return acc;
}, {});
// 2. 將物件格式扁平化轉換成您要求的陣列格式 []
return Object.values(grouped).map(region => ({
id: region.id,
name: region.name,
rooms: Object.values(region.roomsMap) // 將 rooms 轉換成陣列
}));
};
// 執行轉換
const result = transformData(rawData);
this.mockBuildings = result;
this.selectedGuestForBed = item;
this.activeRoomPanel = null;
})
},
changeGuadanGuest(item) {//可以變更人員、床位
console.log('變更人員、床位', item);
},
confirmDeleteGuadanOrderGuest(item) {
console.log('取消預訂', item);
},
checkinGuadanGuest(item) {
console.log('入住', item);
},
showXuzhuGuestModalMethod(item) {
console.log('續住', item);
},
checkoutGuadanOrderGuest(item) {
console.log('退房', item);
}
}
}
</script>
<style>
.form-group label {
font-weight: 600;
}
</style>