diff --git a/web/App_Code/Model/Partial/GuaDanOrderGuest.cs b/web/App_Code/Model/Partial/GuaDanOrderGuest.cs new file mode 100644 index 0000000..5e96bbf --- /dev/null +++ b/web/App_Code/Model/Partial/GuaDanOrderGuest.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Web; + +/// +/// GuaDanOrderGuest 的摘要描述 +/// +namespace Model +{ + public partial class GuaDanOrderGuest + { + // 状态常量定义 + public const string STATUS_BOOKED = "401"; // 预订成功 + public const string STATUS_CHECKED_IN = "402"; // 已入住 + public const string STATUS_CHECKED_OUT = "403"; // 已退房 + public const string STATUS_CANCELLED = "404"; // 已取消 + + public static bool IsStatusTransitionValid(ezEntities db, string targetStatus, Guid guestId) + { + // 获取当前客人对象 + GuaDanOrderGuest currentGuest = db.GuaDanOrderGuest.Find(guestId); + if (currentGuest == null) + { + // 如果没有客人对象,只能创建新的预订 + return targetStatus == STATUS_BOOKED; + } + + // 安全获取当前状态(处理null情况) + string currentStatus = currentGuest.RegionRoomBedStatus?.Code; + + // 如果当前状态为空,只能进入预订成功状态 + if (string.IsNullOrEmpty(currentStatus)) + { + return targetStatus == STATUS_BOOKED; + } + + // 定义有效的状态转换规则 + var validTransitions = new Dictionary> + { + { STATUS_BOOKED, new List { STATUS_CHECKED_IN, STATUS_CANCELLED } }, + { STATUS_CHECKED_IN, new List { STATUS_CHECKED_OUT } }, + { STATUS_CHECKED_OUT, new List { } }, // 终态,不能再转换 + { STATUS_CANCELLED, new List { } } // 终态,不能再转换 + }; + + // 检查转换是否有效 + if (validTransitions.ContainsKey(currentStatus)) + { + return validTransitions[currentStatus].Contains(targetStatus); + } + + return false; // 未知的当前状态 + } + } +} diff --git a/web/App_Code/Model/Partial/RegionAndRoomAndBedSchedule.cs b/web/App_Code/Model/Partial/RegionAndRoomAndBedSchedule.cs index 6d49ec0..ab01fb7 100644 --- a/web/App_Code/Model/Partial/RegionAndRoomAndBedSchedule.cs +++ b/web/App_Code/Model/Partial/RegionAndRoomAndBedSchedule.cs @@ -58,7 +58,7 @@ namespace Model // 找出在日期範圍內被占用的床位 Uuid(包括長期占用 ScheduleDate = null) var busyBedUuidsQuery = db.RegionAndRoomAndBedSchedule - .Where(s => s.IsDeleted == false && s.IsActive + .Where(s => s.IsDeleted == false && !s.IsCancel && (s.ScheduleDate == null || (end.HasValue && s.ScheduleDate >= start @@ -79,25 +79,28 @@ namespace Model public static async Task IsBedAvailableAsync(ezEntities db, Guid targetUuid, DateTime start, DateTime? end) { // 如果 end 為 null,表示長期占用,直接判斷是否已有長期占用 + //不包含结束时间那一天 if (end == null) { var hasLongTerm = await db.RegionAndRoomAndBedSchedule .AnyAsync(s => s.IsDeleted == false - && s.IsActive + && !s.IsCancel && s.TargetUuid == targetUuid && s.ScheduleDate == null); return !hasLongTerm; } // 短期占用,查詢每日排程中有無衝突 - var totalDays = (end.Value.Date - start.Date).Days + 1; + var totalDays = (end.Value.Date - start.Date).Days; for (int i = 0; i < totalDays; i++) { var date = start.Date.AddDays(i); var conflict = await db.RegionAndRoomAndBedSchedule + .Where(s => s.GuaDanOrderGuest.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED) + .Where(s => s.GuaDanOrderGuest.StatusCode != GuaDanOrderGuest.STATUS_CHECKED_OUT) .AnyAsync(s => s.IsDeleted == false - && s.IsActive + && !s.IsCancel && s.TargetUuid == targetUuid && s.ScheduleDate == date); @@ -114,7 +117,10 @@ namespace Model // 找出所有在日期範圍內被占用的床位 var busyBedUuids = await db.RegionAndRoomAndBedSchedule - .Where(s => s.IsDeleted == false && s.IsActive + .Where(s => s.GuaDanOrderGuest.StatusCode != "403") + .Where(s => s.GuaDanOrderGuest.StatusCode != "404") + .Where(a => a.IsCancel == false) + .Where(s => s.IsDeleted == false && (s.ScheduleDate == null // 長期占用 || (s.ScheduleDate >= start && s.ScheduleDate <= end))) .Select(s => s.TargetUuid) diff --git a/web/App_Code/Model/Partial/RegionRoomBed.cs b/web/App_Code/Model/Partial/RegionRoomBed.cs index e31c7e4..3602d65 100644 --- a/web/App_Code/Model/Partial/RegionRoomBed.cs +++ b/web/App_Code/Model/Partial/RegionRoomBed.cs @@ -11,6 +11,9 @@ namespace Model { public partial class RegionRoomBed { + public const string STATUS_BED_FREE = "101"; // 空閒,可使用 + public const string STATUS_BED_OCCUPIED = "102"; // 已佔用 + public const string STATUS_BED_MAINTENANCE = "103"; // 維護中,不可使用 public bool IsAvailable() { //判断床位是否可用:自身是否启用 @@ -34,7 +37,9 @@ namespace Model // 如果資料庫 ScheduleDate 是 date 型別,本身沒有時間部分,可以直接比較 var conflict = _db.RegionAndRoomAndBedSchedule.Any(s => s.TargetUuid == this.Uuid && - s.IsActive && + s.GuaDanOrderGuest.StatusCode != "403" && + s.GuaDanOrderGuest.StatusCode != "404" && + !s.IsCancel && !s.IsDeleted && ( s.ScheduleDate == null || // 長期占用 @@ -46,5 +51,6 @@ namespace Model return !conflict; } + } } diff --git a/web/App_Code/Model/ViewModel/GuaDanOrderView.cs b/web/App_Code/Model/ViewModel/GuaDanOrderView.cs index dd77c32..bfb6177 100644 --- a/web/App_Code/Model/ViewModel/GuaDanOrderView.cs +++ b/web/App_Code/Model/ViewModel/GuaDanOrderView.cs @@ -19,5 +19,6 @@ public class GuaDanOrderView public string bookerName { get; set; } public string bookerPhone { get; set; } public int? bookerFollowerNum { get; set; } + public int? activityNum { get; set; } } \ No newline at end of file diff --git a/web/App_Code/StatusTransitionManager.cs b/web/App_Code/StatusTransitionManager.cs new file mode 100644 index 0000000..ceb58f3 --- /dev/null +++ b/web/App_Code/StatusTransitionManager.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace Model +{ + public static class StatusTransitionManager + { + private static readonly Dictionary> transitions = + new Dictionary> + { + // 掛單狀態 + { "401", new List { "402", "404" } }, + { "402", new List { "403" } }, + { "403", new List() }, + { "404", new List() }, + + // 床位狀態 + { "101", new List {"101", "102","103"} }, + { "102", new List { "101" } }, + { "103", new List { "101" } }, + }; + + public static bool CanTransition(string currentCode, string targetCode) + { + if (string.IsNullOrEmpty(currentCode)) + { + return targetCode == "401" || targetCode == "402" || targetCode == "101"; + } + if(string.IsNullOrEmpty(targetCode)) + { return false; } + return transitions.ContainsKey(currentCode) && + transitions[currentCode].Contains(targetCode); + } + } +} diff --git a/web/App_Code/api/guadanOrderController.cs b/web/App_Code/api/guadanOrderController.cs index 68f8917..dd07ca1 100644 --- a/web/App_Code/api/guadanOrderController.cs +++ b/web/App_Code/api/guadanOrderController.cs @@ -12,14 +12,16 @@ using static regionController; /// guadanOderController 的摘要描述 /// [ezAuthorize] -public class guadanOrderController: ApiController +public class guadanOrderController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); [HttpGet] [Route("api/guadan/list")] public async Task getGuadanList() { - var data = await _db.GuaDanOrder.OrderByDescending(b => b.CreatedAt) + var data = await _db.GuaDanOrder + .Where(a => a.IsCancel == false) + .OrderByDescending(b => b.CreatedAt) .Select(a => new { uuid = a.Uuid, @@ -30,7 +32,10 @@ public class guadanOrderController: ApiController updated_at = a.UpdatedAt, notes = a.Notes, bookerName = a.BookerName, - guest_count = _db.GuaDanOrderGuest.Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo).Count(), + guest_count = _db.GuaDanOrderGuest + .Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo && c.IsDeleted == false) + .Where(c => c.RegionRoomBedStatus.Code != GuaDanOrderGuest.STATUS_CANCELLED) + .Count(), }).ToListAsync(); return Ok(data); } @@ -38,7 +43,10 @@ public class guadanOrderController: ApiController [Route("api/guadan/getorderbyid")] public async Task getGuadanOrderById(string orderId) { - var order = await _db.GuaDanOrder.Where(a => a.GuaDanOrderNo == orderId).FirstOrDefaultAsync(); + var order = await _db.GuaDanOrder + .Where(a => a.GuaDanOrderNo == orderId) + .Where(a => a.IsCancel == false && a.IsDeleted == false) + .FirstOrDefaultAsync(); if (order == null) { return BadRequest("未找到对应订单"); @@ -46,7 +54,7 @@ public class guadanOrderController: ApiController var result = new { order.admin, - order.follower, + order.followers, StartDate = order.StartDate?.ToString("yyyy-MM-dd"), EndDate = order.EndDate?.ToString("yyyy-MM-dd"), order.CreateUser, @@ -59,6 +67,8 @@ public class guadanOrderController: ApiController order.BookerPhone, order.IsDeleted, order.Uuid, + order.ActivityNum, + }; return Ok(result); @@ -71,7 +81,7 @@ public class guadanOrderController: ApiController { return BadRequest("掛單資料不可為空"); } - if(model.Uuid.HasValue) + if (model.Uuid.HasValue) { return BadRequest("已存在对应挂单资料"); } @@ -99,6 +109,7 @@ public class guadanOrderController: ApiController BookerName = model.bookerName, BookerPhone = model.bookerPhone, Uuid = Guid.NewGuid(), + ActivityNum = model.activityNum, }; _db.GuaDanOrder.Add(guadanorder); await _db.SaveChangesAsync(); @@ -138,12 +149,13 @@ public class guadanOrderController: ApiController order.Notes = model.note; order.BookerName = model.bookerName; order.BookerPhone = model.bookerPhone; + order.ActivityNum = model.activityNum; await _db.SaveChangesAsync(); return Ok(model); } [HttpPost] - [Route("api/guadan/delete")] - public async Task deleteGuadanOrder([FromUri] Guid uuid) + [Route("api/guadan/cancel")] + public async Task CancelGuadanOrder([FromUri] Guid uuid) { var guadan = await _db.GuaDanOrder.FindAsync(uuid); if (guadan == null) @@ -158,31 +170,27 @@ public class guadanOrderController: ApiController var guadanGuests = await _db.GuaDanOrderGuest .Where(a => a.GuaDanOrderNo == guadan.GuaDanOrderNo) .ToListAsync(); - var scheduleIds = _db.RegionAndRoomAndBedSchedule - .Where(a => a.GuaDanOrderNo == guadan.GuaDanOrderNo) - .Where( b => b.IsActive == true) - .Select(c => c.GuaDanOrderNo) - .ToList(); if (guadanGuests.Any()) { - _db.GuaDanOrderGuest.RemoveRange(guadanGuests); + foreach (var guest in guadanGuests) + { + guest.StatusCode = "404"; + // 取消所有相關的排程 + if (guest.RegionAndRoomAndBedSchedule != null && guest.RegionAndRoomAndBedSchedule.Any()) + { + foreach (var schedule in guest.RegionAndRoomAndBedSchedule) + { + schedule.IsCancel = true; + } + } + } await _db.SaveChangesAsync(); } - - if (scheduleIds.Any()) - { - var schedules = await _db.RegionAndRoomAndBedSchedule - .Where(a => scheduleIds.Contains(a.GuaDanOrderNo)) - .ToListAsync(); - - if (schedules.Any()) - _db.RegionAndRoomAndBedSchedule.RemoveRange(schedules); - } - _db.GuaDanOrder.Remove(guadan); + guadan.IsCancel = true; await _db.SaveChangesAsync(); transaction.Commit(); - return Ok(new { message = "删除成功" }); + return Ok(new { message = "取消成功" }); } catch (Exception ex) { @@ -200,7 +208,6 @@ public class guadanOrderController: ApiController public int guest_id { get; set; } public DateTime start_date { get; set; } public DateTime? end_date { get; set; } - public Guid? statusUuid { get; set; } public int? create_user { get; set; } public DateTime created_at { get; set; } public DateTime updated_at { get; set; } diff --git a/web/App_Code/api/guadanOrderGuestController.cs b/web/App_Code/api/guadanOrderGuestController.cs index 86a26e0..2e9923a 100644 --- a/web/App_Code/api/guadanOrderGuestController.cs +++ b/web/App_Code/api/guadanOrderGuestController.cs @@ -1,8 +1,12 @@ -using Model; +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; @@ -11,7 +15,7 @@ using System.Web.Http; /// guadanOrderGuest 的摘要描述 /// [ezAuthorize] -public class guadanOrderGuestController: ApiController +public class guadanOrderGuestController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); [HttpGet] @@ -25,12 +29,12 @@ public class guadanOrderGuestController: ApiController [Route("api/guadanorderguest/getbyorderno")] public async Task getByOrderNo(string orderNo) { - // 先查数据库,不做格式化 + // 先查資料庫,不做格式化 var qry = await _db.GuaDanOrderGuest - .Where(a => a.GuaDanOrderNo == orderNo && a.IsDeleted == false) + .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, @@ -44,8 +48,12 @@ public class guadanOrderGuestController: ApiController roomName = a.Room.Name, bedName = a.RegionRoomBed.Name, orderNo = a.GuaDanOrderNo, - follower = a.follower, - statusUuid = a.statusUuid, + 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(); @@ -58,11 +66,14 @@ public class guadanOrderGuestController: ApiController { 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 bed = _db.RegionRoomBed.Find(model.bedUuid.Value); var follower = _db.followers.Find(model.followerNum.Value); if (bed == null || follower == null) @@ -81,16 +92,16 @@ public class guadanOrderGuestController: ApiController } if (!model.bedUuid.HasValue) - return BadRequest("床位 UUID 不能为空"); + return BadRequest("床位 UUID 不能為空"); if (!model.checkInAt.HasValue) - return BadRequest("入住时间不能为空"); + return BadRequest("入住時間不能為空"); // 長期占用處理:checkOutAt 可為 null DateTime? checkOut = model.checkOutAt.Value.Date; if (checkOut.HasValue && model.checkInAt > checkOut) return BadRequest("掛單結束時間不能再開始時間之前"); - if(model.checkInAt == model.checkOutAt) + if (model.checkInAt == model.checkOutAt) { return BadRequest("掛單結束時間和開始時間不能是同一天"); } @@ -103,8 +114,22 @@ public class guadanOrderGuestController: ApiController ); 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, @@ -114,14 +139,9 @@ public class guadanOrderGuestController: ApiController CheckInAt = model.checkInAt?.Date, CheckOutAt = checkOut, Uuid = Guid.NewGuid(), - statusUuid = model.statusUuid, + StatusCode = GuaDanOrderGuest.STATUS_BOOKED, }; - if (model.followerNum.HasValue) - { - if (_db.GuaDanOrderGuest.Any(a => a.FollowerNum == model.followerNum && a.GuaDanOrderNo == model.orderNo)) - return BadRequest("該蓮友已經在該掛單中"); - } _db.GuaDanOrderGuest.Add(guest); await _db.SaveChangesAsync(); @@ -140,12 +160,13 @@ public class guadanOrderGuestController: ApiController Description = "床位掛單", ScheduleDate = scheduleDate, IsDeleted = false, - IsActive = true, + IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, - Uuid = Guid.NewGuid() + Uuid = Guid.NewGuid(), + GuaDanOrderGuestUuid = guest.Uuid, }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } @@ -159,17 +180,17 @@ public class guadanOrderGuestController: ApiController Description = "床位掛單(長期占用)", ScheduleDate = null, IsDeleted = false, - IsActive = true, + IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, - Uuid = Guid.NewGuid() + Uuid = Guid.NewGuid(), + GuaDanOrderGuestUuid = guest.Uuid }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } - - await _db.SaveChangesAsync(); + await _db.SaveChangesAsync(); await _db.SaveChangesAsync(); return Ok(); @@ -201,10 +222,10 @@ public class guadanOrderGuestController: ApiController } if (!model.bedUuid.HasValue) - return BadRequest("床位 UUID 不能为空"); + return BadRequest("床位 UUID 不能為空"); if (!model.checkInAt.HasValue) - return BadRequest("入住时间不能为空"); + return BadRequest("入住時間不能為空"); // 長期占用處理 DateTime? checkOut = model.checkOutAt?.Date; @@ -234,18 +255,27 @@ public class guadanOrderGuestController: ApiController .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.statusUuid = model.statusUuid; + //guest.StatusCode = model.statuscode; + //更新的時候不能更新狀態,狀態都用單獨的操作api控制 // 刪除原有每日排程 var oldSchedules = _db.RegionAndRoomAndBedSchedule - .Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo) + .Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo && s.TargetUuid == guest.BedUuid) .ToList(); _db.RegionAndRoomAndBedSchedule.RemoveRange(oldSchedules); @@ -262,12 +292,13 @@ public class guadanOrderGuestController: ApiController Description = "床位掛單", ScheduleDate = date, IsDeleted = false, - IsActive = true, + IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, - Uuid = Guid.NewGuid() + Uuid = Guid.NewGuid(), + GuaDanOrderGuestUuid = guest.Uuid, }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } @@ -281,12 +312,13 @@ public class guadanOrderGuestController: ApiController Description = "床位掛單(長期占用)", ScheduleDate = null, IsDeleted = false, - IsActive = true, + IsCancel = false, TargetUuid = guest.BedUuid, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo, - Uuid = Guid.NewGuid() + Uuid = Guid.NewGuid(), + GuaDanOrderGuestUuid = guest.Uuid, }; _db.RegionAndRoomAndBedSchedule.Add(schedul); } @@ -298,25 +330,231 @@ public class guadanOrderGuestController: ApiController } [HttpPost] - [Route("api/guadanorderguest/delete")] - public async Task deleteGuadanGuest([FromUri] Guid uuid) + [Route("api/guadanorderguest/cancel")] + public async Task CancelGuadanGuest([FromUri] Guid uuid) { var guest = await _db.GuaDanOrderGuest.FindAsync(uuid); if (guest == null) - return BadRequest("未找到指定挂单资料"); + return NotFound(); - // 删除所有与该 guest 相关的排程(每日排程或長期占用) - var schedules = _db.RegionAndRoomAndBedSchedule - .Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo) - .ToList(); + if (guest.StatusCode == "404") + return BadRequest("該掛單已取消,無需再取消"); - if (schedules.Any()) - _db.RegionAndRoomAndBedSchedule.RemoveRange(schedules); + 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"; - _db.GuaDanOrderGuest.Remove(guest); - await _db.SaveChangesAsync(); + // 2) 取消相關排程(常見做法:只取消未來&未取消的) + var today = DateTime.Today; - return Ok(new { message = "删除成功" }); + 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 && StatusTransitionManager.CanTransition(bed.StatusCode, "102")) // 102 = 占用 + { + bed.StatusCode = "102"; + } + } + + _db.SaveChanges(); + + return Ok(new { message = "入住成功", statusCode = guest.StatusCode }); + } + catch (Exception ex) + { + return InternalServerError(ex); + } } public class guadan_order_guest_dto @@ -328,7 +566,7 @@ public class guadanOrderGuestController: ApiController public Guid? bedUuid { get; set; } public DateTime? checkInAt { get; set; } public DateTime? checkOutAt { get; set; } - public Guid? statusUuid { get; set; } + public string statuscode { get; set; } } public class guadan_order_guest_display_dto { @@ -337,17 +575,23 @@ public class guadanOrderGuestController: ApiController 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 Guid? statusUuid { 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 follower follower { 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; } } } \ No newline at end of file diff --git a/web/App_Code/api/guadanStatisticsController.cs b/web/App_Code/api/guadanStatisticsController.cs index 6d29e5c..0832389 100644 --- a/web/App_Code/api/guadanStatisticsController.cs +++ b/web/App_Code/api/guadanStatisticsController.cs @@ -33,13 +33,13 @@ public class guadanStatisticsController: ApiController var guadanTotalCount = await _db.GuaDanOrder.Where(a => a.IsDeleted == false).CountAsync(); var guadanPeopleTotal = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false).CountAsync(); - var guadanPeopleMale = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false && a.follower.sex == "男眾").CountAsync(); - var guadanPeopleFemale = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false && a.follower.sex == "女眾").CountAsync(); + var guadanPeopleMale = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false && a.followers.sex == "男眾").CountAsync(); + var guadanPeopleFemale = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false && a.followers.sex == "女眾").CountAsync(); dynamic bedCounts = await RegionAndRoomAndBedSchedule.GetAvailableBedCountsAsync(_db, DateTime.Now, DateTime.Now); var guadanCurrentCount = await _db.GuaDanOrder.Where(a => now < a.EndDate).CountAsync(); var guadanPeopleCurrent = await _db.GuaDanOrderGuest.Where( a => a.CheckOutAt > now).CountAsync(); - var guadanPeopleCurrentMale = await _db.GuaDanOrderGuest.Where(a => a.CheckOutAt > now && a.follower.sex == "男眾").CountAsync(); - var guadanPeopleCurrentFemale = await _db.GuaDanOrderGuest.Where(a => a.CheckOutAt > now && a.follower.sex == "女眾").CountAsync(); + var guadanPeopleCurrentMale = await _db.GuaDanOrderGuest.Where(a => a.CheckOutAt > now && a.followers.sex == "男眾").CountAsync(); + var guadanPeopleCurrentFemale = await _db.GuaDanOrderGuest.Where(a => a.CheckOutAt > now && a.followers.sex == "女眾").CountAsync(); var result = new { diff --git a/web/App_Code/api/guadanStatisticsTableController.cs b/web/App_Code/api/guadanStatisticsTableController.cs new file mode 100644 index 0000000..ab521b3 --- /dev/null +++ b/web/App_Code/api/guadanStatisticsTableController.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Http; + +/// +/// guadanStatisticsTable 的摘要描述 +/// +/// +[ezAuthorize] +public class guadanStatisticsTableController: ApiController +{ + private Model.ezEntities _db = new Model.ezEntities(); + public guadanStatisticsTableController() + { + // + // TODO: 在這裡新增建構函式邏輯 + // + } + + [HttpGet] + [Route("api/guadan/guadanstatisticstable/list")] + public IHttpActionResult Get([FromUri] DateTime? start, [FromUri] DateTime? end) + { + // 如果兩個都為空,設定 start = 今天,end = 一個月後 + if (!start.HasValue && !end.HasValue) + { + start = DateTime.Today; + end = DateTime.Today.AddMonths(1); + } + + // 如果有 start 沒有 end,就給 end 預設一個月後 + if (start.HasValue && !end.HasValue) + { + end = start.Value.Date.AddMonths(1); + } + + // 如果有 end 沒有 start,就給 start 預設 end 的前一個月 + if (!start.HasValue && end.HasValue) + { + start = end.Value.Date.AddMonths(-1); + } + + // 最後確保只取 date 部分 + var startDate = start.Value.Date; + var endDate = end.Value.Date; + + // 查詢資料庫時就用 date 型別對應 + var statistics = _db.RegionAndRoomAndBedSchedule + .Where(s => s.IsCancel == false) + .Where(s => s.ScheduleDate >= startDate && s.ScheduleDate <= endDate) + .GroupBy(s => s.ScheduleDate) + .Select(g => new + { + date = g.Key, + todaytotalbookers = g.Count(), // 该日期的总记录数 + checkin = g.Key <= DateTime.Today + ? g.Count(x => x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "402" + || x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "403") + : 0, + }) + .OrderBy(x => x.date) + .ToList(); + var todayDate = DateTime.Today; + dynamic today = statistics.FirstOrDefault(x => x.date == todayDate); + + if (today == null) + { + var todayCount = _db.RegionAndRoomAndBedSchedule + .Where(s => s.ScheduleDate == todayDate && !s.IsCancel) + .Count(); + + today = new { date = todayDate, todaytotalbookers = todayCount }; + } + + var bedcount = _db.RegionRoomBed + .Where(a => a.IsDeleted == false) + .Count(); + var roomcount = _db.Room.Count(); + var result = new + { + bedcount, + roomcount, + statistics, + today, + totalbookers = _db.RegionAndRoomAndBedSchedule + .Where(s => s.IsCancel == false) + .Where(s => s.ScheduleDate >= DateTime.Today) + .Count(), + }; + + return Ok(result); + } + +} \ No newline at end of file diff --git a/web/App_Code/api/lianyouController.cs b/web/App_Code/api/lianyouController.cs index 25765d1..e16dd92 100644 --- a/web/App_Code/api/lianyouController.cs +++ b/web/App_Code/api/lianyouController.cs @@ -11,7 +11,7 @@ using System.Web.Http; /// lianyouController 的摘要描述 /// [ezAuthorize] -public class lianyouController: ApiController +public class lianyouController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); @@ -19,13 +19,34 @@ public class lianyouController: ApiController [Route("api/lianyou/getfollowers")] public async Task GetGuadanFollowers(int page, int pageSize, string searchName = null) { - var qry = _db.followers.AsEnumerable(); - if(searchName != null) + // IQueryable 可讓 EF 在資料庫層面執行過濾和投影 + var qry = _db.followers.AsQueryable(); + + if (!string.IsNullOrEmpty(searchName)) { qry = qry.Where(a => (a.u_name ?? "").Contains(searchName) || (a.phone ?? "").Contains(searchName)); } - var count = qry.Count(); - var data = qry.OrderBy(a => a.f_number).ToPagedList(page, pageSize); - return Ok(new {data = data, count = count}); + + var count = await qry.CountAsync(); + + // 投影到只需要的欄位 + var projected = qry + .OrderBy(a => a.f_number) + .Select(a => new + { + num = a.num, + u_name = a.u_name, + phone = a.phone, + sex = a.sex, + }); + + // 分頁 + var data = projected + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToList(); // 如果使用 EF Core 可用 ToListAsync() + + return Ok(new { data = data, count = count }); } + } \ No newline at end of file diff --git a/web/App_Code/api/regionController.cs b/web/App_Code/api/regionController.cs index e60d137..f1783b1 100644 --- a/web/App_Code/api/regionController.cs +++ b/web/App_Code/api/regionController.cs @@ -11,7 +11,7 @@ using System.Web.Routing; /// regionController 的摘要描述 /// [ezAuthorize] -public class regionController: ApiController +public class regionController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); public regionController() @@ -95,17 +95,18 @@ public class regionController: ApiController bed.Name, bed.Gender, bed.RoomUuid, - bed.StatusUuid, + bed.StatusCode, bed.IsActive, bed.IsDeleted, canuse = bed.IsAvailableDuring(startDate, endDate, _db), statusname = bed.RegionRoomBedStatus.Name, schedules = _db.RegionAndRoomAndBedSchedule + .Where(s => s.IsCancel == false) .Where(s => s.TargetUuid == bed.Uuid && s.IsDeleted == false - && s.IsActive && (s.ScheduleDate == null || (s.ScheduleDate >= startDate && s.ScheduleDate <= endDate))) + .Where(s => s.GuaDanOrderGuest.StatusCode != "403" && s.GuaDanOrderGuest.StatusCode != "404") .Select(s => new { s.Uuid, @@ -113,7 +114,15 @@ public class regionController: ApiController s.UseType, s.Title, s.Description, - s.GuaDanOrderNo + s.GuaDanOrderNo, + s.TargetUuid, + usename = _db.GuaDanOrderGuest + .Where(guest => guest.GuaDanOrderNo == s.GuaDanOrderNo) + .Where(guest => guest.BedUuid == s.TargetUuid) + .Select(guest => guest.followers.u_name) + .FirstOrDefault() + + }) .ToList() }), @@ -183,7 +192,8 @@ public class regionController: ApiController IsActive = region.IsActive, RoomCount = region.RoomCount, BedDto = new List(), - Rooms = region.Room.Select(a => new RoomDto { + Rooms = region.Room.Select(a => new RoomDto + { Uuid = a.Uuid, Name = a.Name, RegionUuid = a.RegionUuid, @@ -194,13 +204,13 @@ public class regionController: ApiController { Uuid = c.Uuid, name = c.Name, - roomUuid = c.RoomUuid, + roomUuid = c.RoomUuid, isactive = c.IsActive, - statusuuid = c.StatusUuid, + statuscode = c.StatusCode, Gender = c.Gender, - + }).ToList(), - + }).ToList(), Children = allRegions .Where(r => r.ParentUuid == region.Uuid) @@ -251,7 +261,7 @@ public class regionController: ApiController name = c.Name, roomUuid = c.RoomUuid, isactive = c.IsActive, - statusuuid = c.StatusUuid + statuscode = c.StatusCode, }).ToList() }) .ToList(); @@ -281,7 +291,7 @@ public class regionController: ApiController BedDto = new List(), Children = children, Gender = region.Gender, - + }; } @@ -303,11 +313,12 @@ public class regionController: ApiController public Nullable UpdatedAt { get; set; } public List beds { get; set; } } - public class BedDto { + public class BedDto + { public Guid Uuid { get; set; } public Guid? roomUuid { get; set; } public string name { get; set; } - public Guid? statusuuid { get; set; } + public string statuscode { get; set; } public bool isactive { get; set; } public bool Gender { get; set; } } @@ -324,13 +335,13 @@ public class regionController: ApiController public bool IsActive { get; set; } = true; public int? RoomCount { get; set; } public List Rooms { get; set; } - public List BedDto { get; set; } - public bool? Gender { get; set; } + public List BedDto { get; set; } + public bool? Gender { get; set; } } [HttpPost] [Route("api/region/create")] - public IHttpActionResult createRegion([FromBody] RegionDto dto) + public IHttpActionResult createRegion([FromBody] RegionDto dto) { if (string.IsNullOrWhiteSpace(dto.Name)) return BadRequest("區域名稱為必填"); @@ -412,7 +423,7 @@ public class regionController: ApiController [HttpPost] [Route("api/region/getRegionType")] public IHttpActionResult getRegionType() - { + { var data = _db.RegionType.Where(a => a.IsActive == true).ToList(); return Ok(data); } @@ -431,7 +442,9 @@ public class regionController: ApiController r.Gender, rooms = r.Room.Select(room => new { - room.Uuid, room.Name, room.RegionUuid + room.Uuid, + room.Name, + room.RegionUuid }).ToList() }).ToList(); return Ok(data); diff --git a/web/App_Code/api/regionRoomBedController.cs b/web/App_Code/api/regionRoomBedController.cs index bfd8988..a2c74e6 100644 --- a/web/App_Code/api/regionRoomBedController.cs +++ b/web/App_Code/api/regionRoomBedController.cs @@ -40,24 +40,23 @@ public class regionRoomBedController : ApiController .Where(a => a.RoomUuid == roomUuid) .ToList(); - // 取出所有相关排程 - var schedules = _db.RegionAndRoomAndBedSchedule - .Where(b => b.IsDeleted == false && b.IsActive) - .ToList(); + StartTime = StartTime.Date; + EndTime = EndTime?.Date; var data = beds.Select(a => { - // 在内存中处理日期比较 - var bedSchedules = schedules + var bedSchedules = _db.RegionAndRoomAndBedSchedule + .Where(s => s.GuaDanOrderGuest.StatusCode != "403") + .Where(s => s.GuaDanOrderGuest.StatusCode != "404") .Where(b => b.TargetUuid == a.Uuid - && (b.ScheduleDate == null // 长期占用 - || (b.ScheduleDate >= StartTime.Date && b.ScheduleDate <= EndTime.Value.Date))) + && (b.ScheduleDate == null + || (b.ScheduleDate >= StartTime && b.ScheduleDate < EndTime))) + .ToList() .Select(c => new { c.Uuid, c.Description, c.IsDeleted, - c.IsActive, c.GuaDanOrderNo, c.UseType, c.Title, @@ -74,13 +73,14 @@ public class regionRoomBedController : ApiController a.Name, a.Gender, a.IsActive, - a.StatusUuid, + a.StatusCode, a.RoomUuid, canUsed, schedule = bedSchedules }; }); + return Ok(data); } @@ -93,7 +93,7 @@ public class regionRoomBedController : ApiController { return BadRequest("當前客房不存在"); } - if(room.Gender != bed.Gender) + if (room.Gender != bed.Gender) { return BadRequest("床為性別和房間性別必須一致"); } @@ -105,7 +105,7 @@ public class regionRoomBedController : ApiController { Name = bed.Name, RoomUuid = bed.RoomUuid, - StatusUuid = bed.StatusUuid, + StatusCode = bed.StatusCode, IsActive = bed.IsActive, Gender = bed.Gender, Uuid = Guid.NewGuid(), @@ -114,10 +114,11 @@ public class regionRoomBedController : ApiController _db.SaveChanges(); //創建床位 - return Ok(new { + return Ok(new + { uuid = regionBed.Uuid, roomUuid = regionBed.RoomUuid, - statusuuid = regionBed.StatusUuid, + statuscode = regionBed.StatusCode, isactive = regionBed.IsActive, gender = regionBed.Gender, name = regionBed.Name, @@ -146,7 +147,7 @@ public class regionRoomBedController : ApiController { return BadRequest("床為性別和房間性別必須一致"); } - oldBed.StatusUuid = bed.StatusUuid; + oldBed.StatusCode = bed.StatusCode; oldBed.IsActive = bed.IsActive; oldBed.Name = bed.Name; oldBed.Gender = bed.Gender; @@ -174,8 +175,37 @@ public class regionRoomBedController : ApiController public async Task GetCanUseBedCountByTime(DateTime startTime, DateTime endTime) { //获取某个时间段内可用床位数量 - var counts = await RegionAndRoomAndBedSchedule.GetAvailableBedCountsAsync(_db, startTime, endTime); - return Ok(counts); + var start = startTime.Date; + var end = endTime.Date; + + // 找出所有在日期範圍內被占用的床位 + var busyBedUuids = await _db.RegionAndRoomAndBedSchedule + .Where(s => s.GuaDanOrderGuest.StatusCode != "403") + .Where(s => s.GuaDanOrderGuest.StatusCode != "404") + .Where(a => a.IsCancel == false) + .Where(s => s.IsDeleted == false + && (s.ScheduleDate == null // 長期占用 + || (s.ScheduleDate >= start && s.ScheduleDate < end))) + .Select(s => s.TargetUuid) + .Distinct() + .ToListAsync(); + + // 可用床位 = 所有床位 - 忙碌床位 + var availableBeds = _db.RegionRoomBed + .Where(b => !busyBedUuids.Contains(b.Uuid)); + + var result = await availableBeds + .GroupBy(b => b.Gender) + .Select(g => new + { + Gender = g.Key, + Count = g.Count() + }) + .ToListAsync(); + + var male = result.Where(r => r.Gender == true).Select(r => r.Count).FirstOrDefault(); + var female = result.Where(r => r.Gender == false).Select(r => r.Count).FirstOrDefault(); + return Ok(new {male, female}); } [HttpPost] @@ -260,7 +290,7 @@ public class regionRoomBedController : ApiController } } - if(index < followers.Count) + if (index < followers.Count) { isAllallocation = false; return; @@ -313,7 +343,7 @@ public class regionRoomBedController : ApiController { // 先拉出床位相關排程到內存,避免 EF 不支援 .Date var schedules = _db.RegionAndRoomAndBedSchedule - .Where(s => s.IsDeleted == false && s.IsActive && s.TargetUuid == req.bedUuid) + .Where(s => s.IsDeleted == false && !s.IsCancel && s.TargetUuid == req.bedUuid) .ToList(); bool conflictExists = schedules.Any(s => @@ -364,7 +394,6 @@ public class regionRoomBedController : ApiController ScheduleDate = date, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, IsDeleted = false, - IsActive = true, CreatedBy = "系统自动分配", CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo @@ -382,7 +411,6 @@ public class regionRoomBedController : ApiController ScheduleDate = null, UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation, IsDeleted = false, - IsActive = true, CreatedBy = "系统自动分配", CreatedAt = DateTime.Now, GuaDanOrderNo = guest.GuaDanOrderNo @@ -406,7 +434,7 @@ public class regionRoomBedController : ApiController private bool BedIsCanUsed(RegionRoomBed bed, DateTime? StartTime, DateTime? EndTime) { - if(!bed.IsActive) + if (!bed.IsActive) { return false; } diff --git a/web/App_Code/api/regionRoomBedStatusController.cs b/web/App_Code/api/regionRoomBedStatusController.cs index 3815679..627f8aa 100644 --- a/web/App_Code/api/regionRoomBedStatusController.cs +++ b/web/App_Code/api/regionRoomBedStatusController.cs @@ -9,7 +9,7 @@ using System.Web.Http; /// regionRoomBedStatusController 的摘要描述 /// [ezAuthorize] -public class regionRoomBedStatusController: ApiController +public class regionRoomBedStatusController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); public regionRoomBedStatusController() @@ -34,7 +34,6 @@ public class regionRoomBedStatusController: ApiController s.Code, s.Name, s.Description, - s.Uuid, s.CategoryName }) .ToList(); @@ -53,9 +52,9 @@ public class regionRoomBedStatusController: ApiController } [HttpPost] [Route("api/region/bed/status/delete")] - public IHttpActionResult DeleteBedStatus([FromUri]Guid id) + public IHttpActionResult DeleteBedStatus([FromUri] string code) { - var rt = _db.RegionRoomBedStatus.Find(id); + var rt = _db.RegionRoomBedStatus.Find(code); if (rt == null) { return NotFound(); diff --git a/web/App_Code/api/regionRoomController.cs b/web/App_Code/api/regionRoomController.cs index 1f01286..dc0e2a9 100644 --- a/web/App_Code/api/regionRoomController.cs +++ b/web/App_Code/api/regionRoomController.cs @@ -14,7 +14,7 @@ using static regionController; /// regionRoomController 的摘要描述 /// [ezAuthorize] -public class regionRoomController: ApiController +public class regionRoomController : ApiController { private Model.ezEntities _db = new Model.ezEntities(); public regionRoomController() @@ -62,7 +62,7 @@ public class regionRoomController: ApiController name = c.Name, roomUuid = c.RoomUuid, isactive = c.IsActive, - statusuuid = c.StatusUuid, + statuscode = c.StatusCode, }).ToList(), }; @@ -86,7 +86,7 @@ public class regionRoomController: ApiController // 如果有不符合性別的床位,不能繼續操作 return BadRequest("房間中已有與房間性別不符的床位,無法操作"); } - if(!room.BedCount.HasValue) + if (!room.BedCount.HasValue) { return BadRequest("請輸入床位數量"); } @@ -97,7 +97,7 @@ public class regionRoomController: ApiController oldRoom.IsActive = room.IsActive; oldRoom.RegionUuid = room.RegionUuid; await _db.SaveChangesAsync(); - return Ok(new { message = "更新成功"}); + return Ok(new { message = "更新成功" }); } [HttpPost] [Route("api/region/room/delete")] diff --git a/web/admin/guadan/create.aspx b/web/admin/guadan/create.aspx index f924212..322434d 100644 --- a/web/admin/guadan/create.aspx +++ b/web/admin/guadan/create.aspx @@ -15,6 +15,15 @@ + 關聯活動 + + + 未關聯 + + {{activity.subject}} + + + @@ -86,14 +95,51 @@ {{item.follower?.u_name}} - - 取消 - - - mdi-pencil - 編輯 - - + + + + 取消預訂 + + + 入住 + + + 續住 + + + + mdi-exit-run + 退房 + + + + + @@ -174,21 +220,6 @@ 請從床位清單中選擇 - - - - - - - @@ -768,6 +799,7 @@ vuetify: new Vuetify(vuetify_options), data() { return { + activityList: [], availableBedCount: { male: 0, female: 0, @@ -782,6 +814,7 @@ bookerName: null, bookerPhone: null, bookerFollowerNum: null, + activityNum: null, }, status_items: [], }, @@ -848,7 +881,7 @@ bedUuid: null, checkInAt: null, checkOutAt: null, - statusUuid: null, + statuscode: null, }, status: [], }, @@ -938,8 +971,14 @@ } }, methods: { + getActivityList() { + axios.post('/api/activity/GetList?page=1&pageSize=500', { kind: 0, subject: "" }) + .then((res) => { + this.activityList = res.data.list + }) + }, getavailablebedcountbytime(startTime, endTime) { - axios.get('/api/region/bed/getavailablebedcountbytime',{ + axios.get('/api/region/bed/getavailablebedcountbytime', { params: { startTime: startTime, endTime: endTime @@ -1117,6 +1156,7 @@ this.guadanorder.order_form.bookerPhone = res.data.bookerPhone; this.guadanorder.order_form.bookerFollowerNum = res.data.bookerFollowerNum; this.guadanorder.order_form.uuid = res.data.uuid; + this.guadanorder.order_form.activityNum = res.data.activityNum; }) } }, @@ -1203,6 +1243,40 @@ //掛單相關方法-------------------end //增加蓮友方法-------------------start + checkoutGuadanOrderGuest(guest) { + this.$refs.confirmModal.open({ + message: `確定要將 ${guest.follower.u_name || ''} 退房嗎?`, + onConfirm: async () => { + try { + const response = await axios.post(`/api/guadanorderguest/checkout`, null, { + params: { uuid: guest.uuid } + }); + + // 成功提示 + this.$refs.messageModal.open({ + title: '操作成功', + message: '退房成功!', + status: 'success' + }); + + // 更新狀態並刷新資料 + guest.statusCode = "403"; // 已退房 + this.getGuadanOrderGuestByOrderNo(); + } catch (error) { + console.error(error); + + // 失敗提示 + this.$refs.messageModal.open({ + title: '操作失敗', + message: error.response?.data?.message || '退房過程中發生錯誤!', + status: 'error' + }); + } + } + }); + }, + + resetInGuest() { this.checkInGuest.inGuest = { uuid: null, @@ -1212,7 +1286,7 @@ bedUuid: null, checkInAt: null, checkOutAt: null, - statusUuid: null, + statuscode: null, }; }, setInGuest() { @@ -1284,8 +1358,7 @@ getGuadanGuestStatus() { axios.get('/api/region/bed/status/list') .then((res) => { - this.checkInGuest.status = res.data.filter(item => item.category === 4); - console.log(this.checkInGuest.status); + this.checkInGuest.status = res.data.filter(item => item.category === 4 && item.code != '404'); }) }, //增加蓮友方法-------------------end @@ -1318,7 +1391,7 @@ }, selectGuadanOrderGuest(guest) { this.selectGuestModal.currentSelectedGuest = guest; - console.log('----------'+ guest) + console.log('----------' + guest) this.selectGuestModal.fullNameText = guest.u_name; this.checkInGuest.inGuest.followerNum = guest.num; this.selectGuestModal.showSelectGuestModal = false; @@ -1336,7 +1409,7 @@ this.getCurrentSelectBedTextByBedId(guest.bedUuid); this.selectGuestModal.fullNameText = guest.follower?.u_name; this.selectGuestModal.currentSelectedGuest = guest.follower; - this.checkInGuest.inGuest.statusUuid = guest.statusUuid; + this.checkInGuest.inGuest.statuscode = guest.statuscode; }, async saveEditGuadanOrderGuest() { @@ -1352,9 +1425,14 @@ }, deleteGuadanOrderGuest(guest) { - axios.post('/api/guadanorderguest/delete?uuid=' + guest.uuid).then((res) => { - this.guadanguest.items = this.guadanguest.items.filter(i => i.uuid != guest.uuid); - }) + axios.post('/api/guadanorderguest/cancel?uuid=' + guest.uuid) + .then((res) => { + this.guadanguest.items = this.guadanguest.items.filter(i => i.uuid != guest.uuid); + }).catch((error) => { + this.$refs.messageModal.open({ + message: (error.response?.data?.message || error.message) + }) + }); }, confirmDeleteGuadanOrderGuest(guest) { this.$refs.confirmModal.open({ @@ -1364,6 +1442,43 @@ } }) }, + async checkinGuadanGuest(guest) { + // 先確認操作 + this.$refs.confirmModal.open({ + message: '確認入住?', + onConfirm: async () => { + try { + // 發送請求到後端 API + const response = await axios.post(`/api/guadanorderguest/checkin`, null, { + params: { uuid: guest.uuid } + }); + + // 成功提示 + this.$refs.messageModal.open({ + title: '操作成功', + message: '入住成功!', + status: 'success' + }); + + // 更新本地列表,修改狀態為已入住 (402) + guest.statusCode = "402"; + + // 如果需要刷新整個列表,也可以調用 + this.getGuadanOrderGuestByOrderNo(); + + } catch (error) { + console.error(error); + + // 失敗提示 + this.$refs.messageModal.open({ + title: '操作失敗', + message: error.response?.data?.message || '入住過程中發生錯誤!', + status: 'error' + }); + } + } + }); + }, //蓮友選擇相關方法---------------end //床位選擇相關方法----------------start @@ -1532,6 +1647,7 @@ this.loadRegions(); this.getGuadanOrderStatus(); this.getGuadanGuestStatus(); + this.getActivityList(); }, computed: { diff --git a/web/admin/guadan/index.aspx b/web/admin/guadan/index.aspx index 9c9816f..9a3fc46 100644 --- a/web/admin/guadan/index.aspx +++ b/web/admin/guadan/index.aspx @@ -56,7 +56,7 @@ return { items: [], headers: [ - { text: '登记挂单莲友', value: 'bookerName'}, + { text: '登记挂单莲友', value: 'bookerName' }, { text: '起始日期', value: 'start_date', align: 'center' }, { text: '結束日期', value: 'end_date', align: 'center' }, { text: '掛單人數', value: 'guest_count' }, @@ -81,7 +81,7 @@ this.$refs.confirmModal.open({ message: '確認取消掛單?', onConfirm: () => { - axios.post('/api/guadan/delete', null, { + axios.post('/api/guadan/cancel', null, { params: { uuid: order.uuid } diff --git a/web/admin/guadan/statistics_table.aspx b/web/admin/guadan/statistics_table.aspx new file mode 100644 index 0000000..fbc2978 --- /dev/null +++ b/web/admin/guadan/statistics_table.aspx @@ -0,0 +1,193 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="statistics_table.aspx.cs" Inherits="admin_guadan_statistics_table" %> + + + + + + + + + + + 今日使用情况 + + + + + + 总预订人数 + {{totalbookers }} + + + 今日预订人数 + {{today.todaytotalbookers }} + + + 今日已入住人数 + {{today.checkin }} + + + 今日待入住人数 + {{today.todaytotalbookers - today.checkin }} + + + 空床數 + {{ bedcount - today.todaytotalbookers}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 查詢 + + + + + + + + + + + + {{ item.date | timeString('YYYY-MM-DD') }} + + + {{ ((item.todaytotalbookers / bedcount) * 100).toFixed(2) + '%' }} + + + {{roomcount}} + + + {{bedcount}} + + + + + + + + + + + diff --git a/web/admin/guadan/statistics_table.aspx.cs b/web/admin/guadan/statistics_table.aspx.cs new file mode 100644 index 0000000..eb1c6bb --- /dev/null +++ b/web/admin/guadan/statistics_table.aspx.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +public partial class admin_guadan_statistics_table : MyWeb.config +{ + protected void Page_Load(object sender, EventArgs e) + { + + } +} \ No newline at end of file diff --git a/web/admin/region/bed/bedstatus/create.aspx b/web/admin/region/bed/bedstatus/create.aspx index 7c9f392..354b430 100644 --- a/web/admin/region/bed/bedstatus/create.aspx +++ b/web/admin/region/bed/bedstatus/create.aspx @@ -16,7 +16,7 @@ - + 名稱 diff --git a/web/admin/region/bed/bedstatus/create.aspx.cs b/web/admin/region/bed/bedstatus/create.aspx.cs index 3150757..0fb6d08 100644 --- a/web/admin/region/bed/bedstatus/create.aspx.cs +++ b/web/admin/region/bed/bedstatus/create.aspx.cs @@ -1,35 +1,36 @@ using Model; using System; -using System.Collections.Generic; using System.Linq; -using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class admin_region_bed_bedstatus_create : MyWeb.config { private Model.ezEntities _db = new Model.ezEntities(); + protected void Page_Load(object sender, EventArgs e) { - if (!IsPostBack) // 加這行 + if (!IsPostBack) { - if (Guid.TryParse(Request.QueryString["statusid"], out Guid id)) + var code = Request.QueryString["code"]; + if (!string.IsNullOrEmpty(code)) { - LoadData(id); + LoadData(code); L_title.Text = "編輯區域類型"; } + var categoryList = RegionRoomBedStatus.GetCategoryList(); - // 假设你的下拉控件ID叫 ddlCategory TB_Category.DataSource = categoryList; - TB_Category.DataTextField = "Text"; // 显示文字 - TB_Category.DataValueField = "Value"; // 选项值 + TB_Category.DataTextField = "Text"; // 顯示文字 + TB_Category.DataValueField = "Value"; // 選項值 TB_Category.DataBind(); - TB_Category.Items.Insert(0, new ListItem("--请选择分类--", "")); + TB_Category.Items.Insert(0, new ListItem("--請選擇分類--", "")); } } - private void LoadData(Guid id) + + private void LoadData(string code) { - var rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Uuid == id); + var rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Code == code); if (rt == null) { L_msg.Text = "找不到資料"; @@ -37,22 +38,24 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config return; } - HF_Id.Value = rt.Uuid.ToString(); + HF_Code.Value = rt.Code; // ✅ 以 Code 為唯一識別 TB_Name.Text = rt.Name; TB_Code.Text = rt.Code; TB_Category.SelectedValue = rt.Category?.ToString(); Description.Text = rt.Description; - } + protected void BTN_Save_Click(object sender, EventArgs e) { try { RegionRoomBedStatus rt; - if (Guid.TryParse(HF_Id.Value, out Guid id)) + var code = HF_Code.Value; // ✅ 用隱藏欄位的 Code 判斷 + + if (!string.IsNullOrEmpty(code)) { // 更新 - rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Uuid == id); + rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Code == code); if (rt == null) { L_msg.Text = "資料不存在"; @@ -63,19 +66,20 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config { // 新增 rt = new RegionRoomBedStatus(); - rt.Uuid = Guid.NewGuid(); + rt.Code = TB_Code.Text.Trim(); // ✅ 以 Code 當主鍵 _db.RegionRoomBedStatus.Add(rt); } rt.Name = TB_Name.Text.Trim(); - if (rt.Name.Length == 0) + if (string.IsNullOrEmpty(rt.Name)) { - L_msg.Text = "名稱不能为空"; + L_msg.Text = "名稱不能為空"; return; } - rt.Code = TB_Code.Text.Trim(); + rt.Description = Description.Text.Trim(); - if(int.TryParse(TB_Category.SelectedValue, out int category)) + + if (int.TryParse(TB_Category.SelectedValue, out int category)) { rt.Category = category; } @@ -83,16 +87,18 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config { rt.Category = null; } + _db.SaveChanges(); L_msg.Text = "儲存成功"; - // 如果是新增,更新隱藏欄位並切換標題為編輯 - if (HF_Id.Value == "") + // ✅ 如果是新增,更新隱藏欄位並切換標題 + if (string.IsNullOrEmpty(HF_Code.Value)) { - HF_Id.Value = rt.Uuid.ToString(); + HF_Code.Value = rt.Code; L_title.Text = "編輯區域類型"; } + Response.Redirect("index.aspx"); } catch (Exception ex) @@ -100,4 +106,4 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config L_msg.Text = $"錯誤:{ex.Message}"; } } -} \ No newline at end of file +} diff --git a/web/admin/region/bed/bedstatus/index.aspx b/web/admin/region/bed/bedstatus/index.aspx index 253584e..754acdb 100644 --- a/web/admin/region/bed/bedstatus/index.aspx +++ b/web/admin/region/bed/bedstatus/index.aspx @@ -20,7 +20,7 @@ :loading="loading" > - 修改 + 修改 { - this.items = this.items.filter(i => i.uuid != item.uuid); + this.items = this.items.filter(i => i.code != item.code); this.$refs.messageModal.open({ title: '操作成功', message: '刪除成功!', diff --git a/web/admin/region/bed/index.aspx b/web/admin/region/bed/index.aspx index 7a4862d..8f51974 100644 --- a/web/admin/region/bed/index.aspx +++ b/web/admin/region/bed/index.aspx @@ -112,7 +112,7 @@ {{ region.regionPath }} - + @@ -138,20 +138,26 @@ 床位名稱 - 是否可用 使用明細 + 掛單 + 當前狀態 {{ bed.name }} - - {{ bed.canuse ? '是' : '否' }} - 查看明細 + + + 快速掛單 + + + + {{bed.statusname}} + @@ -251,6 +257,7 @@ { text: '使用日期', value: 'scheduledate' }, { text: '掛單單號', value: 'guaDanOrderNo' }, { text: '標題', value: 'title' }, + { text: '掛單人', value: 'usename' }, { text: '查看掛單', value: 'actions' }, ], }, @@ -381,9 +388,6 @@ return { maleBeds, femaleBeds }; } } - - - }) diff --git a/web/admin/region/index.aspx b/web/admin/region/index.aspx index 6f4dbae..2c9bcdb 100644 --- a/web/admin/region/index.aspx +++ b/web/admin/region/index.aspx @@ -217,8 +217,8 @@ {{item.isactive ? '啟用' : '停用'}} - - {{getBedStatusNameById(item.statusuuid)}} + + {{getBedStatusNameById(item.statuscode)}} @@ -252,8 +252,8 @@ 狀態 - - + + {{status.name}} @@ -528,7 +528,7 @@ bed_headers: [ { text: '床位編號', value: 'uuid' }, { text: '床位名稱', value: 'name' }, - { text: '床位狀態', value: 'statusuuid' }, + { text: '床位狀態', value: 'statuscode' }, { text: '是否啟用', value: 'isactive' }, { text: '', value: 'action' }, ], @@ -536,7 +536,7 @@ uuid: null, RegionUuid: null, Name: '', - statusuuid: null, + statuscode: null, IsActive: true, Gender: null, }, @@ -664,7 +664,7 @@ }); }, confirmDeleteRegion() { - axios.post('/api/region/delete', { uuid: this.form.uuid }) + axios.post('/api/region/delete', { statuscode: this.form.statuscode }) .then(() => { this.showDeleteModal = false; this.$refs.messageModal.open({ @@ -716,7 +716,7 @@ uuid: null, RoomUuid: this.currentSelectRoom.uuid, Name: '', - statusuuid: null, + statuscode: null, IsActive: true, Gender: this.currentSelectRoom.gender, // 不設預設值,強制選擇 }; @@ -791,7 +791,7 @@ RegionUuid: bed.regionUuid, RoomUuid: bed.roomUuid, Name: bed.name, - statusuuid: bed.statusuuid, + statuscode: bed.statuscode, IsActive: bed.isactive, Gender: bed.gender, }; @@ -811,7 +811,7 @@ ...this.room_bed.bed_items[index], // 保留原本未更新欄位 roomUuid: updated.RoomUuid, name: updated.Name, - statusuuid: updated.statusuuid, + statuscode: updated.statuscode, isactive: updated.IsActive }); } @@ -839,10 +839,8 @@ this.room_bed.bed_status = res.data; }) }, - getBedStatusNameById(id) { - console.log(id) - //傳入一個Id,獲取該Id對應的名稱 - const status = this.room_bed.bed_status.find(i => i.uuid == id); + getBedStatusNameById(statuscode) { + const status = this.room_bed.bed_status.find(i => i.code == statuscode); if (status) { return status.name; }