using DocumentFormat.OpenXml.Drawing; using Model; using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime; using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web; using System.Web.Http; /// /// guadanOrderGuest 的摘要描述 /// [ezAuthorize] public class guadanOrderGuestController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); [HttpGet] [Route("api/guadanorderguest/get")] public async Task Get() { var data = await _db.GuaDanOrderGuest.ToListAsync(); return Ok(data); } [HttpGet] [Route("api/guadanorderguest/getbyorderno")] public async Task getByOrderNo(string orderNo) { // 先查資料庫,不做格式化 var qry = await _db.GuaDanOrderGuest .Where(a => a.GuaDanOrderNo == orderNo && a.IsDeleted == false && a.RegionRoomBedStatus.Code != "404") .ToListAsync(); // 拉到記憶體後再處理日期 var data = qry.Select(a => new guadan_order_guest_display_dto { Uuid = a.Uuid, name = null, followerNum = a.FollowerNum, roomUuid = a.RoomUuid, bedUuid = a.BedUuid, checkinat = a.CheckInAt.HasValue ? a.CheckInAt.Value.ToString("yyyy-MM-dd") : null, checkoutat = a.CheckOutAt.HasValue ? a.CheckOutAt.Value.ToString("yyyy-MM-dd") : null, phone = null, roomName = a.Room.Name, bedName = a.RegionRoomBed.Name, orderNo = a.GuaDanOrderNo, follower = a.followers == null ? null : new FollowerDto { num = a.followers.num, u_name = a.followers.u_name }, statuscode = a.StatusCode, statusName = a.RegionRoomBedStatus?.Name, }).ToList(); return Ok(data); } [HttpPost] [Route("api/guadanorderguest/create")] public async Task create([FromBody] guadan_order_guest_dto model) { if (model == null) return BadRequest(""); /*if(model.statuscode == null) { return BadRequest("狀態不能為空"); }*/ // 驗證床位與蓮友 var bed = _db.RegionRoomBed.Find(model.bedUuid.Value); if (model.followerNum.HasValue && model.bedUuid.HasValue) { var follower = _db.followers.Find(model.followerNum.Value); if (bed == null || follower == null) return BadRequest("床位或蓮友不存在"); bool isMaleFollower; if (follower.sex == "男眾") isMaleFollower = true; else if (follower.sex == "女眾") isMaleFollower = false; else return BadRequest("蓮友性別未知"); if (bed.Gender != isMaleFollower) return BadRequest("床位性別與蓮友性別不同"); } if (!model.bedUuid.HasValue) return BadRequest("床位 UUID 不能為空"); if (!model.checkInAt.HasValue) return BadRequest("入住時間不能為空"); // 長期占用處理:checkOutAt 可為 null DateTime? checkOut = model.checkOutAt.Value.Date; if (checkOut.HasValue && model.checkInAt > checkOut) return BadRequest("掛單結束時間不能再開始時間之前"); if (model.checkInAt == model.checkOutAt) { return BadRequest("掛單結束時間和開始時間不能是同一天"); } // 檢查床位可用性 var bedIsCanUse = await RegionAndRoomAndBedSchedule.IsBedAvailableAsync( _db, model.bedUuid.Value, model.checkInAt.Value.Date, checkOut ); if (!bedIsCanUse) return BadRequest("床位在該時間段內已被占用"); if (model.followerNum.HasValue) { if (_db.GuaDanOrderGuest.Any(a => a.FollowerNum == model.followerNum && a.GuaDanOrderNo == model.orderNo && a.StatusCode != "404" )) return BadRequest("該蓮友已經在該掛單中"); } //建立訂單的的時候,狀態只能是401或者402 var targetStatus = _db.RegionRoomBedStatus.Where(a => a.Code == GuaDanOrderGuest.STATUS_BOOKED).FirstOrDefault(); if (targetStatus == null) { return Content(HttpStatusCode.PreconditionFailed, "找不到目標狀態,請先建立對應狀態"); } var guest = new GuaDanOrderGuest { GuaDanOrderNo = model.orderNo, FollowerNum = model.followerNum, RoomUuid = model.roomUuid, BedUuid = model.bedUuid, CheckInAt = model.checkInAt?.Date, CheckOutAt = checkOut, Uuid = Guid.NewGuid(), StatusCode = GuaDanOrderGuest.STATUS_BOOKED, }; _db.GuaDanOrderGuest.Add(guest); await _db.SaveChangesAsync(); // 生成每日排程 if (checkOut.HasValue) { int totalDays = (checkOut.Value - model.checkInAt.Value.Date).Days; for (int i = 0; i < totalDays; i++) { var scheduleDate = model.checkInAt.Value.Date.AddDays(i); var schedul = new RegionAndRoomAndBedSchedule { Title = "掛單", Description = "床位掛單", ScheduleDate = scheduleDate, IsDeleted = false, IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, Uuid = Guid.NewGuid(), GuaDanOrderGuestUuid = guest.Uuid, }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } } else { // 長期占用,ScheduleDate = null var schedul = new RegionAndRoomAndBedSchedule { Title = "掛單", Description = "床位掛單(長期占用)", ScheduleDate = null, IsDeleted = false, IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, Uuid = Guid.NewGuid(), GuaDanOrderGuestUuid = guest.Uuid }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } await _db.SaveChangesAsync(); return Ok(); } [HttpPost] [Route("api/guadanorderguest/update")] public async Task update([FromBody] guadan_order_guest_dto model) { if (model == null) return BadRequest(""); // 驗證床位與蓮友 if (model.followerNum.HasValue && model.bedUuid.HasValue) { var bed = _db.RegionRoomBed.Find(model.bedUuid.Value); var follower = _db.followers.Find(model.followerNum.Value); if (bed == null || follower == null) return BadRequest("床位或蓮友不存在"); bool isMaleFollower; if (follower.sex == "男眾") isMaleFollower = true; else if (follower.sex == "女眾") isMaleFollower = false; else return BadRequest("蓮友性別未知"); if (bed.Gender != isMaleFollower) return BadRequest("床位性別與蓮友性別不同"); } if (!model.bedUuid.HasValue) return BadRequest("床位 UUID 不能為空"); if (!model.checkInAt.HasValue) return BadRequest("入住時間不能為空"); // 長期占用處理 DateTime? checkOut = model.checkOutAt?.Date; if (checkOut.HasValue && model.checkInAt > checkOut) return BadRequest("掛單結束時間不能再開始時間之前"); var guest = await _db.GuaDanOrderGuest.FindAsync(model.Uuid); if (guest == null) return BadRequest(); // 檢查床位可用性 var bedIsCanUse = await RegionAndRoomAndBedSchedule.IsBedAvailableAsync( _db, model.bedUuid.Value, model.checkInAt.Value.Date, checkOut ); if (!bedIsCanUse && guest.BedUuid != model.bedUuid) return BadRequest("床位在該時間段內已被占用"); if (model.followerNum.HasValue) { bool exists = await _db.GuaDanOrderGuest .Where(a => a.FollowerNum == model.followerNum && a.GuaDanOrderNo == model.orderNo && a.Uuid != model.Uuid) .AnyAsync(); if (exists) return BadRequest("該蓮友已經在該掛單中"); } /*var targetStatus = _db.RegionRoomBedStatus.Find(model.statuscode); if (targetStatus == null) { return BadRequest("目標狀態不存在"); } if(!StatusTransitionManager.CanTransition(guest.StatusCode,targetStatus.Code)) { return BadRequest("狀態的變化不合法"); }*/ // 更新掛單基本資料 guest.FollowerNum = model.followerNum; guest.RoomUuid = model.roomUuid; guest.BedUuid = model.bedUuid; guest.CheckInAt = model.checkInAt?.Date; guest.CheckOutAt = checkOut; //guest.StatusCode = model.statuscode; //更新的時候不能更新狀態,狀態都用單獨的操作api控制 // 刪除原有每日排程 var oldSchedules = _db.RegionAndRoomAndBedSchedule .Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo && s.TargetUuid == guest.BedUuid) .ToList(); _db.RegionAndRoomAndBedSchedule.RemoveRange(oldSchedules); // 重新生成每日排程 if (checkOut.HasValue) { int totalDays = (checkOut.Value - model.checkInAt.Value.Date).Days; for (int i = 0; i < totalDays; i++) { var date = model.checkInAt.Value.Date.AddDays(i); var schedul = new RegionAndRoomAndBedSchedule { Title = "掛單", Description = "床位掛單", ScheduleDate = date, IsDeleted = false, IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, Uuid = Guid.NewGuid(), GuaDanOrderGuestUuid = guest.Uuid, }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } } else { // 長期占用 var schedul = new RegionAndRoomAndBedSchedule { Title = "掛單", Description = "床位掛單(長期占用)", ScheduleDate = null, IsDeleted = false, IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, Uuid = Guid.NewGuid(), GuaDanOrderGuestUuid = guest.Uuid, }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } await _db.SaveChangesAsync(); await _db.SaveChangesAsync(); return Ok(); } [HttpPost] [Route("api/guadanorderguest/xuzhu")] public async Task ExtendStay([FromBody] XuZhuModel model) { //續住方法 if (model == null) return BadRequest("請求數據為空"); if (model.GuestUuid == Guid.Empty || model.GuestBedUuid == Guid.Empty) return BadRequest("GuestUuid 或 GuestBedUuid 無效"); var guest = await _db.GuaDanOrderGuest.FindAsync(model.GuestUuid); if (guest == null) { return BadRequest("掛單不存在"); } if (guest.BedUuid != model.GuestBedUuid) { return BadRequest("床位不正確"); } var bedIsCanUse = await RegionAndRoomAndBedSchedule.IsBedAvailableAsync(_db, model.GuestBedUuid, model.CurrentCheckoutDate, model.NewCheckoutDate); if (!bedIsCanUse) { return BadRequest("該床位在續住時間段內被預定,無法續住"); } var newStartDate = model.CurrentCheckoutDate.Date; var newEndDate = model.NewCheckoutDate.Date.AddDays(-1); if (newEndDate < newStartDate) return BadRequest("續住日期區間無效"); for (var date = newStartDate; date <= newEndDate; date = date.AddDays(1)) { var newSchedule = new RegionAndRoomAndBedSchedule { GuaDanOrderNo = guest.GuaDanOrderNo, Uuid = Guid.NewGuid(), TargetUuid = model.GuestBedUuid, GuaDanOrderGuestUuid = model.GuestUuid, ScheduleDate = date, Title = "續住掛單", // 一天一條,開始和結束是同一天 Description = "續住掛單", UseType = 30, CreatedAt = DateTime.UtcNow }; _db.RegionAndRoomAndBedSchedule.Add(newSchedule); } guest.CheckOutAt = model.NewCheckoutDate.Date; await _db.SaveChangesAsync(); // 保存資料庫操作 return Ok(new { message = "續住成功" }); } [HttpPost] [Route("api/guadanorderguest/cancel")] public async Task CancelGuadanGuest([FromUri] Guid uuid) { var guest = await _db.GuaDanOrderGuest.FindAsync(uuid); if (guest == null) return NotFound(); if (guest.StatusCode == "404") return BadRequest("該掛單已取消,無需再取消"); if (!StatusTransitionManager.CanTransition(guest.StatusCode, "404")) { return BadRequest("當前狀態不能取消"); } var cancelStatus = await _db.RegionRoomBedStatus .FirstOrDefaultAsync(a => a.Code == "404"); if (cancelStatus == null) return Content(HttpStatusCode.PreconditionFailed, "找不到取消狀態(Code=404),請先建立對應狀態"); //把狀態設置為取消 using (var tx = _db.Database.BeginTransaction()) { try { // 1) 更新 guest guest.StatusCode = "404"; // 2) 取消相關排程(常見做法:只取消未來&未取消的) var today = DateTime.Today; var schedules = await _db.RegionAndRoomAndBedSchedule .Where(s => s.GuaDanOrderGuestUuid == guest.Uuid && s.IsCancel == false //&& s.ScheduleDate >= today ) // ✅ 只取消今天與未來的,能取消就代表未入住, // 未入住就要全部取消,如果是入住後提前退房, // 就要取消未來的,取消未來的時候要注意今日是否包含的問題 .ToListAsync(); foreach (var s in schedules) { s.IsCancel = true; } // 3) 釋放占用資源(若有床位/房間占用紀錄) if (guest.BedUuid != null) { // 先抓到目標狀態 var freeStatus = await _db.RegionRoomBedStatus .FirstOrDefaultAsync(a => a.Code == "101"); if (freeStatus == null) return Content(HttpStatusCode.PreconditionFailed, "找不到床位狀態 Code=101"); if (guest.RegionRoomBed != null) { guest.RegionRoomBed.StatusCode = freeStatus.Code; } } await _db.SaveChangesAsync(); tx.Commit(); return Ok(new { message = "取消成功", guestUuid = guest.Uuid, canceledSchedules = schedules.Count }); } catch (DbUpdateConcurrencyException) { tx.Rollback(); return StatusCode(HttpStatusCode.PreconditionFailed); // 或自訂訊息 } catch (Exception ex) { tx.Rollback(); return InternalServerError(ex); } } } [HttpPost] [Route("api/guadanorderguest/checkout")] public IHttpActionResult CheckoutGuadanOrderGuest(Guid uuid) { DbContextTransaction transaction = null; try { transaction = _db.Database.BeginTransaction(); // 開啟事務 // 1️⃣ 取得該筆掛單 var guest = _db.GuaDanOrderGuest .Where(a => a.Uuid == uuid) .Where(a => !a.IsDeleted && a.StatusCode != "404") .FirstOrDefault(); if (guest == null) return NotFound(); // 2️⃣ 標記為已退房 var targetStatus = _db.RegionRoomBedStatus .Where(a => a.Code == "403") .FirstOrDefault(); if (targetStatus == null) return Content(HttpStatusCode.PreconditionFailed, "找不到退房狀態(Code=403),請先建立對應狀態"); if (!StatusTransitionManager.CanTransition(guest.StatusCode, targetStatus.Code)) return BadRequest("掛單狀態轉換不對"); if (!StatusTransitionManager.CanTransition(guest.RegionRoomBed.StatusCode, "101")) return BadRequest("床位掛單狀態轉換不對"); guest.StatusCode = targetStatus.Code; guest.RegionRoomBed.StatusCode = "101"; //更新未來排程為取消 var latestCheckoutStr = _db.GuadanTimeSetting .Select(a => a.LatestCheckOut) // 字串 "HH:mm" .FirstOrDefault(); TimeSpan? latestCheckoutTime = null; if (!string.IsNullOrEmpty(latestCheckoutStr)) { // 嘗試解析字串 if (TimeSpan.TryParse(latestCheckoutStr, out var ts)) { latestCheckoutTime = ts; } } var futureSchedules = _db.RegionAndRoomAndBedSchedule .Where(s => s.GuaDanOrderGuestUuid == guest.Uuid); if (latestCheckoutTime == null || DateTime.Now.TimeOfDay > latestCheckoutTime) { // 包含今天 futureSchedules = futureSchedules.Where(s => s.ScheduleDate > DateTime.Today); } else { // 不包含今天 futureSchedules = futureSchedules.Where(s => s.ScheduleDate >= DateTime.Today); } foreach (var schedule in futureSchedules) { schedule.IsCancel = true; } // 4️⃣ 保存所有變更 _db.SaveChanges(); // 5️⃣ 提交事務 transaction.Commit(); return Ok(new { message = "退房完成", guestUuid = guest.Uuid }); } catch (Exception ex) { if (transaction != null) transaction.Rollback(); // 回滾事務 return InternalServerError(ex); } finally { if (transaction != null) transaction.Dispose(); } } [HttpPost] [Route("api/guadanorderguest/checkin")] public IHttpActionResult CheckinGuadanGuest([FromUri] Guid uuid) { if (uuid == Guid.Empty) return BadRequest("uuid不能為空"); // 獲取掛單客人 var guest = _db.GuaDanOrderGuest .Include(g => g.RegionRoomBedStatus) // 包含導航屬性 .FirstOrDefault(g => g.Uuid == uuid); if (guest == null) return NotFound(); string currentStatus = guest.StatusCode; // 判斷狀態流轉是否合法 if (!StatusTransitionManager.CanTransition(currentStatus, GuaDanOrderGuest.STATUS_CHECKED_IN)) { return BadRequest("當前狀態不允許入住"); } // ---------- 新增:檢查今天是否在排程表 ---------- var today = DateTime.Today; bool hasScheduleToday = _db.RegionAndRoomAndBedSchedule .Any(s => s.GuaDanOrderGuestUuid == guest.Uuid && s.ScheduleDate == today); if (!hasScheduleToday) { return BadRequest("不在入住時間段內,無法入住"); } try { // 更新狀態 guest.StatusCode = GuaDanOrderGuest.STATUS_CHECKED_IN; // 如果需要,更新床位狀態,比如變為占用 if (guest.BedUuid != null) { var bed = _db.RegionRoomBed.FirstOrDefault(b => b.Uuid == guest.BedUuid); if (bed == null) { return BadRequest("入住床位不存在"); } if (StatusTransitionManager.CanTransition(bed.StatusCode, "102")) // 102 = 占用 { bed.StatusCode = "102"; } else { return BadRequest($"當前床位狀態:{bed.RegionRoomBedStatus.Name} 不能入住"); } } else if (guest.BedUuid == null) { return BadRequest("入住床位不存在"); } _db.SaveChanges(); return Ok(new { message = "入住成功", statusCode = guest.StatusCode }); } catch (Exception ex) { return InternalServerError(ex); } } public class guadan_order_guest_dto { public Guid? Uuid { get; set; } public int? followerNum { get; set; } public string orderNo { get; set; } public Guid? roomUuid { get; set; } public Guid? bedUuid { get; set; } public DateTime? checkInAt { get; set; } public DateTime? checkOutAt { get; set; } public string statuscode { get; set; } } public class guadan_order_guest_display_dto { public Guid? Uuid { get; set; } public int? followerNum { get; set; } public string orderNo { get; set; } public string name { get; set; } public Guid? roomUuid { get; set; } public Guid? bedUuid { get; set; } public string checkinat { get; set; } public string checkoutat { get; set; } public int? gender { get; set; } public string statuscode { get; set; } public string statusName { get; set; } public string phone { get; set; } public string note { get; set; } public string roomName { get; set; } public string bedName { get; set; } public bool iscancel { get; set; } public FollowerDto follower { get; set; } } public class FollowerDto { public int num { get; set; } public string u_name { get; set; } } public class XuZhuModel { public Guid GuestUuid { get; set; } // 不可為空 public Guid GuestBedUuid { get; set; } // 不可為空 public DateTime CurrentCheckoutDate { get; set; } // 當前退房時間 public DateTime NewCheckoutDate { get; set; } // 新退房時間 } }