diff --git a/dojo/Program.cs b/dojo/Program.cs new file mode 100644 index 0000000..2d1c2cf --- /dev/null +++ b/dojo/Program.cs @@ -0,0 +1,16 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddCors(p => p.AddDefaultPolicy(b => b.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod())); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); +if (app.Environment.IsDevelopment()) app.UseDeveloperExceptionPage(); +app.UseCors(); +app.UseSwagger(); +app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json","MyApi v1")); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); +app.Run(); \ No newline at end of file diff --git a/web/App_Code/Model/Model.cs b/web/App_Code/Model/Model.cs index 462d80d..601ca96 100644 --- a/web/App_Code/Model/Model.cs +++ b/web/App_Code/Model/Model.cs @@ -2047,6 +2047,7 @@ namespace Model public System.Guid Uuid { get; set; } public System.Guid RoomUuid { get; set; } public string StatusCode { get; set; } + public string Limits { get; set; } public virtual Room Room { get; set; } public virtual RegionRoomBedStatus RegionRoomBedStatus { get; set; } diff --git a/web/App_Code/Model/Model.edmx b/web/App_Code/Model/Model.edmx index 59956ab..1041ef1 100644 --- a/web/App_Code/Model/Model.edmx +++ b/web/App_Code/Model/Model.edmx @@ -1202,6 +1202,7 @@ + @@ -5507,6 +5508,7 @@ + @@ -7277,6 +7279,7 @@ + diff --git a/web/App_Code/api/guadanOrderController.cs b/web/App_Code/api/guadanOrderController.cs index db0409d..0ab5727 100644 --- a/web/App_Code/api/guadanOrderController.cs +++ b/web/App_Code/api/guadanOrderController.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Threading.Tasks; +using System.util.collections; using System.Web; using System.Web.Http; using static Model.admin_log; @@ -30,7 +31,7 @@ public class guadanOrderController : ApiController var query = _db.GuaDanOrder .Where(a => a.IsCancel == false) .Where(a => a.IsDeleted == false); - if(!string.IsNullOrEmpty(search.guaDanOrderNo)) + if (!string.IsNullOrEmpty(search.guaDanOrderNo)) { query = query.Where(order => order.GuaDanOrderNo == search.guaDanOrderNo); } @@ -87,14 +88,14 @@ public class guadanOrderController : ApiController .Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo && c.IsDeleted == false) .Where(c => c.RegionRoomBedStatus.Code != GuaDanOrderGuest.STATUS_CANCELLED) .Count(), - guadan_status = _db.GuaDanOrderGuest + guadan_status = _db.GuaDanOrderGuest .Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false) .Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED) - .All(g => g.StatusCode == "401") ? new { code=501, name="預約" }: + .All(g => g.StatusCode == "401") ? new { code = 501, name = "預約" } : _db.GuaDanOrderGuest .Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false) .Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED) - .All(g => g.StatusCode == "403") ? new { code = 502, name = "全部退房" } : + .All(g => g.StatusCode == "403") ? new { code = 502, name = "全部退房" } : _db.GuaDanOrderGuest .Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false) .Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED) @@ -110,6 +111,114 @@ public class guadanOrderController : ApiController data }); } + [HttpPost] + [Route("api/guadan/getorderbyuuid")] + public async Task getGuadanOrderByUuid([FromBody] dynamic req) + { + if (!Guid.TryParse((string)req.uuid, out Guid targetGuid)) + { + return BadRequest("無效的 GUID 格式"); + } + var order = await _db.GuaDanOrder + .Where(a => a.Uuid == targetGuid) + .Where(a => a.IsCancel == false && a.IsDeleted == false) + .FirstOrDefaultAsync(); + if (order == null) + { + return BadRequest("未找到對應掛單資料"); + } + // 1. 先把全台/全區的 Region 載入記憶體做成字典,方便 GetParentPath 快速查找 + var regionDict = _db.Region.ToDictionary(r => r.Uuid); + + // 2. 修改你的 GetParentPath 邏輯(改用高效的 while 迴圈) + List GetRegionPathNames(Guid? startRegionUuid) + { + var pathNames = new List(); + Guid? nextId = startRegionUuid; + + while (nextId.HasValue && regionDict.TryGetValue(nextId.Value, out var reg)) + { + pathNames.Add(reg.Name); // 收集區域名稱 + nextId = reg.ParentUuid; // 假設你的欄位叫 ParentUuid,請依實際欄位修改 + } + + // 反轉順序讓它變成:[大區域, 中區域, 小區域] + pathNames.Reverse(); + return pathNames; + } + var result = new + { + order_form = new + { + order.admin, + order.followers, + startdate = order.StartDate?.ToString("yyyy-MM-dd"), + enddate = order.EndDate?.ToString("yyyy-MM-dd"), + order.CreateUser, + order.CreatedAt, + order.UpdatedAt, + note = order.Notes, + orderNo = order.GuaDanOrderNo, + order.BookerFollowerNum, + bookerName = order.BookerName, + bookerPhone = order.BookerPhone, + order.IsDeleted, + uuid = order.Uuid, + activityNum = order.ActivityNum, + }, + details = _db.GuaDanOrderGuest + .Where(g => g.GuaDanOrderNo == order.GuaDanOrderNo && g.IsDeleted == false) + .Select(g => new + { + g.Uuid, + g.GuaDanOrderNo, + g.RoomUuid, + g.BedUuid, + checkinat = g.CheckInAt, + checkoutat = g.CheckOutAt, + gender = g.Sex, + specialNeeds = g.Handi, + name = g.Name, + statuscode = g.StatusCode, + g.FollowerNum, + bedName = _db.RegionRoomBed.Where(b => b.Uuid == g.BedUuid).Select(b => b.Name).FirstOrDefault(), + // 先把資料庫能查的查出來 + roomRaw = _db.Room.Where(r => r.Uuid == g.RoomUuid).Select(r => new + { + r.Name, + r.RegionUuid + }).FirstOrDefault() + }).AsEnumerable() // 🔥 關鍵:從這裡切斷,後面的 Select 會在 C# 記憶體內執行 + .Select(g => new + { + g.Uuid, + g.GuaDanOrderNo, + g.RoomUuid, + g.BedUuid, + g.checkinat, + g.checkoutat, + g.gender, + g.specialNeeds, + g.name, + g.statuscode, + g.FollowerNum, + g.bedName, + room = g.roomRaw == null ? null : new + { + g.roomRaw.Name, + g.roomRaw.RegionUuid, + // 在記憶體中,這時候可以直接查字典拿到當前區域名稱 + regionName = regionDict.TryGetValue(g.roomRaw.RegionUuid, out var r) ? r.Name : null, + // 🔥 這時候呼叫區域函式就不會報錯了! + resion = GetRegionPathNames(g.roomRaw.RegionUuid) + } + }) + .ToList() + + }; + + return Ok(result); + } [HttpGet] [Route("api/guadan/getorderbyid")] public async Task getGuadanOrderById(string orderId) @@ -187,12 +296,53 @@ public class guadanOrderController : ApiController return Ok(guadanorder); } + [HttpPost] + [Route("api/guadan/changeGuadanGuest")] + public async Task ChangeGuadanGuest([FromBody] dynamic req) + { + var json = req; + if (json != null && json.guest != null) + { + var guest = json.guest; + } + + return Ok(); + } + + [HttpPost] + [Route("api/guadan/updateGandan")] + public async Task UpdateGandan([FromBody] dynamic req) + { + var json = req; + if (json != null && json.order != null) + { + var order = json.order; + string guadanOrderNo = order.GuaDanOrderNo; + var oldOrder = _db.GuaDanOrder.Where(a => a.GuaDanOrderNo == guadanOrderNo).FirstOrDefault(); + if (oldOrder == null) + { + return BadRequest("未找到對應掛單資料"); + } + + oldOrder.StartDate = order.startdate; + oldOrder.EndDate = order.enddate; + oldOrder.UpdatedAt = DateTime.Now; + oldOrder.Notes = order.note; + oldOrder.BookerName = order.bookerName; + oldOrder.BookerPhone = order.bookerPhone; + await _db.SaveChangesAsync(); + } + return Ok(); + } + + [HttpPost] [Route("api/guadan/saveGandan")] public async Task SaveGandan([FromBody] dynamic req) { var json = req; - if (json != null&&json.order!=null) { + if (json != null && json.order != null) + { var order = json.order; if (string.IsNullOrEmpty(order.GuaDanOrderNo)) {//表示新增掛單 @@ -215,12 +365,12 @@ public class guadanOrderController : ApiController var list = new List(); var guest = new GuaDanOrderGuest(); var bedList = new List(); - if (json.detail!=null) + if (json.detail != null) { var details = json.detail; foreach (var detail in details) { - guest=new GuaDanOrderGuest + guest = new GuaDanOrderGuest { GuaDanOrderNo = guadanorder.GuaDanOrderNo, Uuid = Guid.NewGuid(), @@ -240,7 +390,7 @@ public class guadanOrderController : ApiController return BadRequest("無效的 GUID 格式"); } - if (detail.bedUuid!=null&&!string.IsNullOrEmpty((string)detail.bedUuid)) + if (detail.bedUuid != null && !string.IsNullOrEmpty((string)detail.bedUuid)) { var bed = _db.RegionRoomBed.Where(r => r.Uuid == targetGuid); if (bed == null) @@ -255,7 +405,8 @@ public class guadanOrderController : ApiController if (!bedIsCanUse) return BadRequest("床位在該時間段內已被占用"); } - else { + else + { return BadRequest("未設定床位"); } @@ -284,54 +435,12 @@ public class guadanOrderController : ApiController } } - + _db.GuaDanOrder.Add(guadanorder); _db.GuaDanOrderGuest.AddRange(list); _db.RegionAndRoomAndBedSchedule.AddRange(bedList); _db.SaveChanges(); } - else - {//表示更新掛單 - var model =await _db.GuaDanOrder.FindAsync((string)order.Uuid); - if (model == null) - { - return BadRequest("未找到對應掛單資料"); - } - model.StartDate = order.startdate; - model.EndDate = order.enddate; - model.Notes = order.note; - model.BookerName = order.bookerName; - model.BookerPhone = order.bookerPhone; - model.ActivityNum = order.activityNum; - - //更新掛單明細...還要處理RegionAndRoomAndBedSchedule - } - //if (json.detail != null) - //{ - // var details = json.detail; - // foreach (var detail in details) - // { - // var guadanDetail = new GuaDanOrderGuest - // { - // GuaDanOrderNo = order.GuaDanOrderNo, - // //FollowerNum = detail.guestName, - // Uuid = detail.uuid, - // StatusCode = "401", // 預約狀態 - // RoomUuid = detail.roomUuid, - // BedUuid = detail.bedUuid, - // CheckInAt = detail.checkInAt, - // CheckOutAt = detail.checkOutAt, - // Sex = detail.gender, - // Handi = detail.handi, - // IsDeleted = false, - // Name = detail.name, - // }; - // _db.GuaDanOrderGuest.Add(guadanDetail); - // } - - - // //return BadRequest("掛單資料不可為空"); - //} } return Ok(); } diff --git a/web/App_Code/api/regionController.cs b/web/App_Code/api/regionController.cs index 3e81908..0d9a45d 100644 --- a/web/App_Code/api/regionController.cs +++ b/web/App_Code/api/regionController.cs @@ -236,7 +236,7 @@ public class regionController : ApiController isactive = c.IsActive, statuscode = c.StatusCode, Gender = c.Gender, - + Limits=c.Limits, }).ToList(), }).ToList(), @@ -349,6 +349,7 @@ public class regionController : ApiController public string statuscode { get; set; } public bool isactive { get; set; } public bool Gender { get; set; } + public string Limits { get; set; } } public class RegionDto diff --git a/web/App_Code/api/regionRoomBedController.cs b/web/App_Code/api/regionRoomBedController.cs index 893fb19..1f1d460 100644 --- a/web/App_Code/api/regionRoomBedController.cs +++ b/web/App_Code/api/regionRoomBedController.cs @@ -195,6 +195,7 @@ public class regionRoomBedController : ApiController oldBed.IsActive = bed.IsActive; oldBed.Name = bed.Name; oldBed.Gender = bed.Gender; + oldBed.Limits = bed.Limits; _db.SaveChanges(); //創建床位 @@ -294,18 +295,86 @@ public class regionRoomBedController : ApiController .Distinct() .ToList(); - // 空閒床位 = 所有床位 - 忙碌床位 - var availableBeds = _db.RegionRoomBed.Select(b=>new { - b.Name ,b.Uuid,b.StatusCode,b.IsActive, - b.RoomUuid, - Room=_db.Room.Where(r=>r.Uuid==b.RoomUuid). - Select(r=>new { r.Name,r.RegionUuid, - RegionName=_db.Region.Where(g=>g.Uuid==r.RegionUuid).Select(g=>g.Name).FirstOrDefault()}).FirstOrDefault(), - }) - .Where(b => !busyBedUuids.Contains(b.Uuid)) - .ToList(); + // 1. 先把全台/全區的 Region 載入記憶體做成字典,方便 GetParentPath 快速查找 + var regionDict = _db.Region.ToDictionary(r => r.Uuid); + + // 2. 修改你的 GetParentPath 邏輯(改用高效的 while 迴圈) + List GetRegionPathNames(Guid? startRegionUuid) + { + var pathNames = new List(); + Guid? nextId = startRegionUuid; + + while (nextId.HasValue && regionDict.TryGetValue(nextId.Value, out var reg)) + { + pathNames.Add(reg.Name); // 收集區域名稱 + nextId = reg.ParentUuid; // 假設你的欄位叫 ParentUuid,請依實際欄位修改 + } + + // 反轉順序讓它變成:[大區域, 中區域, 小區域] + pathNames.Reverse(); + return pathNames; + } + // 3. 步驟 A:純資料庫查詢 (只撈取需要的資料,不呼叫 C# 函式) + var dbBeds = _db.RegionRoomBed + .Where(b => !busyBedUuids.Contains(b.Uuid)) // 建議過濾條件放在 ToList 之前,減少傳輸資料量 + .Select(b => new + { + b.Name, + b.Uuid, + b.StatusCode, + b.IsActive, + b.RoomUuid, + b.Limits, + // 直接在 SQL 層級把 Room 撈出來 + Room = _db.Room + .Where(r => r.Uuid == b.RoomUuid) + .Select(r => new + { + r.Name, + r.RegionUuid + }) + .FirstOrDefault() + }) + .AsEnumerable(); // 💡 關鍵:從這裡開始切換到記憶體(LINQ to Objects) + + // 4. 步驟 B:在記憶體中加工,呼叫 GetRegionPathNames + var availableBeds = dbBeds.Select(b => new + { + b.Name, + b.Uuid, + b.StatusCode, + b.IsActive, + b.RoomUuid, + b.Limits, + // 💡 此時已經在記憶體中,可以安全地呼叫 C# 函式 + RegionPath = GetRegionPathNames(b.Room?.RegionUuid), + Room = b.Room == null ? null : new + { + b.Room.Name, + b.Room.RegionUuid, + // 從你已經載入的字典中直接抓 RegionName,省去在 SQL 裡面重複 Join Region 表 + + // 💡 修正關鍵:用 is 語法自動拆箱並安全檢查,完美避開 ?? 的型態限制 + RegionName = b.Room.RegionUuid is Guid rId && regionDict.TryGetValue(rId, out var reg) + ? reg.Name + : null + } + }).ToList(); + // 空閒床位 = 所有床位 - 忙碌床位 + //var availableBeds = _db.RegionRoomBed.Select(b=>new { + // b.Name ,b.Uuid,b.StatusCode,b.IsActive, + // b.RoomUuid,b.Limits, + // resion = GetRegionPathNames(b.RoomUuid), + // Room =_db.Room.Where(r=>r.Uuid==b.RoomUuid). + // Select(r=>new { r.Name,r.RegionUuid, + // RegionName=_db.Region.Where(g=>g.Uuid==r.RegionUuid). + // Select(g=>g.Name).FirstOrDefault()}).FirstOrDefault(), + + //}) + // .Where(b => !busyBedUuids.Contains(b.Uuid)) + // .ToList(); + - return Ok(new { availableBeds }); } diff --git a/web/admin/guadan/apply.vue b/web/admin/guadan/apply.vue index 93a7b3c..abb6a23 100644 --- a/web/admin/guadan/apply.vue +++ b/web/admin/guadan/apply.vue @@ -118,7 +118,7 @@ - 特殊需求: {{ item.specialNeeds }} + 特殊需求: {{ getSpecialNeedsText(item.specialNeeds) }} mdi-alert-circle-outline @@ -138,6 +138,10 @@ + + mdi-trash + 刪除 + mdi-cancel 取消預訂 @@ -197,18 +201,36 @@ {{ room.name }} (剩餘: {{ roomAvailableBedsCount(room) }}) - + - - {{ bed.name }} - mdi-check-circle - + @@ -277,7 +299,7 @@ - + @@ -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); }, diff --git a/web/admin/guadan/edit.vue b/web/admin/guadan/edit.vue new file mode 100644 index 0000000..7ec6ca0 --- /dev/null +++ b/web/admin/guadan/edit.vue @@ -0,0 +1,921 @@ + + + + + \ No newline at end of file diff --git a/web/admin/guadan/index.aspx b/web/admin/guadan/index.aspx index 08c47fc..1118ff2 100644 --- a/web/admin/guadan/index.aspx +++ b/web/admin/guadan/index.aspx @@ -49,23 +49,56 @@ mdi-close 掛單作業 - <%-- - - - Save - - --%> - <%--Cafe Badilico--%> + + + + + + + mdi-close + + 掛單作業 + + + + + + + + + + + + + mdi-close + + 掛單作業 + + + + + + @@ -82,17 +115,22 @@ data() { return { dialog: false, + viewdialog: false, + editdialog: false, notifications: false, sound: true, widgets: false, currentView: 'main-component', twoData: {}, + formData: null, }; }, components: { 'main-component': httpVueLoader('./main.vue'), 'apply-component': httpVueLoader('./apply.vue'), + 'view-component': httpVueLoader('./view.vue'), + 'edit-component': httpVueLoader('./edit.vue'), }, watch: { options: { @@ -110,9 +148,15 @@ console.log("go to new"); this.dialog = true; } else if (item.action === "view") { - console.log("go to view"); + console.log("go to view", item.item); + this.formData = item.item.uuid; + //Object.assign(this.formData , item.item); + this.viewdialog = true; } else if (item.action === "edit") { + //修改只能改主檔,明細要個別新增或個別修改 console.log("go to edit"); + this.formData = item.item.uuid; + this.editdialog = true; } } }, diff --git a/web/admin/guadan/main.vue b/web/admin/guadan/main.vue index 32a5ba9..5830876 100644 --- a/web/admin/guadan/main.vue +++ b/web/admin/guadan/main.vue @@ -2,7 +2,7 @@
@@ -26,10 +26,10 @@ :item-class="item => item.is_timeout ? 'row-timeout' : ''" hide-default-footer> + + + + \ No newline at end of file diff --git a/web/admin/region/index.aspx b/web/admin/region/index.aspx index b4c85d0..9689a90 100644 --- a/web/admin/region/index.aspx +++ b/web/admin/region/index.aspx @@ -213,6 +213,13 @@ :headers="room_bed.bed_headers" :items ="room_bed.bed_items" > + + @@ -257,7 +264,20 @@
- + +
+ +
+ + +
+
@@ -547,9 +567,15 @@ { text: '床位編號', value: 'uuid' }, { text: '床位名稱', value: 'name' }, { text: '床位狀態', value: 'statuscode' }, + { text: '設定條件', value: 'limits' }, // 可選:在表格中顯示條件 { text: '是否啟用', value: 'isactive' }, { text: '', value: 'action' }, ], + // 1. 新增條件來源選單 (可經由 API 載入,這裡預設填入您提供的 JSON) + bed_conditions: [ + { "paramNo": "01", "paramName": "需要輪椅", "remark": "" }, + { "paramNo": "02", "paramName": "需要拐杖", "remark": "" } + ], newBedForm: { uuid: null, RegionUuid: null, @@ -557,13 +583,37 @@ statuscode: null, IsActive: true, Gender: null, + Limits:[] }, showBedModal: false, bed_status: [], //床位狀態列表 + //bed_conditions:[] } }; }, methods: { + loadBedConditions() { + this.room_bed.bed_conditions = 0; // 清空陣列 + let self = this; + let data = []; + axios.post(HTTP_HOST + 'api/guadan/getGuadanLimit').then((res) => { + + if (res.status == 200) { + res.data.forEach(item => { + console.log('掛單條件', item); + }) + this.room_bed.bed_conditions = res.data; + } + }) + }, + getConditionNames(conditionIds) { + let condition = conditionIds.split(","); + if (!condition || !Array.isArray(condition)) return ''; + return this.room_bed.bed_conditions + .filter(c => condition.includes(c.paramNo)) + .map(c => c.paramName) + .join('、'); + }, closeBedInUsedModal() { this.bed_is_used_modal = false; this.bed_is_used_modal_message = null; @@ -759,6 +809,7 @@ statuscode: "101", IsActive: true, Gender: this.currentSelectRoom.gender, // 不設預設值,強制選擇 + Limits: [] // 重置為空陣列 }; this.room_bed.showBedModal = true; }, @@ -834,13 +885,24 @@ statuscode: bed.statuscode, IsActive: bed.isactive, Gender: bed.gender, + // 確保將後端回傳的條件清單帶入(如果是字串或陣列,建議統一轉為陣列) + Limits: bed.limits ? [...bed.limits] : [] }; this.room_bed.showBedModal = true; }, async saveEditBed() { try { - await axios.post(HTTP_HOST + 'api/region/bed/update', this.room_bed.newBedForm); + let data= { + uuid: this.room_bed.newBedForm.uuid, + RoomUuid: this.room_bed.newBedForm.RoomUuid, + Name: this.room_bed.newBedForm.Name, + statuscode: this.room_bed.newBedForm.statuscode, + IsActive: this.room_bed.newBedForm.IsActive, + Gender: this.room_bed.newBedForm.Gender, + Limits: this.room_bed.newBedForm.Limits.join(',') // 假設後端接受逗號分隔的字串 + }; + await axios.post(HTTP_HOST + 'api/region/bed/update', data); this.room_bed.showBedModal = false; const updated = this.room_bed.newBedForm; @@ -852,7 +914,8 @@ roomUuid: updated.RoomUuid, name: updated.Name, statuscode: updated.statuscode, - isactive: updated.IsActive + isactive: updated.IsActive, + limits: updated.Limits }); } @@ -1015,6 +1078,7 @@ } }, mounted() { + this.loadBedConditions(); this.loadRegions(); this.loadRegionType(); this.getBedStatus();