掛單管理

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

+248 -32
View File
@@ -118,7 +118,7 @@
<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>特殊需求: {{ item.specialNeeds }}</span>
<span>特殊需求: {{ getSpecialNeedsText(item.specialNeeds) }}</span>
</v-tooltip>
<v-icon v-else color="grey lighten-3" size="20">mdi-alert-circle-outline</v-icon>
</div>
@@ -138,6 +138,10 @@
</v-btn>
</template>
<v-list dense>
<v-list-item :disabled="item.statuscode !== '401'" @click="confirmRemoveGuadanOrderGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-trash</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>
@@ -197,18 +201,36 @@
{{ room.name }} (剩餘: {{ roomAvailableBedsCount(room) }})
</div>
<!-- 床位列表 -->
<!-- 床位列表 (加入 Tooltip 提示特殊條件) -->
<v-chip-group column>
<v-chip v-for="bed in room.beds"
:key="bed.id"
: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)"
small>
{{ bed.name }}
<v-icon v-if="bed.id === selectedGuestForBed.bedUuid" right size="14">mdi-check-circle</v-icon>
</v-chip>
<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>
@@ -277,7 +299,7 @@
<v-select :items="e2" v-model="e5" label="性別"></v-select>
</v-col>
<v-col cols="12">
<v-select v-model="e6" :items="states" label="特殊需求" multiple hint="選擇條件" persistent-hint></v-select>
<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>
@@ -391,20 +413,28 @@
const guestGender = this.selectedGuestForBed.gender;
const currentGuestBedUuid = this.selectedGuestForBed.bedUuid;
const guestNeeds = (this.selectedGuestForBed.specialNeeds || '').split(',').filter(c => c);
return this.mockBuildings.map(building => {
console.log('filteredMockBuildings:', 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));
room["regionPath"] = bed.regionPath;
return {
...bed,
isAvailableNow: bed.isAvailable && !isTakenByOthers
isAvailableNow: bed.isAvailable && !isTakenByOthers && isMatchConditions,
isMatchConditions: isMatchConditions
};
})
};
console.log("filteredMockBuildings: room:", room);
}).filter(room =>
!guestGender || guestGender === '請選擇' || room.genderLimit === guestGender
);
@@ -438,6 +468,16 @@
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;
@@ -445,8 +485,11 @@
axios.post(HTTP_HOST + 'api/guadan/getGuadanLimit').then((res) => {
if (res.status == 200) {
res.data.forEach(item => {
console.log('掛單條件', item);
self.states.push(item.paramName);
self.states.push({
text: item.paramName,
value: item.paramNo
});
})
}
@@ -457,7 +500,18 @@
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({
@@ -495,8 +549,40 @@
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;
},
@@ -522,7 +608,6 @@
let detail = []
this.guadanguest.items.forEach(item => {
console.log('明細', item);
detail.push({
uuid: item.uuid,
fullName: item.fullName,
@@ -534,10 +619,12 @@
note: item.note,
handi: item.specialNeeds,
bedUuid: item.bedUuid,
roomUuid: item.roomUuid,
name: item.fullName
})
console.log('掛單明細', detail);
})
console.log('掛單主檔', this.guadanorder.order_form);
//console.log('掛單主檔', this.guadanorder.order_form);
if (!this.validateOrderForm()) return;
axios.post(HTTP_HOST + 'api/guadan/saveGandan', { order: this.guadanorder.order_form, detail: detail })
.then((res => {
@@ -564,14 +651,94 @@
this.checkInGuest.showSelectGuadanOrderGuest = true;
},
showAutomaticBedAllocation() {
if (!this.guadanorder.order_form.uuid) {
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;
this.message = '請先儲存掛單資訊';
return;
}
this.automaticBedAllocation.showModal = true;
this.getavailablebedcountbytime(this.guadanorder.order_form.startdate, this.guadanorder.order_form.enddate);
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) {
@@ -591,9 +758,56 @@
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【床位具備條件】:無特殊限制`;
}
console.log(room, bed);
// 3. 設定床位並跳出點擊後的二次提示
this.$set(this.selectedGuestForBed, 'bedName', `${building.name} - ${room.name} - ${bed.name}`);
this.$set(this.selectedGuestForBed, 'bedUuid', bed.id);
this.$set(this.selectedGuestForBed, 'roomUuid', room.id);
this.message = `已將「${building.name} ${room.name} ${bed.name}」分配給該明細!`;
this.message_dialog = true;
@@ -610,7 +824,6 @@
},
async selectGuestForBed(item) {
console.log('選床', item);
axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: item.checkinat, endTime: item.checkoutat
}).then((resp) => {
@@ -623,7 +836,8 @@
const regionName = bed.room.regionName;
const roomId = bed.roomUuid;
const roomName = bed.room.name;
const regionPath = bed.regionPath;
console.log('床位資料', bed);
// 自動判斷性別限制 (若區域名稱包含"男眾"則為"男眾",反之女眾)
const genderLimit = regionName.includes('男眾') ? '男眾' : '女眾';
@@ -653,7 +867,9 @@
acc[regionId].roomsMap[roomId].beds.push({
id: bed.uuid,
name: bed.name,
isAvailable: isAvailable
isAvailable: isAvailable,
limits: bed.limits || [],
regionPath: regionPath
});
return acc;
@@ -669,15 +885,15 @@
// 執行轉換
const result = transformData(rawData);
//console.log(JSON.stringify(result, null, 4));
this.mockBuildings = result;
this.selectedGuestForBed = item;
this.activeRoomPanel = null;
})
},
confirmRemoveGuadanOrderGuest(item) {
//let temp = this.guadanguest.items.find(x => x.uuid == item.uuid);
this.guadanguest.items = this.guadanguest.items.filter(x => x.uuid != item.uuid);
},
confirmDeleteGuadanOrderGuest(item) {
console.log('取消預訂', item);
},