修改挂单功能

This commit is contained in:
2025-09-09 16:25:28 +08:00
parent e6c6b1f43f
commit ded24a8446
23 changed files with 1073 additions and 231 deletions

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
/// <summary>
/// GuaDanOrderGuest 的摘要描述
/// </summary>
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<string, List<string>>
{
{ STATUS_BOOKED, new List<string> { STATUS_CHECKED_IN, STATUS_CANCELLED } },
{ STATUS_CHECKED_IN, new List<string> { STATUS_CHECKED_OUT } },
{ STATUS_CHECKED_OUT, new List<string> { } }, // 终态,不能再转换
{ STATUS_CANCELLED, new List<string> { } } // 终态,不能再转换
};
// 检查转换是否有效
if (validTransitions.ContainsKey(currentStatus))
{
return validTransitions[currentStatus].Contains(targetStatus);
}
return false; // 未知的当前状态
}
}
}

View File

@@ -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<bool> 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)

View File

@@ -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;
}
}
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Model
{
public static class StatusTransitionManager
{
private static readonly Dictionary<string, List<string>> transitions =
new Dictionary<string, List<string>>
{
// 掛單狀態
{ "401", new List<string> { "402", "404" } },
{ "402", new List<string> { "403" } },
{ "403", new List<string>() },
{ "404", new List<string>() },
// 床位狀態
{ "101", new List<string> {"101", "102","103"} },
{ "102", new List<string> { "101" } },
{ "103", new List<string> { "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);
}
}
}

View File

@@ -12,14 +12,16 @@ using static regionController;
/// guadanOderController 的摘要描述
/// </summary>
[ezAuthorize]
public class guadanOrderController: ApiController
public class guadanOrderController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
[Route("api/guadan/list")]
public async Task<IHttpActionResult> 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<IHttpActionResult> 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<IHttpActionResult> deleteGuadanOrder([FromUri] Guid uuid)
[Route("api/guadan/cancel")]
public async Task<IHttpActionResult> 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; }

View File

@@ -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 的摘要描述
/// </summary>
[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<IHttpActionResult> 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<IHttpActionResult> deleteGuadanGuest([FromUri] Guid uuid)
[Route("api/guadanorderguest/cancel")]
public async Task<IHttpActionResult> 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; }
}
}

View File

@@ -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
{

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
/// <summary>
/// guadanStatisticsTable 的摘要描述
/// </summary>
///
[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);
}
}

View File

@@ -11,7 +11,7 @@ using System.Web.Http;
/// lianyouController 的摘要描述
/// </summary>
[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<IHttpActionResult> 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 });
}
}

View File

@@ -11,7 +11,7 @@ using System.Web.Routing;
/// regionController 的摘要描述
/// </summary>
[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<BedDto>(),
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<BedDto>(),
Children = children,
Gender = region.Gender,
};
}
@@ -303,11 +313,12 @@ public class regionController: ApiController
public Nullable<System.DateTime> UpdatedAt { get; set; }
public List<BedDto> 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<RoomDto> Rooms { get; set; }
public List<BedDto> BedDto { get; set; }
public bool? Gender { get; set; }
public List<BedDto> 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);

View File

@@ -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<IHttpActionResult> 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;
}

View File

@@ -9,7 +9,7 @@ using System.Web.Http;
/// regionRoomBedStatusController 的摘要描述
/// </summary>
[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();

View File

@@ -14,7 +14,7 @@ using static regionController;
/// regionRoomController 的摘要描述
/// </summary>
[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")]