diff --git a/.env.example b/.env.example index 9920f34e706e4df5793c45ca4e1242b892322235..98a85a2f0727c3492c5f3f4e6eb286984573a954 100644 --- a/.env.example +++ b/.env.example @@ -18,16 +18,18 @@ SqlServerConnectStr=${SQLSERVER_CONNECT_STR} # Security Jwt__Key=${JWT_SIGNING_KEY} -Jwt__ExpiryMinutes=20 +Jwt__ExpiryMinutes=15 +Jwt__RefreshTokenExpiryDays=7 # CORS AllowedOrigins__0=http://localhost:8080 AllowedOrigins__1=https://www.yourdomain.com # Quartz jobs -JobKeys__0=ReservationExpirationCheckJob -JobKeys__1=MailServiceCheckJob -JobKeys__2=RedisServiceCheckJob +JobKeys__0=ReservationExpirationCheckJob:0 0 1 * * ? +JobKeys__1=MailServiceCheckJob:0 */5 * * * ? +JobKeys__2=RedisServiceCheckJob:0 */5 * * * ? +JobKeys__3=AutomaticallyUpgradeMembershipLevelJob:0 30 1 * * ? ExpirationSettings__NotifyDaysBefore=3 ExpirationSettings__CheckIntervalMinutes=5 @@ -52,6 +54,14 @@ Mail__DisplayName=TSHotel Redis__Enabled=false Redis__ConnectionString=${REDIS_CONNECTION_STRING} Redis__DefaultDatabase=0 +Redis__ConnectTimeoutMs=5000 +Redis__AsyncTimeoutMs=2000 +Redis__SyncTimeoutMs=2000 +Redis__KeepAliveSeconds=15 +Redis__ConnectRetry=3 +Redis__ReconnectRetryBaseDelayMs=3000 +Redis__OperationTimeoutMs=1200 +Redis__FailureCooldownSeconds=30 # Lsky (optional) Lsky__Enabled=false diff --git a/.gitignore b/.gitignore index 46058609ea76f1a2f4b7d3c4e1189f0572099e80..aa1f521d7d74a546fae9a7ca5fd7c0132ff100b3 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ frontend/ .version *.txt docs/ +.codex-staging/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/EOM.TSHotelManagement.API/Authorization/BusinessOperationAuditAttribute.cs b/EOM.TSHotelManagement.API/Authorization/BusinessOperationAuditAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..addd7665fd16cb4d2c7363742b510b13c3b1da70 --- /dev/null +++ b/EOM.TSHotelManagement.API/Authorization/BusinessOperationAuditAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace EOM.TSHotelManagement.WebApi.Authorization +{ + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public sealed class BusinessOperationAuditAttribute : Attribute + { + } +} diff --git a/EOM.TSHotelManagement.API/Authorization/PermissionsAuthorization.cs b/EOM.TSHotelManagement.API/Authorization/PermissionsAuthorization.cs index e37fa554ac097e2c92e756db28bb331f8281a261..b2884f678c2f8e51b6534b63e0fcb652e6494c89 100644 --- a/EOM.TSHotelManagement.API/Authorization/PermissionsAuthorization.cs +++ b/EOM.TSHotelManagement.API/Authorization/PermissionsAuthorization.cs @@ -135,7 +135,7 @@ namespace EOM.TSHotelManagement.WebApi.Authorization // 获取用户绑定的有效角色 var roleNumbers = await _db.Queryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToListAsync(); @@ -147,8 +147,7 @@ namespace EOM.TSHotelManagement.WebApi.Authorization // 判断角色是否拥有该权限 var hasPermission = await _db.Queryable() - .Where(rp => rp.IsDelete != 1 - && roleNumbers.Contains(rp.RoleNumber) + .Where(rp => roleNumbers.Contains(rp.RoleNumber) && rp.PermissionNumber == requirement.Code) .AnyAsync(); diff --git a/EOM.TSHotelManagement.API/Controllers/Application/FavoriteCollection/FavoriteCollectionController.cs b/EOM.TSHotelManagement.API/Controllers/Application/FavoriteCollection/FavoriteCollectionController.cs new file mode 100644 index 0000000000000000000000000000000000000000..80936838b47619fb262a43f732badbf0ca40f21f --- /dev/null +++ b/EOM.TSHotelManagement.API/Controllers/Application/FavoriteCollection/FavoriteCollectionController.cs @@ -0,0 +1,44 @@ +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Service; +using Microsoft.AspNetCore.Mvc; + +namespace EOM.TSHotelManagement.WebApi.Controllers +{ + /// + /// 收藏夹持久化接口 + /// + public class FavoriteCollectionController : ControllerBase + { + private readonly IFavoriteCollectionService _favoriteCollectionService; + + /// + /// 构造收藏夹控制器 + /// + /// 收藏夹服务 + public FavoriteCollectionController(IFavoriteCollectionService favoriteCollectionService) + { + _favoriteCollectionService = favoriteCollectionService; + } + + /// + /// 保存当前登录用户的收藏夹快照 + /// + /// 收藏夹保存请求 + /// 保存结果 + [HttpPost] + public SingleOutputDto SaveFavoriteCollection([FromBody] SaveFavoriteCollectionInputDto input) + { + return _favoriteCollectionService.SaveFavoriteCollection(input); + } + + /// + /// 获取当前登录用户的收藏夹快照 + /// + /// 收藏夹读取结果 + [HttpGet] + public SingleOutputDto GetFavoriteCollection() + { + return _favoriteCollectionService.GetFavoriteCollection(); + } + } +} diff --git a/EOM.TSHotelManagement.API/Controllers/Application/Profile/ProfileController.cs b/EOM.TSHotelManagement.API/Controllers/Application/Profile/ProfileController.cs new file mode 100644 index 0000000000000000000000000000000000000000..02db3036cc7c953532a587bdb25f29545fdf6c68 --- /dev/null +++ b/EOM.TSHotelManagement.API/Controllers/Application/Profile/ProfileController.cs @@ -0,0 +1,78 @@ +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Service; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.WebApi.Controllers +{ + /// + /// 个人中心控制器 + /// + public class ProfileController( + IProfileService profileService, + JwtTokenRevocationService tokenRevocationService, + ILogger logger) : ControllerBase + { + private readonly IProfileService _profileService = profileService; + private readonly JwtTokenRevocationService _tokenRevocationService = tokenRevocationService; + private readonly ILogger _logger = logger; + + /// + /// 获取当前登录人基础资料 + /// + [HttpGet] + public SingleOutputDto GetCurrentProfile() + { + return _profileService.GetCurrentProfile(GetCurrentSerialNumber()); + } + + /// + /// 上传当前登录人头像 + /// + [HttpPost] + public async Task> UploadAvatar([FromForm] UploadAvatarInputDto inputDto, IFormFile file) + { + return await _profileService.UploadAvatar(GetCurrentSerialNumber(), inputDto, file); + } + + /// + /// 修改当前登录人密码 + /// + [HttpPost] + public BaseResponse ChangePassword([FromBody] ChangePasswordInputDto inputDto) + { + var response = _profileService.ChangePassword(GetCurrentSerialNumber(), inputDto); + if (!response.Success) + { + return response; + } + + try + { + var authorizationHeader = Request.Headers["Authorization"].ToString(); + if (JwtTokenRevocationService.TryGetBearerToken(authorizationHeader, out var token)) + { + Task.Run(async () => { await _tokenRevocationService.RevokeTokenAsync(token); }); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to revoke current token after password change."); + } + + return response; + } + + private string GetCurrentSerialNumber() + { + return User.FindFirstValue(ClaimTypes.SerialNumber) + ?? User.FindFirst("serialnumber")?.Value + ?? string.Empty; + } + } +} diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Asset/AssetController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Asset/AssetController.cs index 08d903c2d916b405de71d7875ee9d841f95304a4..1fc76e1db0883f1e9a45c542b513ba8256750ccd 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Asset/AssetController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Asset/AssetController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 资产信息控制器 /// + [BusinessOperationAudit] public class AssetController : ControllerBase { /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs index 15b12f08fb976203c99649a16fc9ca15a5d37dfb..eb178c0fec8e2d3d7234d3b7fb47371322d69642 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs @@ -1,11 +1,14 @@ using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Service; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System; using System.Security.Claims; namespace EOM.TSHotelManagement.WebApi.Controllers { + [ApiExplorerSettings(GroupName = "v1_Mobile")] public class CustomerAccountController : ControllerBase { private readonly ICustomerAccountService _customerAccountService; @@ -25,7 +28,21 @@ namespace EOM.TSHotelManagement.WebApi.Controllers [IgnoreAntiforgeryToken] public SingleOutputDto Login([FromBody] ReadCustomerAccountInputDto readCustomerAccountInputDto) { - return _customerAccountService.Login(readCustomerAccountInputDto); + var result = _customerAccountService.Login(readCustomerAccountInputDto); + + // 如果登录成功,设置Refresh Token到HttpOnly Cookie + if (result.Code == BusinessStatusCode.Success && result.Data != null && !string.IsNullOrWhiteSpace(result.Data.RefreshToken)) + { + Response.Cookies.Append("refreshToken", result.Data.RefreshToken, new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict, + Expires = DateTime.Now.AddDays(7) // 与Refresh Token过期时间一致 + }); + } + + return result; } /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerController.cs index 78aef59c594129475786b79934e99575e4883057..6b7c98c4c879adcb308ba921146bac881bfd999f 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 用户信息控制器 /// + [BusinessOperationAudit] public class CustomerController : ControllerBase { /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/EnergyManagement/EnergyManagementController.cs b/EOM.TSHotelManagement.API/Controllers/Business/EnergyManagement/EnergyManagementController.cs index 297d362fbd6fdf056a06d07f0e402734d50ac886..78eb4fe399e1878905c6da70772dfb2d05db1929 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/EnergyManagement/EnergyManagementController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/EnergyManagement/EnergyManagementController.cs @@ -7,6 +7,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 水电信息控制器 /// + [BusinessOperationAudit] public class EnergyManagementController : ControllerBase { /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs b/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs index 072e3e382737d5361171c53886fd092e019cc701..6eecfe0ea94dc52456de341cbe55b13a46d4bbb8 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; namespace EOM.TSHotelManagement.WebApi { + [ApiExplorerSettings(GroupName = "v1_Mobile")] public class NewsController : ControllerBase { private readonly INewsService newsService; diff --git a/EOM.TSHotelManagement.API/Controllers/Business/PromotionContent/PromotionContentController.cs b/EOM.TSHotelManagement.API/Controllers/Business/PromotionContent/PromotionContentController.cs index 8e00131040a41d54fcc9580e86f6dce3cb1ed026..b1f53941d3afbd65a812b2f2da30c5471636829e 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/PromotionContent/PromotionContentController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/PromotionContent/PromotionContentController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 酒店宣传联动内容控制器 /// + [BusinessOperationAudit] public class PromotionContentController : ControllerBase { /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Reser/ReserController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Reser/ReserController.cs index 950862e511b50dfb70f22c02812f8e247aadd3a0..79ccfe67c1096e87b4efa5ac709ec63e88ef920e 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Reser/ReserController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Reser/ReserController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 预约信息控制器 /// + [BusinessOperationAudit] public class ReserController : ControllerBase { /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs index e2530ce6a7ba2d238c7d13ffd0ef9c743916b1f4..0e90d4bc8efa9a052b9c3040a01af7f0bdf69737 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 房间信息控制器 /// + [BusinessOperationAudit] public class RoomController : ControllerBase { private readonly IRoomService roomService; @@ -145,6 +146,13 @@ namespace EOM.TSHotelManagement.WebApi.Controllers return roomService.SelectRoomByRoomPrice(inputDto); } + [RequirePermission("roommanagement.srbrp")] + [HttpGet] + public SingleOutputDto SelectRoomPricingOptions([FromQuery] ReadRoomInputDto inputDto) + { + return roomService.SelectRoomPricingOptions(inputDto); + } + /// /// 查询脏房数量 /// diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomTypeController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomTypeController.cs index c870e6b5a72dba8e7100382c8e2db76ebbcf2fac..cc01fd705a1ed1b3a386add91d0f588bce241489 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomTypeController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomTypeController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 房间类型控制器 /// + [BusinessOperationAudit] public class RoomTypeController : ControllerBase { private readonly IRoomTypeService roomTypeService; @@ -77,4 +78,4 @@ namespace EOM.TSHotelManagement.WebApi.Controllers return roomTypeService.DeleteRoomType(inputDto); } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Sellthing/SellthingController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Sellthing/SellthingController.cs index 14927740390911c65773c30f0ef1891457d521af..db0a1ebec15a7b3d359b01d35dc0d3121e6d65b1 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Sellthing/SellthingController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Sellthing/SellthingController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 商品消费控制器 /// + [BusinessOperationAudit] public class SellthingController : ControllerBase { private readonly ISellService sellService; @@ -79,4 +80,3 @@ namespace EOM.TSHotelManagement.WebApi.Controllers } } - diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs index 136169da66e4c93a1506e79368e6a964149c780f..9a9f4a5f33c0401ec942a9ec3ec50da910c39da8 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs @@ -8,6 +8,7 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 消费信息控制器 /// + [BusinessOperationAudit] public class SpendController : ControllerBase { private readonly ISpendService spendService; @@ -67,13 +68,13 @@ namespace EOM.TSHotelManagement.WebApi.Controllers /// /// 撤回客户消费信息 /// - /// + /// /// [RequirePermission("customerspend.ucs")] [HttpPost] - public BaseResponse UndoCustomerSpend([FromBody] UpdateSpendInputDto updateSpendInputDto) + public BaseResponse UndoCustomerSpend([FromBody] UndoCustomerSpendInputDto undoCustomerSpendInputDto) { - return spendService.UndoCustomerSpend(updateSpendInputDto); + return spendService.UndoCustomerSpend(undoCustomerSpendInputDto); } /// diff --git a/EOM.TSHotelManagement.API/Controllers/Employee/EmployeeController.cs b/EOM.TSHotelManagement.API/Controllers/Employee/EmployeeController.cs index bb4a27f20f56a89a2046a727de78918ad5e81afd..91d3f3464b5c1e6fb959d9fafb2fb33e46a3fb65 100644 --- a/EOM.TSHotelManagement.API/Controllers/Employee/EmployeeController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Employee/EmployeeController.cs @@ -1,8 +1,12 @@ using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Infrastructure; using EOM.TSHotelManagement.Service; using EOM.TSHotelManagement.WebApi.Authorization; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using System; using System.Security.Claims; namespace EOM.TSHotelManagement.WebApi.Controllers @@ -13,10 +17,12 @@ namespace EOM.TSHotelManagement.WebApi.Controllers public class EmployeeController : ControllerBase { private readonly IEmployeeService workerService; + private readonly JwtConfig _jwtConfig; - public EmployeeController(IEmployeeService workerService) + public EmployeeController(IEmployeeService workerService, IOptions jwtConfig) { this.workerService = workerService; + _jwtConfig = jwtConfig.Value; } /// @@ -89,7 +95,21 @@ namespace EOM.TSHotelManagement.WebApi.Controllers [IgnoreAntiforgeryToken] public SingleOutputDto EmployeeLogin([FromBody] EmployeeLoginDto inputDto) { - return workerService.EmployeeLogin(inputDto); + var result = workerService.EmployeeLogin(inputDto); + + // 如果登录成功,设置Refresh Token到HttpOnly Cookie + if (result.Code == BusinessStatusCode.Success && result.Data != null && !string.IsNullOrWhiteSpace(result.Data.RefreshToken)) + { + Response.Cookies.Append("refreshToken", result.Data.RefreshToken, new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict, + Expires = DateTime.Now.AddDays(_jwtConfig.RefreshTokenExpiryDays) // 与Refresh Token过期时间一致 + }); + } + + return result; } /// diff --git a/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs b/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs index a74afedc607ebe1e71b6bd03e94706add1117b67..d8e0890d87286165fe031088edde3ab528edeb35 100644 --- a/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs @@ -29,6 +29,18 @@ namespace EOM.TSHotelManagement.WebApi.Controllers return workerHistoryService.AddHistoryByEmployeeId(workerHistory); } + /// + /// 根据工号更新员工履历 + /// + /// + /// + [RequirePermission("staffmanagement.update")] + [HttpPost] + public BaseResponse UpdateHistoryByEmployeeId([FromBody] UpdateEmployeeHistoryInputDto workerHistory) + { + return workerHistoryService.UpdateHistoryByEmployeeId(workerHistory); + } + /// /// 根据工号查询履历信息 /// @@ -43,4 +55,3 @@ namespace EOM.TSHotelManagement.WebApi.Controllers } } - diff --git a/EOM.TSHotelManagement.API/Controllers/LoginController.cs b/EOM.TSHotelManagement.API/Controllers/LoginController.cs index e3f6415aaae92fdd874758585783eebc2256a069..3cca2401e733da604085c50480b48ed019dcc8f5 100644 --- a/EOM.TSHotelManagement.API/Controllers/LoginController.cs +++ b/EOM.TSHotelManagement.API/Controllers/LoginController.cs @@ -3,13 +3,16 @@ using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Infrastructure; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; using System.Threading.Tasks; -namespace EOM.TSHotelManagement.WebApi +namespace EOM.TSHotelManagement.API.Controllers { /// /// 认证相关接口。 @@ -18,16 +21,26 @@ namespace EOM.TSHotelManagement.WebApi { private readonly IAntiforgery _antiforgery; private readonly CsrfTokenConfig _csrfConfig; + private readonly JwtConfig _jwtConfig; private readonly JwtTokenRevocationService _tokenRevocationService; + private readonly JWTHelper _jwtHelper; + + private readonly ILogger _logger; public LoginController( IAntiforgery antiforgery, IOptions csrfConfig, - JwtTokenRevocationService tokenRevocationService) + IOptions jwtConfig, + JwtTokenRevocationService tokenRevocationService, + JWTHelper jwtHelper, + ILogger logger) { _antiforgery = antiforgery; _csrfConfig = csrfConfig.Value; + _jwtConfig = jwtConfig.Value; _tokenRevocationService = tokenRevocationService; + _jwtHelper = jwtHelper; + _logger = logger; } /// @@ -39,34 +52,21 @@ namespace EOM.TSHotelManagement.WebApi [IgnoreAntiforgeryToken] public SingleOutputDto GetCSRFToken() { - var response = new SingleOutputDto(); + var tokens = _antiforgery.GetAndStoreTokens(HttpContext); + var expiresAt = DateTime.Now.AddMinutes(_csrfConfig.TokenExpirationInMinutes); - try - { - var tokens = _antiforgery.GetAndStoreTokens(HttpContext); - var expiresAt = DateTime.Now.AddMinutes(_csrfConfig.TokenExpirationInMinutes); - var needsRefresh = false; + Response.Cookies.Append(_csrfConfig.CookieName, tokens.RequestToken); + var refreshThreshold = expiresAt.AddMinutes(-_csrfConfig.TokenRefreshThresholdInMinutes); - Response.Cookies.Append(_csrfConfig.CookieName, tokens.RequestToken); - var refreshThreshold = expiresAt.AddMinutes(-_csrfConfig.TokenRefreshThresholdInMinutes); - if (DateTime.Now >= refreshThreshold) - { - needsRefresh = true; - } - - response.Data = new CsrfTokenDto + return new SingleOutputDto + { + Data = new CsrfTokenDto { Token = tokens.RequestToken ?? string.Empty, ExpiresAt = expiresAt, - NeedsRefresh = needsRefresh - }; - } - catch (Exception) - { - response.Data = null; - } - - return response; + NeedsRefresh = DateTime.Now >= refreshThreshold + } + }; } /// @@ -91,37 +91,171 @@ namespace EOM.TSHotelManagement.WebApi public async Task> Logout() { var authorizationHeader = Request.Headers["Authorization"].ToString(); - if (!JwtTokenRevocationService.TryGetBearerToken(authorizationHeader, out var token)) + var accessTokenRevoked = false; + var refreshTokenRevoked = false; + + // 撤销Access Token + bool accessTokenNeedsRevocation = false; + if (JwtTokenRevocationService.TryGetBearerToken(authorizationHeader, out var accessToken)) { - return BuildLogoutResponse( - loggedOut: false, - message: "Authorization header missing. Treated as logged out.", - reason: "missing_authorization"); + if (!TryReadJwtToken(accessToken, out var jwtToken) || IsTokenExpired(jwtToken)) + { + // Token已过期或无效,但仍视为已登出 + } + else if (await _tokenRevocationService.IsTokenRevokedAsync(accessToken)) + { + // Token已被撤销 + } + else + { + accessTokenNeedsRevocation = true; + try + { + await _tokenRevocationService.RevokeTokenAsync(accessToken); + accessTokenRevoked = true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to revoke access token."); + } + } } - if (!TryReadJwtToken(token, out var jwtToken) || IsTokenExpired(jwtToken)) + // 撤销Refresh Token (从Cookie中获取) + bool refreshTokenNeedsRevocation = false; + var refreshToken = Request.Cookies["refreshToken"]; + if (!string.IsNullOrWhiteSpace(refreshToken)) { - return BuildLogoutResponse( - loggedOut: false, - message: "Token already invalidated.", - reason: "already_invalidated"); + if (await _tokenRevocationService.IsTokenRevokedAsync(refreshToken)) + { + // Refresh Token已被撤销 + } + else + { + refreshTokenNeedsRevocation = true; + try + { + await _tokenRevocationService.RevokeTokenAsync(refreshToken); + refreshTokenRevoked = true; + + // 清除Refresh Token Cookie + Response.Cookies.Delete("refreshToken", new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to revoke refresh token."); + } + } } - if (await _tokenRevocationService.IsTokenRevokedAsync(token)) + // 确保需要撤销的令牌都成功撤销 + if ((accessTokenNeedsRevocation && !accessTokenRevoked) || (refreshTokenNeedsRevocation && !refreshTokenRevoked)) { return BuildLogoutResponse( loggedOut: false, - message: "Token already invalidated.", - reason: "already_invalidated"); + message: "Logout failed due to token revocation error.", + reason: "token_revocation_failed"); } - await _tokenRevocationService.RevokeTokenAsync(token); return BuildLogoutResponse( loggedOut: true, message: "Logout success."); } - private static bool TryReadJwtToken(string token, out JwtSecurityToken jwtToken) + /// + /// 刷新访问令牌 + /// + /// 新的访问令牌信息 + [HttpPost] + [AllowAnonymous] + [IgnoreAntiforgeryToken] + public async Task> RefreshToken([FromBody] TokenRefreshRequestDto request) + { + if (string.IsNullOrWhiteSpace(request.RefreshToken)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Refresh token is required", "需要刷新令牌") + }; + } + + try + { + // 验证Refresh Token + var principal = _jwtHelper.ValidateAndDecryptToken(request.RefreshToken); + var employeeId = principal.FindFirst(ClaimTypes.SerialNumber)?.Value; + + if (string.IsNullOrWhiteSpace(employeeId)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Unauthorized, + Message = LocalizationHelper.GetLocalizedString("Invalid refresh token", "无效的刷新令牌") + }; + } + + // 检查Refresh Token是否被撤销 + if (await _tokenRevocationService.IsTokenRevokedAsync(request.RefreshToken)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Unauthorized, + Message = LocalizationHelper.GetLocalizedString("Refresh token has been revoked", "刷新令牌已被撤销") + }; + } + + // 生成新的Access Token + var claimsIdentity = new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.Name, principal.FindFirst(ClaimTypes.Name)?.Value ?? ""), + new Claim(ClaimTypes.SerialNumber, employeeId) + }); + + var newAccessToken = _jwtHelper.GenerateJWT(claimsIdentity); + var newRefreshToken = _jwtHelper.GenerateRefreshToken(new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.SerialNumber, employeeId), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + })); + + // 撤销旧的Refresh Token + await _tokenRevocationService.RevokeTokenAsync(request.RefreshToken); + + // 设置新的Refresh Token到Cookie + Response.Cookies.Append("refreshToken", newRefreshToken, new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict, + Expires = DateTime.Now.AddDays(_jwtConfig.RefreshTokenExpiryDays) + }); + + return new SingleOutputDto + { + Data = new TokenRefreshResponseDto + { + AccessToken = newAccessToken, + RefreshToken = newRefreshToken + } + }; + } + catch (Exception) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Unauthorized, + Message = LocalizationHelper.GetLocalizedString("Invalid refresh token", "无效的刷新令牌") + }; + } + } + + private bool TryReadJwtToken(string token, out JwtSecurityToken jwtToken) { jwtToken = null; if (string.IsNullOrWhiteSpace(token)) @@ -134,8 +268,13 @@ namespace EOM.TSHotelManagement.WebApi jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token); return jwtToken != null; } - catch + catch (ArgumentException) + { + return false; + } + catch (Exception ex) { + _logger.LogWarning(ex, "Failed to parse JWT token."); return false; } } diff --git a/EOM.TSHotelManagement.API/Controllers/SystemManagement/Administrator/AdminController.cs b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Administrator/AdminController.cs index f160b3baf86dd48e3b460899b385177c86b59547..2244739e1a50718a293248524f182e7eff054b0a 100644 --- a/EOM.TSHotelManagement.API/Controllers/SystemManagement/Administrator/AdminController.cs +++ b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Administrator/AdminController.cs @@ -3,7 +3,9 @@ using EOM.TSHotelManagement.Contract.SystemManagement.Dto.Permission; using EOM.TSHotelManagement.Service; using EOM.TSHotelManagement.WebApi.Authorization; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System; using System.Security.Claims; namespace EOM.TSHotelManagement.WebApi.Controllers @@ -37,7 +39,21 @@ namespace EOM.TSHotelManagement.WebApi.Controllers [IgnoreAntiforgeryToken] public SingleOutputDto Login([FromBody] ReadAdministratorInputDto admin) { - return adminService.Login(admin); + var result = adminService.Login(admin); + + // 如果登录成功,设置Refresh Token到HttpOnly Cookie + if (result.Code == BusinessStatusCode.Success && result.Data != null && !string.IsNullOrWhiteSpace(result.Data.RefreshToken)) + { + Response.Cookies.Append("refreshToken", result.Data.RefreshToken, new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict, + Expires = DateTime.Now.AddDays(7) // 与Refresh Token过期时间一致 + }); + } + + return result; } /// diff --git a/EOM.TSHotelManagement.API/Controllers/SystemManagement/EmployeePermission/EmployeeController.cs b/EOM.TSHotelManagement.API/Controllers/SystemManagement/EmployeePermission/EmployeePermissionController.cs similarity index 100% rename from EOM.TSHotelManagement.API/Controllers/SystemManagement/EmployeePermission/EmployeeController.cs rename to EOM.TSHotelManagement.API/Controllers/SystemManagement/EmployeePermission/EmployeePermissionController.cs diff --git a/EOM.TSHotelManagement.API/Controllers/SystemManagement/Quartz/QuartzController.cs b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Quartz/QuartzController.cs new file mode 100644 index 0000000000000000000000000000000000000000..019999a1e8c2beace1bc0c01a1f38a097ba9325e --- /dev/null +++ b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Quartz/QuartzController.cs @@ -0,0 +1,27 @@ +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Service; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.WebApi.Controllers +{ + public class QuartzController : ControllerBase + { + private readonly IQuartzAppService _quartzAppService; + + public QuartzController(IQuartzAppService quartzAppService) + { + _quartzAppService = quartzAppService; + } + + /// + /// 查询当前已注册的Quartz任务和触发器列表 + /// + /// + [HttpGet] + public async Task> SelectQuartzJobList() + { + return await _quartzAppService.SelectQuartzJobList(); + } + } +} diff --git a/EOM.TSHotelManagement.API/Controllers/SystemManagement/Role/RoleController.cs b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Role/RoleController.cs index 04c9eeb98accad45b71e311871b54db86a821bea..76573330c3733fb0740acbf5cec70ad1d1ef2d1f 100644 --- a/EOM.TSHotelManagement.API/Controllers/SystemManagement/Role/RoleController.cs +++ b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Role/RoleController.cs @@ -87,6 +87,16 @@ namespace EOM.TSHotelManagement.WebApi.Controllers return _roleAppService.ReadRolePermissions(input.RoleNumber); } + /// + /// 读取指定角色菜单和权限授权(菜单与权限独立) + /// + [RequirePermission("system:role:rrg")] + [HttpPost] + public SingleOutputDto ReadRoleGrants([FromBody] ReadByRoleNumberInputDto input) + { + return _roleAppService.ReadRoleGrants(input.RoleNumber); + } + /// /// 读取隶属于指定角色的管理员用户编码集合 /// diff --git a/EOM.TSHotelManagement.API/EOM.TSHotelManagement.API.csproj b/EOM.TSHotelManagement.API/EOM.TSHotelManagement.API.csproj index 4e8ca9d199776ef3b1a3212389728f54a9dac19e..5030be8fdd562c93d7fe195082f560da56500e3d 100644 --- a/EOM.TSHotelManagement.API/EOM.TSHotelManagement.API.csproj +++ b/EOM.TSHotelManagement.API/EOM.TSHotelManagement.API.csproj @@ -23,7 +23,7 @@ - + diff --git a/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs index f3342a52482ace48f38036a44b7503bd6eb1b8d0..66972cbf3538235d344f9e49aa5a589a1bc59bfa 100644 --- a/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs @@ -85,7 +85,7 @@ namespace EOM.TSHotelManagement.WebApi { app.MapControllers(); app.MapGet("api/version", () => - $"Software Version: {Environment.GetEnvironmentVariable("SoftwareVersion") ?? "Local Mode"}"); + $"Software Version: {SoftwareVersionHelper.GetSoftwareVersion(app.Configuration["SoftwareVersion"], "Local Mode")}"); } public static void ConfigureSwaggerUI(this WebApplication app) @@ -101,6 +101,7 @@ namespace EOM.TSHotelManagement.WebApi app.UseReDoc(config => { config.Path = "/redoc"; + config.DocumentPath = "/swagger/v1/swagger.json"; }); } } diff --git a/EOM.TSHotelManagement.API/Extensions/ClientApiGroupConvention.cs b/EOM.TSHotelManagement.API/Extensions/ClientApiGroupConvention.cs new file mode 100644 index 0000000000000000000000000000000000000000..53cdafe471a6a34d7b008cd369cefbff6cec5e24 --- /dev/null +++ b/EOM.TSHotelManagement.API/Extensions/ClientApiGroupConvention.cs @@ -0,0 +1,110 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using System; +using System.Collections.Generic; + +namespace EOM.TSHotelManagement.WebApi +{ + internal static class ClientApiGroups + { + public const string Web = "v1_Web"; + public const string Desktop = "v1_Desktop"; + public const string Mobile = "v1_Mobile"; + + public static readonly string[] All = new[] { Web, Desktop, Mobile }; + } + + internal sealed class ClientApiGroupConvention : IApplicationModelConvention + { + private static readonly HashSet MobileControllers = new(StringComparer.OrdinalIgnoreCase) + { + "CustomerAccount", + "News" + }; + + private static readonly HashSet DesktopEndpoints = new(StringComparer.OrdinalIgnoreCase) + { + "/Base/DeleteRewardPunishmentType", + "/Base/InsertRewardPunishmentType", + "/Base/SelectCustoTypeByTypeId", + "/Base/SelectDept", + "/Base/SelectDeptAllCanUse", + "/Base/SelectEducation", + "/Base/SelectGenderTypeAll", + "/Base/SelectNation", + "/Base/SelectPassPortTypeByTypeId", + "/Base/SelectPosition", + "/Base/SelectReserTypeAll", + "/Base/SelectRewardPunishmentTypeAll", + "/Base/SelectRewardPunishmentTypeAllCanUse", + "/Base/SelectRewardPunishmentTypeByTypeId", + "/Base/UpdateRewardPunishmentType", + "/Customer/UpdCustomerTypeByCustoNo", + "/CustomerAccount/Login", + "/CustomerAccount/Register", + "/Employee/UpdateEmployeeAccountPassword", + "/EmployeeCheck/SelectWorkerCheckDaySumByEmployeeId", + "/EmployeePhoto/DeleteWorkerPhoto", + "/EmployeePhoto/EmployeePhoto", + "/EmployeePhoto/UpdateWorkerPhoto", + "/EnergyManagement/InsertEnergyManagementInfo", + "/Login/RefreshCSRFToken", + "/NavBar/AddNavBar", + "/NavBar/DeleteNavBar", + "/NavBar/NavBarList", + "/NavBar/UpdateNavBar", + "/News/AddNews", + "/News/DeleteNews", + "/News/News", + "/News/SelectNews", + "/News/UpdateNews", + "/Notice/InsertNotice", + "/Notice/SelectNoticeAll", + "/Notice/SelectNoticeByNoticeNo", + "/PromotionContent/SelectPromotionContents", + "/RewardPunishment/AddRewardPunishment", + "/Role/ReadRolePermissions", + "/Room/DayByRoomNo", + "/Room/SelectCanUseRoomAllByRoomState", + "/Room/SelectFixingRoomAllByRoomState", + "/Room/SelectNotClearRoomAllByRoomState", + "/Room/SelectNotUseRoomAllByRoomState", + "/Room/SelectReservedRoomAllByRoomState", + "/Room/SelectRoomByRoomNo", + "/Room/SelectRoomByRoomPrice", + "/Room/SelectRoomByRoomState", + "/Room/SelectRoomByTypeName", + "/Room/UpdateRoomInfoWithReser", + "/Room/UpdateRoomStateByRoomNo", + "/RoomType/SelectRoomTypeByRoomNo", + "/Sellthing/SelectSellThingByNameAndPrice", + "/Spend/SelectSpendByRoomNo", + "/Spend/SeletHistorySpendInfoAll", + "/Spend/SumConsumptionAmount", + "/Utility/AddLog", + "/Utility/DeleteRequestlogByRange", + "/VipRule/SelectVipRule" + }; + + public void Apply(ApplicationModel application) + { + foreach (var controller in application.Controllers) + { + foreach (var action in controller.Actions) + { + action.ApiExplorer.GroupName = ResolveGroupName(controller.ControllerName, action.ActionName); + } + } + } + + private static string ResolveGroupName(string controllerName, string actionName) + { + if (MobileControllers.Contains(controllerName)) + { + return ClientApiGroups.Mobile; + } + + var endpoint = $"/{controllerName}/{actionName}"; + return DesktopEndpoints.Contains(endpoint) ? ClientApiGroups.Desktop : ClientApiGroups.Web; + } + } +} diff --git a/EOM.TSHotelManagement.API/Extensions/PermissionSyncExtensions.cs b/EOM.TSHotelManagement.API/Extensions/PermissionSyncExtensions.cs index 53b471808706cf310113bafcc2ad9822df228f94..52efbb6c28c153434c77e1f537b3df1fed346611 100644 --- a/EOM.TSHotelManagement.API/Extensions/PermissionSyncExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/PermissionSyncExtensions.cs @@ -67,7 +67,6 @@ namespace EOM.TSHotelManagement.WebApi // 查询已有权限 var existing = db.Queryable() - .Where(p => p.IsDelete != 1) .Select(p => p.PermissionNumber) .ToList(); @@ -88,7 +87,6 @@ namespace EOM.TSHotelManagement.WebApi Description = "Auto-synced from [RequirePermission] on API action", MenuKey = null, ParentNumber = null, - IsDelete = 0, DataInsUsr = "System", DataInsDate = now }).ToList(); @@ -104,7 +102,9 @@ namespace EOM.TSHotelManagement.WebApi // 默认将所有扫描到的权限与超级管理员角色关联 // 如角色不存在,DatabaseInitializer 已负责创建;此处仅做映射补齐 var existingRolePerms = db.Queryable() - .Where(rp => rp.RoleNumber == AdminRoleNumber && rp.IsDelete != 1) + .Where(rp => rp.RoleNumber == AdminRoleNumber + && rp.PermissionNumber != null + && rp.PermissionNumber != "") .Select(rp => rp.PermissionNumber) .ToList(); @@ -121,7 +121,6 @@ namespace EOM.TSHotelManagement.WebApi { RoleNumber = AdminRoleNumber, PermissionNumber = p, - IsDelete = 0, DataInsUsr = "System", DataInsDate = now }).ToList(); @@ -161,4 +160,4 @@ namespace EOM.TSHotelManagement.WebApi return "api"; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs index 14b066ad61bcb5e91ec920a3407d194bbbb28cd6..a4ff3ea8189d9ab890c51d1cb5c5b05be9816759 100644 --- a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs @@ -1,4 +1,6 @@ +using EOM.TSHotelManagement.API.Filters; using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob; using EOM.TSHotelManagement.Infrastructure; using EOM.TSHotelManagement.Service; using EOM.TSHotelManagement.WebApi.Authorization; @@ -20,6 +22,7 @@ using NSwag; using NSwag.Generation.Processors.Security; using Quartz; using System; +using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; @@ -59,57 +62,35 @@ namespace EOM.TSHotelManagement.WebApi services.AddQuartz(q => { var jobs = configuration.GetSection(SystemConstant.JobKeys.Code).Get() ?? Array.Empty(); + var jobRegistrations = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + [nameof(ReservationExpirationCheckJob)] = (configurator, jobName, cronExpression) => + RegisterJobAndTrigger(configurator, jobName, cronExpression), + [nameof(MailServiceCheckJob)] = (configurator, jobName, cronExpression) => + RegisterJobAndTrigger(configurator, jobName, cronExpression), + [nameof(ImageHostingServiceCheckJob)] = (configurator, jobName, cronExpression) => + RegisterJobAndTrigger(configurator, jobName, cronExpression), + [nameof(RedisServiceCheckJob)] = (configurator, jobName, cronExpression) => + RegisterJobAndTrigger(configurator, jobName, cronExpression), + [nameof(AutomaticallyUpgradeMembershipLevelJob)] = (configurator, jobName, cronExpression) => + RegisterJobAndTrigger(configurator, jobName, cronExpression) + }; + var registeredJobs = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var job in jobs) + foreach (var job in jobs.Where(a => !string.IsNullOrWhiteSpace(a)).Distinct(StringComparer.OrdinalIgnoreCase)) { - var reservationJobKey = $"{job}-Reservation"; - var mailJobKey = $"{job}-Mail"; - var imageHostingJobKey = $"{job}-ImageHosting"; - var redisJobKey = $"{job}-Redis"; - - q.AddJob(opts => opts - .WithIdentity(reservationJobKey) - .StoreDurably() - .WithDescription($"{reservationJobKey} 定时作业")); - - q.AddTrigger(opts => opts - .ForJob(reservationJobKey) - .WithIdentity($"{reservationJobKey}-Trigger") - //.WithCronSchedule("* * * * * ? ")); // Debug Use Only 每秒执行一次 - .WithCronSchedule("0 0 1 * * ?")); // 每天1:00 AM执行 - - q.AddJob(opts => opts - .WithIdentity(mailJobKey) - .StoreDurably() - .WithDescription($"{mailJobKey} 定时作业")); - - q.AddTrigger(opts => opts - .ForJob(mailJobKey) - .WithIdentity($"{mailJobKey}-Trigger") - //.WithCronSchedule("* * * * * ? ")); // Debug Use Only 每秒执行一次 - .WithCronSchedule("0 */5 * * * ?")); // 每5分钟执行一次 - - q.AddJob(opts => opts - .WithIdentity(imageHostingJobKey) - .StoreDurably() - .WithDescription($"{imageHostingJobKey} 定时作业")); - - q.AddTrigger(opts => opts - .ForJob(imageHostingJobKey) - .WithIdentity($"{imageHostingJobKey}-Trigger") - //.WithCronSchedule("* * * * * ? ")); // Debug Use Only 每秒执行一次 - .WithCronSchedule("0 */5 * * * ?")); // 每5分钟执行一次 - - q.AddJob(opts => opts - .WithIdentity(redisJobKey) - .StoreDurably() - .WithDescription($"{redisJobKey} 定时作业")); - - q.AddTrigger(opts => opts - .ForJob(redisJobKey) - .WithIdentity($"{redisJobKey}-Trigger") - //.WithCronSchedule("* * * * * ? ")); // Debug Use Only 每秒执行一次 - .WithCronSchedule("0 */5 * * * ?")); // 每5分钟执行一次 + var (jobName, cronExpression) = ParseJobRegistration(job); + if (!registeredJobs.Add(jobName)) + { + throw new InvalidOperationException($"Duplicate quartz job configuration found for '{jobName}'."); + } + + if (!jobRegistrations.TryGetValue(jobName, out var register)) + { + throw new InvalidOperationException($"Unsupported quartz job '{jobName}' in '{SystemConstant.JobKeys.Code}'."); + } + + register(q, jobName, cronExpression); } }); @@ -121,6 +102,41 @@ namespace EOM.TSHotelManagement.WebApi }); } + private static void RegisterJobAndTrigger(IServiceCollectionQuartzConfigurator quartz, string jobName, string cronExpression) + where TJob : IJob + { + quartz.AddJob(opts => opts + .WithIdentity(jobName) + .StoreDurably() + .WithDescription($"{jobName} 定时作业")); + + quartz.AddTrigger(opts => opts + .ForJob(jobName) + .WithIdentity($"{jobName}-Trigger") + .WithCronSchedule(cronExpression)); + } + + private static (string JobName, string CronExpression) ParseJobRegistration( + string jobConfiguration) + { + var entry = jobConfiguration?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(entry)) + { + throw new InvalidOperationException($"Invalid quartz job configuration value in '{SystemConstant.JobKeys.Code}'."); + } + + var separatorIndex = entry.IndexOf(':'); + if (separatorIndex > 0 && separatorIndex < entry.Length - 1) + { + var jobName = entry[..separatorIndex].Trim(); + var cronExpression = entry[(separatorIndex + 1)..].Trim(); + return (jobName, cronExpression); + } + + throw new InvalidOperationException( + $"Quartz job '{entry}' is missing cron expression. Use 'JobName:CronExpression' format."); + } + public static void ConfigureXForward(this IServiceCollection services) { services.Configure(options => @@ -241,10 +257,14 @@ namespace EOM.TSHotelManagement.WebApi public static void ConfigureControllers(this IServiceCollection services) { + services.AddScoped(); + services.AddControllers(options => { options.Filters.Add(); + options.Filters.Add(); options.Conventions.Add(new AuthorizeAllControllersConvention()); + options.Conventions.Add(new ClientApiGroupConvention()); options.RespectBrowserAcceptHeader = true; options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true; }).AddJsonOptions(options => @@ -253,6 +273,7 @@ namespace EOM.TSHotelManagement.WebApi options.JsonSerializerOptions.DictionaryKeyPolicy = null; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter()); }) .ConfigureApiBehaviorOptions(options => @@ -288,6 +309,73 @@ namespace EOM.TSHotelManagement.WebApi config.Title = "TS酒店管理系统API说明文档"; config.Version = "v1"; config.DocumentName = "v1"; + config.ApiGroupNames = ClientApiGroups.All; + + config.OperationProcessors.Add(new CSRFTokenOperationProcessor()); + + config.AddSecurity("JWT", new OpenApiSecurityScheme + { + Type = OpenApiSecuritySchemeType.Http, + In = OpenApiSecurityApiKeyLocation.Header, + Name = "Authorization", + Description = "Type into the textbox: your JWT token", + Scheme = "bearer", + BearerFormat = "JWT" + }); + + config.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT")); + }); + + services.AddOpenApiDocument(config => + { + config.Title = "TS Hotel Management System API - Web"; + config.Version = ClientApiGroups.Web; + config.DocumentName = ClientApiGroups.Web; + config.ApiGroupNames = new[] { ClientApiGroups.Web }; + + config.OperationProcessors.Add(new CSRFTokenOperationProcessor()); + + config.AddSecurity("JWT", new OpenApiSecurityScheme + { + Type = OpenApiSecuritySchemeType.Http, + In = OpenApiSecurityApiKeyLocation.Header, + Name = "Authorization", + Description = "Type into the textbox: your JWT token", + Scheme = "bearer", + BearerFormat = "JWT" + }); + + config.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT")); + }); + + services.AddOpenApiDocument(config => + { + config.Title = "TS Hotel Management System API - Desktop"; + config.Version = ClientApiGroups.Desktop; + config.DocumentName = ClientApiGroups.Desktop; + config.ApiGroupNames = new[] { ClientApiGroups.Desktop }; + + config.OperationProcessors.Add(new CSRFTokenOperationProcessor()); + + config.AddSecurity("JWT", new OpenApiSecurityScheme + { + Type = OpenApiSecuritySchemeType.Http, + In = OpenApiSecurityApiKeyLocation.Header, + Name = "Authorization", + Description = "Type into the textbox: your JWT token", + Scheme = "bearer", + BearerFormat = "JWT" + }); + + config.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT")); + }); + + services.AddOpenApiDocument(config => + { + config.Title = "TS Hotel Management System API - Mobile"; + config.Version = ClientApiGroups.Mobile; + config.DocumentName = ClientApiGroups.Mobile; + config.ApiGroupNames = new[] { ClientApiGroups.Mobile }; config.OperationProcessors.Add(new CSRFTokenOperationProcessor()); diff --git a/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs b/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs new file mode 100644 index 0000000000000000000000000000000000000000..4479486d9067e7ebf1213854aa4a5398497232f7 --- /dev/null +++ b/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs @@ -0,0 +1,456 @@ +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Data; +using EOM.TSHotelManagement.Domain; +using EOM.TSHotelManagement.WebApi.Authorization; +using jvncorelib.CodeLib; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.WebApi.Filters +{ + public class BusinessOperationAuditFilter : IAsyncActionFilter + { + private static readonly ConcurrentDictionary PropertyCache = new(); + private static readonly Regex LogControlCharsRegex = new(@"[\p{Cc}\p{Cf}]+", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private static readonly HashSet ExcludedControllers = new(StringComparer.OrdinalIgnoreCase) + { + "Login", + "Utility", + "CustomerAccount", + "News" + }; + + private static readonly string[] ReadOnlyActionPrefixes = + { + "Select", + "Read", + "Get", + "Build" + }; + + private static readonly string[] SensitivePropertyKeywords = + { + "password", + "token", + "secret", + "recoverycode", + "verificationcode", + "otp", + "creditcard", + "ssn", + "bankaccount", + "phonenumber" + }; + + private readonly GenericRepository operationLogRepository; + private readonly ILogger logger; + + public BusinessOperationAuditFilter( + GenericRepository operationLogRepository, + ILogger logger) + { + this.operationLogRepository = operationLogRepository; + this.logger = logger; + } + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (!ShouldAudit(context)) + { + await next(); + return; + } + + var executedContext = await next(); + await TryWriteAuditLogAsync(context, executedContext); + } + + private static bool ShouldAudit(ActionExecutingContext context) + { + if (context.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) + { + return false; + } + + if (!IsBusinessController(actionDescriptor)) + { + return false; + } + + if (ExcludedControllers.Contains(actionDescriptor.ControllerName)) + { + return false; + } + + if (HttpMethods.IsGet(context.HttpContext.Request.Method) + || HttpMethods.IsHead(context.HttpContext.Request.Method) + || HttpMethods.IsOptions(context.HttpContext.Request.Method)) + { + return false; + } + + return !ReadOnlyActionPrefixes.Any(prefix => + actionDescriptor.ActionName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + } + + private static bool IsBusinessController(ControllerActionDescriptor actionDescriptor) + { + if (actionDescriptor.ControllerTypeInfo.IsDefined(typeof(BusinessOperationAuditAttribute), inherit: true)) + { + return true; + } + + var controllerNamespace = actionDescriptor.ControllerTypeInfo.Namespace ?? string.Empty; + return controllerNamespace.Contains(".Controllers.Business.", StringComparison.OrdinalIgnoreCase); + } + + private async Task TryWriteAuditLogAsync(ActionExecutingContext context, ActionExecutedContext executedContext) + { + try + { + var httpContext = context?.HttpContext; + if (httpContext == null) + { + logger.LogWarning("HttpContext is null, skipping audit log."); + return; + } + + var request = httpContext.Request; + var path = request.Path.HasValue ? request.Path.Value! : string.Empty; + var isChinese = IsChineseLanguage(httpContext); + var operationAccount = ClaimsPrincipalExtensions.GetUserNumber(httpContext.User); + if (string.IsNullOrWhiteSpace(operationAccount)) + { + operationAccount = Localize(isChinese, "Anonymous", "匿名用户"); + } + + var (isSuccess, responseCode, responseMessage) = ResolveExecutionResult(executedContext); + var argumentSummary = BuildArgumentSummary(context.ActionArguments, isChinese); + var statusText = Localize(isChinese, isSuccess ? "Success" : "Failed", isSuccess ? "成功" : "失败"); + var logContent = BuildLogContent( + isChinese, + statusText, + request.Method, + path, + operationAccount, + argumentSummary, + responseCode, + responseMessage); + + var operationLog = new OperationLog + { + OperationId = new UniqueCode().GetNewId("OP-"), + OperationTime = DateTime.Now, + LogContent = logContent, + LoginIpAddress = httpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty, + OperationAccount = operationAccount, + LogLevel = isSuccess ? (int)Common.LogLevel.Normal : (int)Common.LogLevel.Warning, + SoftwareVersion = SoftwareVersionHelper.GetSoftwareVersion(), + DataInsUsr = operationAccount, + DataInsDate = DateTime.Now + }; + + operationLogRepository.Insert(operationLog); + } + catch (Exception ex) + { + var path = context?.HttpContext?.Request?.Path.Value ?? string.Empty; + logger.LogWarning(ex, "Failed to write business operation audit log for {Path}", path); + } + + await Task.CompletedTask; + } + + private static string BuildLogContent( + bool isChinese, + string statusText, + string method, + string path, + string operationAccount, + string argumentSummary, + int responseCode, + string responseMessage) + { + var safeStatusText = SanitizeForLog(statusText, 32); + var safeMethod = SanitizeForLog(method, 16); + var safePath = SanitizeForLog(path, 240); + var safeOperationAccount = SanitizeForLog(operationAccount, 80); + var safeArgumentSummary = SanitizeForLog(argumentSummary, 900); + var localizedResponseMessage = string.IsNullOrWhiteSpace(responseMessage) + ? Localize(isChinese, "None", "无") + : SanitizeForLog(responseMessage, 300); + + var content = Localize( + isChinese, + $"{safeStatusText} {safeMethod} {safePath} | User={safeOperationAccount} | Args={safeArgumentSummary} | ResponseCode={responseCode} | Message={localizedResponseMessage}", + $"{safeStatusText} {safeMethod} {safePath} | 操作人={safeOperationAccount} | 参数={safeArgumentSummary} | 响应码={responseCode} | 响应信息={localizedResponseMessage}"); + + return TrimLogContent(content, isChinese); + } + + private static string SanitizeForLog(string value, int maxLength = 300) + { + var sanitized = LogControlCharsRegex.Replace(value ?? string.Empty, " ").Trim(); + return sanitized.Length <= maxLength ? sanitized : sanitized[..(maxLength - 3)] + "..."; + } + + private static (bool IsSuccess, int ResponseCode, string ResponseMessage) ResolveExecutionResult(ActionExecutedContext executedContext) + { + if (executedContext.Exception != null && !executedContext.ExceptionHandled) + { + return (false, BusinessStatusCode.InternalServerError, executedContext.Exception.Message); + } + + var baseResponse = ExtractBaseResponse(executedContext.Result); + if (baseResponse != null) + { + return (baseResponse.Success, baseResponse.Code, baseResponse.Message ?? string.Empty); + } + + if (executedContext.Result is StatusCodeResult statusCodeResult) + { + return (statusCodeResult.StatusCode < 400, statusCodeResult.StatusCode, string.Empty); + } + + return (true, 0, string.Empty); + } + + private static BaseResponse ExtractBaseResponse(IActionResult result) + { + return result switch + { + ObjectResult objectResult when objectResult.Value is BaseResponse baseResponse => baseResponse, + JsonResult jsonResult when jsonResult.Value is BaseResponse baseResponse => baseResponse, + _ => null + }; + } + + private static string BuildArgumentSummary(IDictionary arguments, bool isChinese) + { + if (arguments == null || arguments.Count == 0) + { + return Localize(isChinese, "None", "无"); + } + + var items = arguments + .Where(a => a.Value != null) + .Select(a => $"{SanitizeForLog(a.Key, 60)}={SummarizeValue(a.Key, a.Value, isChinese)}") + .Where(a => !string.IsNullOrWhiteSpace(a)) + .ToList(); + + return items.Count == 0 ? Localize(isChinese, "None", "无") : string.Join("; ", items); + } + + private static string SummarizeValue(string name, object value, bool isChinese) + { + if (value == null) + { + return Localize(isChinese, "null", "空"); + } + + if (IsSensitive(name)) + { + return "***"; + } + + if (value is IFormFile file) + { + var safeFileName = SanitizeForLog(file.FileName, 120); + return Localize( + isChinese, + $"File({safeFileName}, {file.Length} bytes)", + $"文件({safeFileName}, {file.Length} 字节)"); + } + + if (IsSimpleValue(value.GetType())) + { + return FormatSimpleValue(value); + } + + if (value is IEnumerable enumerable && value is not string) + { + return SummarizeEnumerable(enumerable, isChinese); + } + + return SummarizeComplexObject(value, isChinese); + } + + private static string SummarizeEnumerable(IEnumerable enumerable, bool isChinese) + { + var count = 0; + var previews = new List(); + + foreach (var item in enumerable) + { + count++; + if (previews.Count >= 3) + { + continue; + } + + previews.Add(item == null ? Localize(isChinese, "null", "空") : FormatSimpleValue(item)); + } + + return previews.Count == 0 + ? "[]" + : Localize( + isChinese, + $"[{string.Join(", ", previews)}{(count > previews.Count ? ", ..." : string.Empty)}] (Count={count})", + $"[{string.Join(", ", previews)}{(count > previews.Count ? ", ..." : string.Empty)}](数量={count})"); + } + + private static string SummarizeComplexObject(object value, bool isChinese) + { + var type = value.GetType(); + var properties = GetCachedProperties(type) + .Take(8) + .Select(p => + { + object propertyValue; + try + { + propertyValue = p.GetValue(value); + } + catch + { + return null; + } + + if (propertyValue == null) + { + return null; + } + + var safePropertyName = SanitizeForLog(p.Name, 60); + + if (IsSensitive(p.Name)) + { + return $"{safePropertyName}=***"; + } + + if (propertyValue is IFormFile file) + { + var safeFileName = SanitizeForLog(file.FileName, 120); + return Localize( + isChinese, + $"{safePropertyName}=File({safeFileName}, {file.Length} bytes)", + $"{safePropertyName}=文件({safeFileName}, {file.Length} 字节)"); + } + + if (IsSimpleValue(propertyValue.GetType())) + { + return $"{safePropertyName}={FormatSimpleValue(propertyValue)}"; + } + + if (propertyValue is IEnumerable enumerable && propertyValue is not string) + { + return $"{safePropertyName}={SummarizeEnumerable(enumerable, isChinese)}"; + } + + return null; + }) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .ToList(); + + var safeTypeName = SanitizeForLog(type.Name, 80); + return properties.Count == 0 + ? safeTypeName + : $"{safeTypeName}({string.Join(", ", properties)})"; + } + + private static PropertyInfo[] GetCachedProperties(Type type) + { + return PropertyCache.GetOrAdd(type, static currentType => currentType + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CanRead && p.GetIndexParameters().Length == 0) + .ToArray()); + } + + private static bool IsSimpleValue(Type type) + { + var actualType = Nullable.GetUnderlyingType(type) ?? type; + return actualType.IsPrimitive + || actualType.IsEnum + || actualType == typeof(string) + || actualType == typeof(decimal) + || actualType == typeof(Guid) + || actualType == typeof(DateTime) + || actualType == typeof(DateTimeOffset) + || actualType == typeof(TimeSpan); + } + + private static bool IsSensitive(string name) + { + var normalized = name?.Replace("_", string.Empty, StringComparison.OrdinalIgnoreCase) ?? string.Empty; + return SensitivePropertyKeywords.Any(keyword => + normalized.Contains(keyword, StringComparison.OrdinalIgnoreCase)); + } + + private static string FormatSimpleValue(object value) + { + var formatted = value switch + { + DateTime dateTime => dateTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), + DateTimeOffset dateTimeOffset => dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture), + string text => text, + _ => Convert.ToString(value, CultureInfo.InvariantCulture) ?? value.ToString() ?? string.Empty + }; + + return SanitizeForLog(formatted, 120); + } + + private static string TrimLogContent(string content, bool isChinese) + { + if (string.IsNullOrWhiteSpace(content)) + { + return Localize(isChinese, "Operation audit log", "业务操作审计日志"); + } + + return SanitizeForLog(content, 1900); + } + + private static bool IsChineseLanguage(HttpContext httpContext) + { + var acceptLanguage = httpContext.Request.Headers.AcceptLanguage.ToString(); + if (!string.IsNullOrWhiteSpace(acceptLanguage)) + { + var firstLanguage = acceptLanguage + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(item => item.Split(';', 2, StringSplitOptions.TrimEntries)[0]) + .FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(firstLanguage)) + { + return firstLanguage.StartsWith("zh", StringComparison.OrdinalIgnoreCase); + } + } + + var cultureName = CultureInfo.CurrentUICulture?.Name; + if (!string.IsNullOrWhiteSpace(cultureName)) + { + return cultureName.StartsWith("zh", StringComparison.OrdinalIgnoreCase); + } + + return false; + } + + private static string Localize(bool isChinese, string englishText, string chineseText) + { + return isChinese ? chineseText : englishText; + } + } +} diff --git a/EOM.TSHotelManagement.API/Filters/RequestLoggingMiddleware.cs b/EOM.TSHotelManagement.API/Filters/RequestLoggingMiddleware.cs index ddad22c74577a1301d1dbc4f35007eddb06da29c..a930483960c1ee0fec7858c7434f94192be22738 100644 --- a/EOM.TSHotelManagement.API/Filters/RequestLoggingMiddleware.cs +++ b/EOM.TSHotelManagement.API/Filters/RequestLoggingMiddleware.cs @@ -25,9 +25,7 @@ public class RequestLoggingMiddleware { _next = next; _logger = logger; - _softwareVersion = Environment.GetEnvironmentVariable("APP_VERSION") - ?? config["SoftwareVersion"] - ?? GetDefaultVersion(); + _softwareVersion = EOM.TSHotelManagement.Common.SoftwareVersionHelper.GetSoftwareVersion(config["SoftwareVersion"]); } public async Task InvokeAsync(HttpContext context) @@ -147,30 +145,6 @@ public class RequestLoggingMiddleware return (long)((stop - start) * 1000 / (double)Stopwatch.Frequency); } - private string GetDefaultVersion() - { - try - { - var rootPath = Path.GetDirectoryName(GetType().Assembly.Location); - var versionFilePath = Path.Combine(rootPath, "version.txt"); - - if (File.Exists(versionFilePath)) - { - var versionContent = File.ReadAllText(versionFilePath).Trim(); - if (!string.IsNullOrWhiteSpace(versionContent)) - { - return versionContent; - } - } - } - catch (Exception ex) - { - Debug.WriteLine($"Error reading version.txt: {ex.Message}"); - } - - return GetType().Assembly.GetName().Version?.ToString(3) ?? "1.0.0"; - } - private async Task GetRequestParametersAsync(HttpRequest request) { if (request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase)) @@ -245,4 +219,4 @@ public class RequestLoggingMiddleware ip.StartsWith("172.30.") || ip.StartsWith("172.31."); } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.API/Filters/ValidationFilter.cs b/EOM.TSHotelManagement.API/Filters/ValidationFilter.cs index 989b0e68ed7cd2d687b04fe7aa92fc15fcc9b977..7cadfb5c3543ec5f996fd4a931286a9efb96e7f2 100644 --- a/EOM.TSHotelManagement.API/Filters/ValidationFilter.cs +++ b/EOM.TSHotelManagement.API/Filters/ValidationFilter.cs @@ -1,10 +1,11 @@ -using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common; using EOM.TSHotelManagement.Contract; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System.Collections.Generic; +using System.Linq; -namespace EOM.TSHotelManagement.WebApi.Filters +namespace EOM.TSHotelManagement.API.Filters { public class ValidationFilter : IActionFilter { @@ -12,15 +13,9 @@ namespace EOM.TSHotelManagement.WebApi.Filters { if (!context.ModelState.IsValid) { - var errors = new List(); - - foreach (var modelState in context.ModelState.Values) - { - foreach (var error in modelState.Errors) - { - errors.Add(error.ErrorMessage); - } - } + var errors = context.ModelState.Values.SelectMany(v => v.Errors) + .Select(e => e.ErrorMessage) + .ToList(); var response = new BaseResponse { diff --git a/EOM.TSHotelManagement.API/appsettings.Application.json b/EOM.TSHotelManagement.API/appsettings.Application.json index b77796a8dd15e4a5d466c257ffb92b47f94e8cc8..f5f5b47fb267df1d8a2f3d130d20eaf323e1211f 100644 --- a/EOM.TSHotelManagement.API/appsettings.Application.json +++ b/EOM.TSHotelManagement.API/appsettings.Application.json @@ -12,9 +12,17 @@ ], "AllowedHosts": "*", "JobKeys": [ - "ReservationExpirationCheckJob", - "MailServiceCheckJob", - "RedisServiceCheckJob" + // For production + "ReservationExpirationCheckJob:0 0 1 * * ?", + "MailServiceCheckJob:0 */5 * * * ?", + "RedisServiceCheckJob:0 */5 * * * ?", + "AutomaticallyUpgradeMembershipLevelJob:0 */5 * * * ?" + + // For testing "* * * * * ? " + //"ReservationExpirationCheckJob:* * * * * ? ", + //"MailServiceCheckJob:* * * * * ? ", + //"RedisServiceCheckJob:* * * * * ? ", + //"AutomaticallyUpgradeMembershipLevelJob:* * * * * ? " ], "ExpirationSettings": { "NotifyDaysBefore": 3, diff --git a/EOM.TSHotelManagement.API/appsettings.Database.json b/EOM.TSHotelManagement.API/appsettings.Database.json index f4844c299062d2f9cfed4867e12420aadcb53eab..0c7186c7c647d9cc19897377a9a7ecbde3c778db 100644 --- a/EOM.TSHotelManagement.API/appsettings.Database.json +++ b/EOM.TSHotelManagement.API/appsettings.Database.json @@ -10,7 +10,15 @@ "Redis": { "Enabled": false, "ConnectionString": "host:port,password=your_redis_password", - "DefaultDatabase": 0 + "DefaultDatabase": 1, + "ConnectTimeoutMs": 5000, + "AsyncTimeoutMs": 2000, + "SyncTimeoutMs": 2000, + "KeepAliveSeconds": 15, + "ConnectRetry": 3, + "ReconnectRetryBaseDelayMs": 3000, + "OperationTimeoutMs": 1200, + "FailureCooldownSeconds": 30 }, "InitializeDatabase": false } diff --git a/EOM.TSHotelManagement.API/appsettings.Services.json b/EOM.TSHotelManagement.API/appsettings.Services.json index 98919c7f93592bcdd935169956c2c5fa02e0756f..33e25ae5ae0037c47f62e4ee716534ae998472a3 100644 --- a/EOM.TSHotelManagement.API/appsettings.Services.json +++ b/EOM.TSHotelManagement.API/appsettings.Services.json @@ -1,7 +1,8 @@ { "Jwt": { "Key": "", - "ExpiryMinutes": 20 + "ExpiryMinutes": 15, + "RefreshTokenExpiryDays": 7 }, "Mail": { "Enabled": false, diff --git a/EOM.TSHotelManagement.Common/Constant/CheckTypeConstant.cs b/EOM.TSHotelManagement.Common/Constant/CheckTypeConstant.cs new file mode 100644 index 0000000000000000000000000000000000000000..9568ac66bf82ab4767205a280dc9dd86885f8e0d --- /dev/null +++ b/EOM.TSHotelManagement.Common/Constant/CheckTypeConstant.cs @@ -0,0 +1,22 @@ +using EOM.TSHotelManagement.Infrastructure; +using System; +using System.Collections.Generic; +using System.Text; + +namespace EOM.TSHotelManagement.Common; + +public class CheckTypeConstant : CodeConstantBase +{ + /// + /// 客户端 + /// + public static readonly CheckTypeConstant Client = new CheckTypeConstant("Client", "客户端"); + /// + /// 网页端 + /// + public static readonly CheckTypeConstant Web = new CheckTypeConstant("Web", "网页端"); + private CheckTypeConstant(string code, string description) : base(code, description) + { + + } +} diff --git a/EOM.TSHotelManagement.Common/Constant/SpendTypeConstant.cs b/EOM.TSHotelManagement.Common/Constant/SpendTypeConstant.cs index c03aee1695da5acd98a883f474e5f2b038246ecc..a1dca3720c7b3e978a7a8f5dcdff30c56ffaa789 100644 --- a/EOM.TSHotelManagement.Common/Constant/SpendTypeConstant.cs +++ b/EOM.TSHotelManagement.Common/Constant/SpendTypeConstant.cs @@ -1,10 +1,10 @@ -using EOM.TSHotelManagement.Infrastructure; +using EOM.TSHotelManagement.Infrastructure; namespace EOM.TSHotelManagement.Common { public class SpendTypeConstant : CodeConstantBase { - public static readonly SpendTypeConstant Product = new SpendTypeConstant("Product", "商品"); + public static readonly SpendTypeConstant Product = new SpendTypeConstant("Goods", "商品"); public static readonly SpendTypeConstant Room = new SpendTypeConstant("Room", "房间"); public static readonly SpendTypeConstant Other = new SpendTypeConstant("Other", "其他"); diff --git a/EOM.TSHotelManagement.Common/EOM.TSHotelManagement.Common.csproj b/EOM.TSHotelManagement.Common/EOM.TSHotelManagement.Common.csproj index a0d5fece4ab1882fc2fd811c905c27459c354859..1f099603be5c6402db6c64340e0be20a841ca24b 100644 --- a/EOM.TSHotelManagement.Common/EOM.TSHotelManagement.Common.csproj +++ b/EOM.TSHotelManagement.Common/EOM.TSHotelManagement.Common.csproj @@ -20,18 +20,18 @@ - - + + - + + - + - diff --git a/EOM.TSHotelManagement.Common/Helper/ClaimsPrincipalExtensions.cs b/EOM.TSHotelManagement.Common/Helper/ClaimsPrincipalExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..91e6dbc58b60efed4e25b1bb95bff32af14cb1e0 --- /dev/null +++ b/EOM.TSHotelManagement.Common/Helper/ClaimsPrincipalExtensions.cs @@ -0,0 +1,15 @@ +using System.Security.Claims; + +namespace EOM.TSHotelManagement.Common +{ + public static class ClaimsPrincipalExtensions + { + public static string GetUserNumber(this ClaimsPrincipal? principal) + { + return principal?.FindFirst(ClaimTypes.SerialNumber)?.Value + ?? principal?.FindFirst("serialnumber")?.Value + ?? principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value + ?? string.Empty; + } + } +} diff --git a/EOM.TSHotelManagement.Common/Helper/DataProtectionHelper.cs b/EOM.TSHotelManagement.Common/Helper/DataProtectionHelper.cs index 8241879cae3b29d555e3baba3eef53fdd76ddfcd..267eb3325d8e3f9ced25cda94e7d051b5909f862 100644 --- a/EOM.TSHotelManagement.Common/Helper/DataProtectionHelper.cs +++ b/EOM.TSHotelManagement.Common/Helper/DataProtectionHelper.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection; using System.Security.Cryptography; namespace EOM.TSHotelManagement.Common @@ -67,6 +67,10 @@ namespace EOM.TSHotelManagement.Common private string EncryptData(string plainText, IDataProtector protector) { + if (LooksLikeDataProtectionPayload(plainText)) + { + return plainText; + } return string.IsNullOrEmpty(plainText) ? plainText : protector.Protect(plainText); diff --git a/EOM.TSHotelManagement.Common/Helper/EntityMapper.cs b/EOM.TSHotelManagement.Common/Helper/EntityMapper.cs index db5d8687c3ce69a1892aeb2c0cb5c4cd9ee4ad40..0599119c6c318f1108dbca845a2d5341ef917448 100644 --- a/EOM.TSHotelManagement.Common/Helper/EntityMapper.cs +++ b/EOM.TSHotelManagement.Common/Helper/EntityMapper.cs @@ -2,19 +2,95 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Mapster; namespace EOM.TSHotelManagement.Common { public static class EntityMapper { /// - /// 映射单个实体 + /// Maps a single object instance. /// public static TDestination Map(TSource source) where TDestination : new() { if (source == null) return default; + try + { + var destination = source.Adapt(); + ApplyLegacyOverrides(source, destination); + return destination; + } + catch + { + return LegacyMap(source); + } + } + + /// + /// Maps a list of objects. + /// + public static List MapList(List sourceList) + where TDestination : new() + { + return sourceList?.Select(Map).ToList(); + } + + // Preserve the legacy edge cases while Mapster handles the common path. + private static void ApplyLegacyOverrides(TSource source, TDestination destination) + { + var sourceProperties = typeof(TSource).GetProperties( + BindingFlags.Public | BindingFlags.Instance); + var destinationProperties = typeof(TDestination).GetProperties( + BindingFlags.Public | BindingFlags.Instance); + + foreach (var destinationProperty in destinationProperties) + { + if (!destinationProperty.CanWrite) continue; + + var exactSourceProperty = sourceProperties + .SingleOrDefault(p => p.Name.Equals(destinationProperty.Name, StringComparison.Ordinal)); + var matchedSourceProperty = exactSourceProperty ?? sourceProperties + .SingleOrDefault(p => p.Name.Equals(destinationProperty.Name, StringComparison.OrdinalIgnoreCase)); + + if (matchedSourceProperty == null) continue; + + var sourceValue = matchedSourceProperty.GetValue(source); + + if (sourceValue == null) + { + if (destinationProperty.Name.Equals("RowVersion", StringComparison.OrdinalIgnoreCase) + && destinationProperty.PropertyType == typeof(long)) + { + destinationProperty.SetValue(destination, 0L); + continue; + } + + if (destinationProperty.PropertyType.IsValueType && + Nullable.GetUnderlyingType(destinationProperty.PropertyType) != null) + { + destinationProperty.SetValue(destination, null); + } + + continue; + } + + if (matchedSourceProperty.Name.Equals(destinationProperty.Name, StringComparison.Ordinal) && + !NeedConversion(matchedSourceProperty.PropertyType, destinationProperty.PropertyType)) + { + continue; + } + + destinationProperty.SetValue( + destination, + ConvertValue(sourceValue, destinationProperty.PropertyType)); + } + } + + private static TDestination LegacyMap(TSource source) + where TDestination : new() + { var destination = new TDestination(); var sourceProperties = typeof(TSource).GetProperties( @@ -34,23 +110,8 @@ namespace EOM.TSHotelManagement.Common var sourceValue = sourceProperty.GetValue(source); - if (sourceValue is DateOnly dateOnlyValue && - destinationProperty.PropertyType == typeof(DateTime?)) - { - if (dateOnlyValue == DateOnly.MinValue || dateOnlyValue == new DateOnly(1900, 1, 1)) - { - destinationProperty.SetValue(destination, null); - } - else - { - destinationProperty.SetValue(destination, dateOnlyValue.ToDateTime(TimeOnly.MinValue)); - } - continue; - } - if (sourceValue == null) { - // Ensure optimistic-lock version does not silently fall back to entity defaults. if (destinationProperty.Name.Equals("RowVersion", StringComparison.OrdinalIgnoreCase) && destinationProperty.PropertyType == typeof(long)) { @@ -63,22 +124,30 @@ namespace EOM.TSHotelManagement.Common { destinationProperty.SetValue(destination, null); } - continue; - } - if (NeedConversion(sourceProperty.PropertyType, destinationProperty.PropertyType)) - { - sourceValue = SmartConvert(sourceValue, destinationProperty.PropertyType); + continue; } - destinationProperty.SetValue(destination, sourceValue); + destinationProperty.SetValue( + destination, + ConvertValue(sourceValue, destinationProperty.PropertyType)); } return destination; } + private static object ConvertValue(object value, Type targetType) + { + if (!NeedConversion(value.GetType(), targetType)) + { + return value; + } + + return SmartConvert(value, targetType); + } + /// - /// 智能类型转换 + /// Performs legacy type conversions. /// private static object SmartConvert(object value, Type targetType) { @@ -117,7 +186,6 @@ namespace EOM.TSHotelManagement.Common } catch { - } } @@ -127,7 +195,7 @@ namespace EOM.TSHotelManagement.Common } /// - /// 检查是否为最小值 + /// Checks whether a value is treated as a legacy minimum sentinel. /// private static bool IsMinValue(object value) { @@ -141,7 +209,7 @@ namespace EOM.TSHotelManagement.Common } /// - /// 将最小值转换为空值 + /// Converts legacy minimum sentinel values to null or empty string. /// private static object ConvertMinValueToNull(object value, Type targetType) { @@ -159,7 +227,7 @@ namespace EOM.TSHotelManagement.Common } /// - /// 处理 DateOnly 类型转换 + /// Handles DateOnly conversions. /// private static object HandleDateOnlyConversion(DateOnly dateOnly, Type targetType) { @@ -190,7 +258,7 @@ namespace EOM.TSHotelManagement.Common } /// - /// 处理 DateTime 类型转换 + /// Handles DateTime conversions. /// private static object HandleDateTimeConversion(DateTime dateTime, Type targetType) { @@ -221,7 +289,7 @@ namespace EOM.TSHotelManagement.Common } /// - /// 处理字符串日期转换 + /// Handles string-to-date conversions. /// private static object HandleStringConversion(string dateString, Type targetType) { @@ -244,25 +312,14 @@ namespace EOM.TSHotelManagement.Common } /// - /// 判断是否需要类型转换 + /// Determines whether source and target types still need a custom conversion. /// private static bool NeedConversion(Type sourceType, Type targetType) { var underlyingSource = Nullable.GetUnderlyingType(sourceType) ?? sourceType; var underlyingTarget = Nullable.GetUnderlyingType(targetType) ?? targetType; - if (underlyingSource == underlyingTarget) return false; - - return true; - } - - /// - /// 映射实体列表 - /// - public static List MapList(List sourceList) - where TDestination : new() - { - return sourceList?.Select(Map).ToList(); + return underlyingSource != underlyingTarget; } } } diff --git a/EOM.TSHotelManagement.Common/Helper/EnumHelper.cs b/EOM.TSHotelManagement.Common/Helper/EnumHelper.cs index 00854d46cbcd3d5a5314a2b520f5bd967f5494b0..63765c7bade411cf02d36b1fd4b1e6fb6ad54efb 100644 --- a/EOM.TSHotelManagement.Common/Helper/EnumHelper.cs +++ b/EOM.TSHotelManagement.Common/Helper/EnumHelper.cs @@ -3,11 +3,11 @@ using System.ComponentModel; using System.Linq; using System.Reflection; -namespace EOM.TSHotelManagement.Common +namespace EOM.TSHotelManagement.Common.Helper { - public class EnumHelper + public static class EnumHelper { - public string GetEnumDescription(Enum value) + public static string GetEnumDescription(Enum value) { FieldInfo field = value.GetType().GetField(value.ToString()); DescriptionAttribute attribute = field? @@ -17,7 +17,7 @@ namespace EOM.TSHotelManagement.Common return attribute?.Description ?? value.ToString(); } - public int GetEnumValue(Enum value) + public static int GetEnumValue(Enum value) { if (value == null) throw new ArgumentNullException(nameof(value)); @@ -25,14 +25,7 @@ namespace EOM.TSHotelManagement.Common return Convert.ToInt32(value); } - public int GetEnumValue(int value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - return Convert.ToInt32(value); - } - - public string GetDescriptionByName(string enumName) where TEnum : Enum + public static string GetDescriptionByName(string enumName) where TEnum : Enum { Type enumType = typeof(TEnum); diff --git a/EOM.TSHotelManagement.Common/Helper/JWTHelper.cs b/EOM.TSHotelManagement.Common/Helper/JWTHelper.cs index 5714c4dd369eaf33f8913bc260c67f08ef4856f4..9ca28d66b7301e831d2557b51a5ad7ade28fa082 100644 --- a/EOM.TSHotelManagement.Common/Helper/JWTHelper.cs +++ b/EOM.TSHotelManagement.Common/Helper/JWTHelper.cs @@ -1,4 +1,5 @@ using EOM.TSHotelManagement.Infrastructure; +using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; @@ -11,10 +12,12 @@ namespace EOM.TSHotelManagement.Common public class JWTHelper { private readonly JwtConfigFactory _jwtConfigFactory; + private readonly ILogger _logger; - public JWTHelper(JwtConfigFactory jwtConfigFactory) + public JWTHelper(JwtConfigFactory jwtConfigFactory, ILogger logger) { _jwtConfigFactory = jwtConfigFactory; + _logger = logger; } public Dictionary ClaimTypeMappings { get; set; } = new() @@ -49,6 +52,28 @@ namespace EOM.TSHotelManagement.Common return tokenHandler.WriteToken(token); } + /// + /// 生成 Refresh Token + /// + public string GenerateRefreshToken(ClaimsIdentity claimsIdentity) + { + var jwtConfig = GetJwtConfig(); + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.UTF8.GetBytes(jwtConfig.Key); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = claimsIdentity, + Expires = DateTime.Now.AddDays(jwtConfig.RefreshTokenExpiryDays), + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(key), + SecurityAlgorithms.HmacSha256Signature) + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + /// /// JWT 解密验证(返回 Claims 主体) /// @@ -72,16 +97,29 @@ namespace EOM.TSHotelManagement.Common { return tokenHandler.ValidateToken(token, validationParameters, out _); } - catch (SecurityTokenExpiredException) + catch (SecurityTokenExpiredException ex) { + _logger.LogWarning(ex, "JWT token has expired."); throw new ApplicationException("Token 已过期"); } - catch (SecurityTokenInvalidSignatureException) + catch (SecurityTokenInvalidSignatureException ex) { + _logger.LogError(ex, "JWT signature validation failed."); throw new ApplicationException("无效的签名"); } + catch (SecurityTokenMalformedException ex) + { + _logger.LogError(ex, "Invalid JWT format."); + throw new ApplicationException("Token 格式错误"); + } + catch (SecurityTokenException ex) + { + _logger.LogError(ex, "JWT token validation failed."); + throw new ApplicationException($"Token 验证失败: {ex.Message}"); + } catch (Exception ex) { + _logger.LogError(ex, "Unexpected error during JWT token validation."); throw new ApplicationException($"Token 验证失败: {ex.Message}"); } } diff --git a/EOM.TSHotelManagement.Common/Helper/JwtTokenRevocationService.cs b/EOM.TSHotelManagement.Common/Helper/JwtTokenRevocationService.cs index 500266b3c9e42b4f01339c57de43e3114a041e8f..ea69ddcd4bc78b294ded9e8968221a213aa13fe7 100644 --- a/EOM.TSHotelManagement.Common/Helper/JwtTokenRevocationService.cs +++ b/EOM.TSHotelManagement.Common/Helper/JwtTokenRevocationService.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using StackExchange.Redis; using System; using System.Collections.Concurrent; using System.IdentityModel.Tokens.Jwt; @@ -15,11 +16,14 @@ namespace EOM.TSHotelManagement.Common private const string RevokedTokenKeyPrefix = "auth:revoked:"; private readonly ConcurrentDictionary _memoryStore = new(); private long _memoryProbeCount; + private long _redisBypassUntilUtcTicks; private readonly RedisHelper _redisHelper; private readonly ILogger _logger; private readonly bool _useRedis; private readonly TimeSpan _fallbackTtl = TimeSpan.FromMinutes(30); + private readonly TimeSpan _redisOperationTimeout; + private readonly TimeSpan _redisFailureCooldown; public JwtTokenRevocationService( @@ -30,6 +34,13 @@ namespace EOM.TSHotelManagement.Common _redisHelper = redisHelper; _logger = logger; _useRedis = ResolveRedisEnabled(configuration); + + var redisSection = configuration.GetSection("Redis"); + var operationTimeoutMs = redisSection.GetValue("OperationTimeoutMs") ?? 1200; + var failureCooldownSeconds = redisSection.GetValue("FailureCooldownSeconds") ?? 30; + + _redisOperationTimeout = TimeSpan.FromMilliseconds(Math.Clamp(operationTimeoutMs, 200, 5000)); + _redisFailureCooldown = TimeSpan.FromSeconds(Math.Clamp(failureCooldownSeconds, 5, 300)); } public async Task RevokeTokenAsync(string token) @@ -43,14 +54,21 @@ namespace EOM.TSHotelManagement.Common var key = BuildRevokedTokenKey(normalizedToken); var ttl = CalculateRevokedTtl(normalizedToken); - if (_useRedis) + if (CanUseRedis()) { try { var db = _redisHelper.GetDatabase(); - await db.StringSetAsync(key, "1", ttl); + await ExecuteRedisWithTimeoutAsync( + () => db.StringSetAsync(key, "1", ttl), + "revoke-token"); + ClearRedisBypass(); return; } + catch (Exception ex) when (IsRedisUnavailableException(ex)) + { + MarkRedisUnavailable(ex, "revoke-token"); + } catch (Exception ex) { _logger.LogError(ex, "Redis token revoke failed, fallback to memory store."); @@ -71,12 +89,20 @@ namespace EOM.TSHotelManagement.Common var key = BuildRevokedTokenKey(normalizedToken); - if (_useRedis) + if (CanUseRedis()) { try { var db = _redisHelper.GetDatabase(); - return await db.KeyExistsAsync(key); + var isRevoked = await ExecuteRedisWithTimeoutAsync( + () => db.KeyExistsAsync(key), + "revoke-check"); + ClearRedisBypass(); + return isRevoked; + } + catch (Exception ex) when (IsRedisUnavailableException(ex)) + { + MarkRedisUnavailable(ex, "revoke-check"); } catch (Exception ex) { @@ -111,6 +137,81 @@ namespace EOM.TSHotelManagement.Common return !string.IsNullOrWhiteSpace(token); } + private bool CanUseRedis() + { + if (!_useRedis) + { + return false; + } + + var bypassUntilTicks = Interlocked.Read(ref _redisBypassUntilUtcTicks); + if (bypassUntilTicks <= 0) + { + return true; + } + + return DateTime.UtcNow.Ticks >= bypassUntilTicks; + } + + private void MarkRedisUnavailable(Exception ex, string operation) + { + var bypassUntil = DateTime.UtcNow.Add(_redisFailureCooldown).Ticks; + Interlocked.Exchange(ref _redisBypassUntilUtcTicks, bypassUntil); + + _logger.LogWarning( + ex, + "Redis {Operation} failed, bypass Redis for {CooldownSeconds}s and fallback to memory store.", + operation, + _redisFailureCooldown.TotalSeconds); + } + + private void ClearRedisBypass() + { + Interlocked.Exchange(ref _redisBypassUntilUtcTicks, 0); + } + + private static bool IsRedisUnavailableException(Exception ex) + { + return ex is TimeoutException || ex is RedisException; + } + + private async Task ExecuteRedisWithTimeoutAsync(Func redisOperation, string operation) + { + var redisTask = redisOperation(); + if (await Task.WhenAny(redisTask, Task.Delay(_redisOperationTimeout)) == redisTask) + { + await redisTask; + return; + } + + ObserveTaskFailure(redisTask); + throw new TimeoutException($"Redis operation '{operation}' timed out after {_redisOperationTimeout.TotalMilliseconds}ms."); + } + + private async Task ExecuteRedisWithTimeoutAsync(Func> redisOperation, string operation) + { + var redisTask = redisOperation(); + if (await Task.WhenAny(redisTask, Task.Delay(_redisOperationTimeout)) == redisTask) + { + return await redisTask; + } + + ObserveTaskFailure(redisTask); + throw new TimeoutException($"Redis operation '{operation}' timed out after {_redisOperationTimeout.TotalMilliseconds}ms."); + } + + private static void ObserveTaskFailure(Task task) + { + task.ContinueWith( + t => + { + var _ = t.Exception; + }, + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + private static string BuildRevokedTokenKey(string token) { var hash = SHA256.HashData(Encoding.UTF8.GetBytes(token)); @@ -188,7 +289,8 @@ namespace EOM.TSHotelManagement.Common private static bool ResolveRedisEnabled(IConfiguration configuration) { var redisSection = configuration.GetSection("Redis"); - var enable = redisSection.GetValue("Enabled"); + var enable = redisSection.GetValue("Enable") + ?? redisSection.GetValue("Enabled"); if (enable.HasValue) { return enable.Value; @@ -197,4 +299,4 @@ namespace EOM.TSHotelManagement.Common return redisSection.GetValue("Enabled"); } } -} +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Common/Helper/LskyHelper.cs b/EOM.TSHotelManagement.Common/Helper/LskyHelper.cs index 7fab1b32c0b981d5f3607b022e71fb12147d251d..27fa423dda325e585b78aedd5c42a332ced656d0 100644 --- a/EOM.TSHotelManagement.Common/Helper/LskyHelper.cs +++ b/EOM.TSHotelManagement.Common/Helper/LskyHelper.cs @@ -1,4 +1,4 @@ -using EOM.TSHotelManagement.Infrastructure; +using EOM.TSHotelManagement.Infrastructure; using Microsoft.Extensions.Logging; using System; using System.IO; @@ -155,7 +155,7 @@ namespace EOM.TSHotelManagement.Common } var result = await response.Content.ReadFromJsonAsync(); - return result?.Data?.Links?.Url ?? throw new Exception("响应中未包含有效URL"); + return result?.Data?.Links?.Url ?? throw new Exception("无效图片,请重新选择图片上传"); } catch (Exception ex) { diff --git a/EOM.TSHotelManagement.Common/Helper/RedisHelper.cs b/EOM.TSHotelManagement.Common/Helper/RedisHelper.cs index 1238879225c1d203bef93855a89e92f8d4b764b1..52b26ca4f975bc8f0f4d50b9d48479cf0ae5c6a9 100644 --- a/EOM.TSHotelManagement.Common/Helper/RedisHelper.cs +++ b/EOM.TSHotelManagement.Common/Helper/RedisHelper.cs @@ -2,6 +2,7 @@ using EOM.TSHotelManagement.Infrastructure; using Microsoft.Extensions.Logging; using StackExchange.Redis; using System; +using System.Threading.Tasks; namespace EOM.TSHotelManagement.Common { @@ -9,6 +10,7 @@ namespace EOM.TSHotelManagement.Common { private readonly object _lock = new object(); private IConnectionMultiplexer _connection; + private int _defaultDatabase = -1; private readonly ILogger logger; private readonly RedisConfigFactory configFactory; @@ -23,29 +25,44 @@ namespace EOM.TSHotelManagement.Common { lock (_lock) { - if (_connection != null) return; + if (_connection != null) + { + return; + } + try { var redisConfig = configFactory.GetRedisConfig(); - if (!redisConfig.Enable) { logger.LogInformation("Redis功能未启用,跳过初始化"); return; } - int defaultDatabase = redisConfig.DefaultDatabase ?? -1; - - if (string.IsNullOrWhiteSpace(redisConfig?.ConnectionString)) + if (string.IsNullOrWhiteSpace(redisConfig.ConnectionString)) + { throw new ArgumentException("Redis连接字符串不能为空"); + } - var options = ConfigurationOptions.Parse(redisConfig.ConnectionString); + _defaultDatabase = redisConfig.DefaultDatabase ?? -1; + + var options = ConfigurationOptions.Parse(redisConfig.ConnectionString, ignoreUnknown: true); options.AbortOnConnectFail = false; - options.ConnectTimeout = 5000; - options.ReconnectRetryPolicy = new ExponentialRetry(3000); + options.ConnectTimeout = Clamp(redisConfig.ConnectTimeoutMs, 1000, 30000, 5000); + options.SyncTimeout = Clamp(redisConfig.SyncTimeoutMs, 500, 30000, 2000); + options.AsyncTimeout = Clamp(redisConfig.AsyncTimeoutMs, 500, 30000, 2000); + options.KeepAlive = Clamp(redisConfig.KeepAliveSeconds, 5, 300, 15); + options.ConnectRetry = Clamp(redisConfig.ConnectRetry, 1, 10, 3); + options.ReconnectRetryPolicy = new ExponentialRetry( + Clamp(redisConfig.ReconnectRetryBaseDelayMs, 500, 30000, 3000)); + + if (_defaultDatabase >= 0) + { + options.DefaultDatabase = _defaultDatabase; + } _connection = ConnectionMultiplexer.Connect(options); - _connection.GetDatabase(defaultDatabase).Ping(); + _connection.GetDatabase(_defaultDatabase).Ping(); } catch (Exception ex) { @@ -58,13 +75,27 @@ namespace EOM.TSHotelManagement.Common public IDatabase GetDatabase() { if (_connection == null) - throw new System.Exception("RedisHelper not initialized. Call Initialize first."); + { + throw new Exception("RedisHelper not initialized. Call Initialize first."); + } - return _connection.GetDatabase(); + return _connection.GetDatabase(_defaultDatabase); } public async Task CheckServiceStatusAsync() { + var redisConfig = configFactory.GetRedisConfig(); + if (!redisConfig.Enable) + { + logger.LogInformation("Redis功能未启用,跳过初始化"); + return false; + } + + if (string.IsNullOrWhiteSpace(redisConfig.ConnectionString)) + { + throw new ArgumentException("Redis连接字符串不能为空"); + } + try { var db = GetDatabase(); @@ -86,12 +117,10 @@ namespace EOM.TSHotelManagement.Common var db = GetDatabase(); if (expiry.HasValue) { - return await db.StringSetAsync(key, value, new StackExchange.Redis.Expiration(expiry.Value)); - } - else - { - return await db.StringSetAsync(key, value); + return await db.StringSetAsync(key, value, expiry.Value); } + + return await db.StringSetAsync(key, value); } catch (Exception ex) { @@ -142,5 +171,10 @@ namespace EOM.TSHotelManagement.Common } } + private static int Clamp(int? value, int min, int max, int fallback) + { + return Math.Clamp(value ?? fallback, min, max); + } } } + diff --git a/EOM.TSHotelManagement.Common/Helper/SoftwareVersionHelper.cs b/EOM.TSHotelManagement.Common/Helper/SoftwareVersionHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..dca993253d0cdd132394803c33e89fa33d2d9dae --- /dev/null +++ b/EOM.TSHotelManagement.Common/Helper/SoftwareVersionHelper.cs @@ -0,0 +1,51 @@ +using System.Reflection; + +namespace EOM.TSHotelManagement.Common +{ + public static class SoftwareVersionHelper + { + public static string GetSoftwareVersion(string? configuredVersion = null, string defaultVersion = "1.0.0") + { + var version = Environment.GetEnvironmentVariable("APP_VERSION") + ?? Environment.GetEnvironmentVariable("SoftwareVersion") + ?? configuredVersion + ?? GetVersionFromFile() + ?? GetAssemblyVersion(); + + return string.IsNullOrWhiteSpace(version) ? defaultVersion : version.Trim(); + } + + private static string? GetVersionFromFile() + { + try + { + var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); + var rootPath = Path.GetDirectoryName(assembly.Location); + if (string.IsNullOrWhiteSpace(rootPath)) + { + return null; + } + + var versionFilePath = Path.Combine(rootPath, "version.txt"); + if (!File.Exists(versionFilePath)) + { + return null; + } + + var versionContent = File.ReadAllText(versionFilePath).Trim(); + return string.IsNullOrWhiteSpace(versionContent) ? null : versionContent; + } + catch + { + return null; + } + } + + private static string? GetAssemblyVersion() + { + var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); + return assembly.GetCustomAttribute()?.InformationalVersion + ?? assembly.GetName().Version?.ToString(3); + } + } +} diff --git a/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs new file mode 100644 index 0000000000000000000000000000000000000000..36f5724176f1de04aca6713435babfe0acc9d8d0 --- /dev/null +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs @@ -0,0 +1,199 @@ +using EOM.TSHotelManagement.Domain; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Quartz; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob +{ + [DisallowConcurrentExecution] + public class AutomaticallyUpgradeMembershipLevelJob : IJob + { + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + public AutomaticallyUpgradeMembershipLevelJob( + ILogger logger, + IServiceProvider serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + } + + public Task Execute(IJobExecutionContext context) + { + _logger.LogInformation("开始批量处理会员等级升级。"); + + try + { + var result = ValidateAndUpdateCustomerInfo(); + _logger.LogInformation( + "会员等级升级完成,扫描客户 {ScannedCount} 个,需升级 {NeedUpgradeCount} 个,实际更新 {UpdatedCount} 个。", + result.ScannedCount, + result.NeedUpgradeCount, + result.UpdatedCount); + } + catch (Exception ex) + { + _logger.LogError(ex, "处理会员等级升级时发生异常"); + } + + return Task.CompletedTask; + } + + private MembershipUpgradeResult ValidateAndUpdateCustomerInfo() + { + using var scope = _serviceProvider.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var listVipRules = db.Queryable() + .Where(v => v.IsDelete != 1) + .OrderByDescending(a => a.RuleValue) + .ToList(); + + if (listVipRules.Count == 0) + { + _logger.LogWarning("会员等级升级已跳过:未找到有效的会员规则。"); + return MembershipUpgradeResult.Empty; + } + + var enabledCustomerTypes = db.Queryable() + .Where(x => x.IsDelete != 1) + .Select(x => x.CustomerType) + .ToList() + .ToHashSet(); + + listVipRules = listVipRules + .Where(x => enabledCustomerTypes.Contains(x.VipLevelId)) + .ToList(); + + if (listVipRules.Count == 0) + { + _logger.LogWarning("会员等级升级已跳过:规则指向的会员等级不存在或已被删除。"); + return MembershipUpgradeResult.Empty; + } + + var customerSpends = db.Queryable() + .Where(s => s.IsDelete != 1 && s.CustomerNumber != null && s.CustomerNumber != string.Empty) + .GroupBy(s => s.CustomerNumber) + .Select((s) => new CustomerSpendAggregate + { + CustomerNumber = s.CustomerNumber, + TotalConsumptionAmount = SqlFunc.AggregateSum(s.ConsumptionAmount) + }) + .ToList(); + + if (customerSpends.Count == 0) + { + _logger.LogInformation("会员等级升级已跳过:未找到有效消费记录。"); + return MembershipUpgradeResult.Empty; + } + + var customerNumbers = customerSpends + .Select(x => x.CustomerNumber) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct() + .ToList(); + + var customers = db.Queryable() + .Where(c => c.IsDelete != 1 && customerNumbers.Contains(c.CustomerNumber)) + .ToList(); + + var customerLookup = customers + .Where(c => !string.IsNullOrWhiteSpace(c.CustomerNumber)) + .GroupBy(c => c.CustomerNumber) + .ToDictionary(g => g.Key, g => g.First()); + + var updateTime = DateTime.Now; + var customerToUpdate = new List(); + + foreach (var customerSpend in customerSpends) + { + if (string.IsNullOrWhiteSpace(customerSpend.CustomerNumber)) + { + continue; + } + + if (!customerLookup.TryGetValue(customerSpend.CustomerNumber, out var customer)) + { + continue; + } + + var targetVipLevel = listVipRules + .FirstOrDefault(vipRule => customerSpend.TotalConsumptionAmount >= vipRule.RuleValue)? + .VipLevelId ?? 0; + + if (targetVipLevel <= 0 || targetVipLevel == customer.CustomerType) + { + continue; + } + + customer.CustomerType = targetVipLevel; + customer.DataChgDate = updateTime; + customer.DataChgUsr = "BatchJobSystem"; + customerToUpdate.Add(customer); + } + + if (customerToUpdate.Count == 0) + { + return new MembershipUpgradeResult + { + ScannedCount = customerSpends.Count, + NeedUpgradeCount = 0, + UpdatedCount = 0 + }; + } + + var updatedCount = 0; + + db.Ado.BeginTran(); + try + { + foreach (var batch in customerToUpdate.Chunk(200)) + { + var currentBatch = batch.ToList(); + updatedCount += db.Updateable(currentBatch) + .UpdateColumns( + nameof(Customer.CustomerType), + nameof(Customer.DataChgDate), + nameof(Customer.DataChgUsr)) + .ExecuteCommand(); + } + + db.Ado.CommitTran(); + } + catch + { + db.Ado.RollbackTran(); + throw; + } + + return new MembershipUpgradeResult + { + ScannedCount = customerSpends.Count, + NeedUpgradeCount = customerToUpdate.Count, + UpdatedCount = updatedCount + }; + } + + private sealed class CustomerSpendAggregate + { + public string CustomerNumber { get; set; } = string.Empty; + public decimal TotalConsumptionAmount { get; set; } + } + + private sealed class MembershipUpgradeResult + { + public static MembershipUpgradeResult Empty { get; } = new MembershipUpgradeResult(); + + public int ScannedCount { get; set; } + public int NeedUpgradeCount { get; set; } + public int UpdatedCount { get; set; } + } + } +} diff --git a/EOM.TSHotelManagement.Common/QuartzWorkspace/Job/ReservationExpirationCheckJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs similarity index 84% rename from EOM.TSHotelManagement.Common/QuartzWorkspace/Job/ReservationExpirationCheckJob.cs rename to EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs index 6431906bb483d9a28921f8e58700bf04d03ea051..0d6105c43863ae50097939f5c47087a9214ef400 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/Job/ReservationExpirationCheckJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs @@ -1,4 +1,5 @@ -using EOM.TSHotelManagement.Domain; +using EOM.TSHotelManagement.Common.Helper; +using EOM.TSHotelManagement.Domain; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,7 +12,7 @@ using System.Security; using System.Text; using System.Threading.Tasks; -namespace EOM.TSHotelManagement.Common +namespace EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob { [DisallowConcurrentExecution] public class ReservationExpirationCheckJob : IJob @@ -78,8 +79,7 @@ namespace EOM.TSHotelManagement.Common shouldDeleteReservations.Add(item.Id); } - var helper = new EnumHelper(); - var channelDescription = helper.GetDescriptionByName(item.ReservationChannel); + var channelDescription = EnumHelper.GetDescriptionByName(item.ReservationChannel); var mailTemplate = EmailTemplate.SendReservationExpirationNotificationTemplate( item.ReservationRoomNumber, @@ -98,16 +98,6 @@ namespace EOM.TSHotelManagement.Common mailTemplate.Body ); } - - if (shouldDeleteReservations.Any() && shouldReleaseRooms.Any()) - { - db.Updateable() - .SetColumns(r => new Room { RoomStateId = (int)RoomState.Vacant }) - .Where(r => shouldReleaseRooms.Contains(r.RoomNumber)) - .ExecuteCommand(); - db.Deleteable(r => shouldDeleteReservations.Contains(r.Id)); - } - _logger.LogInformation("已为 {ItemName} 发送过期提醒", item.ReservationId); } catch (Exception ex) @@ -116,6 +106,18 @@ namespace EOM.TSHotelManagement.Common } } + if (shouldDeleteReservations.Any() && shouldReleaseRooms.Any()) + { + db.Updateable() + .SetColumns(r => new Room { RoomStateId = (int)RoomState.Vacant }) + .Where(r => shouldReleaseRooms.Contains(r.RoomNumber)) + .ExecuteCommand(); + db.Updateable() + .SetColumns(r => new Reser { ReservationStatus = 2 }) + .Where(r => shouldDeleteReservations.Contains(r.Id)) + .ExecuteCommand(); + } + _logger.LogInformation("数据过期检查完成,共处理 {Count} 个项目", expiringItems.Count); } } diff --git a/EOM.TSHotelManagement.Common/QuartzWorkspace/ImageHosting/ImageHostingServiceCheckJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/ImageHosting/ImageHostingServiceCheckJob.cs index 7ce390c0139629d787a860f93632b4fcc5a55c09..16b8cd0bacd440d31d5bb1128c4e9b047b3e1a05 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/ImageHosting/ImageHostingServiceCheckJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/ImageHosting/ImageHostingServiceCheckJob.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Quartz; @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace EOM.TSHotelManagement.Common +namespace EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob { [DisallowConcurrentExecution] public class ImageHostingServiceCheckJob : IJob diff --git a/EOM.TSHotelManagement.Common/QuartzWorkspace/Mail/MailServiceCheckJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/Mail/MailServiceCheckJob.cs index 91e10904be390c4ce7a5c892b613ace663a92396..d1cc1347ceeee5c560ca5353fa3daefd73ad9a87 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/Mail/MailServiceCheckJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/Mail/MailServiceCheckJob.cs @@ -1,4 +1,4 @@ -using EOM.TSHotelManagement.Domain; +using EOM.TSHotelManagement.Domain; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace EOM.TSHotelManagement.Common +namespace EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob { [DisallowConcurrentExecution] public class MailServiceCheckJob : IJob diff --git a/EOM.TSHotelManagement.Common/QuartzWorkspace/Redis/RedisServiceCheckJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/Redis/RedisServiceCheckJob.cs index 1257fcac544709ff90d3bbbc81e3c792bd9f9df1..98a68b27f990b97d0b9f75569d4340b6039d6391 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/Redis/RedisServiceCheckJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/Redis/RedisServiceCheckJob.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Quartz; @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace EOM.TSHotelManagement.Common +namespace EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob { [DisallowConcurrentExecution] public class RedisServiceCheckJob : IJob diff --git a/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/ReadFavoriteCollectionOutputDto.cs b/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/ReadFavoriteCollectionOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..36e670cce75d76ba474204b721137cb6c7edd7b8 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/ReadFavoriteCollectionOutputDto.cs @@ -0,0 +1,23 @@ +namespace EOM.TSHotelManagement.Contract +{ + /// + /// 读取收藏夹响应 DTO + /// + public class ReadFavoriteCollectionOutputDto + { + /// + /// 收藏路由列表 + /// + public List FavoriteRoutes { get; set; } = new(); + + /// + /// 收藏夹最后更新时间 + /// + public DateTime? UpdatedAt { get; set; } + + /// + /// 当前快照版本号 + /// + public long? RowVersion { get; set; } + } +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionInputDto.cs b/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionInputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..55ec5b6c8307e7dad801744092142b78ce091054 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionInputDto.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; + +namespace EOM.TSHotelManagement.Contract +{ + /// + /// 保存收藏夹请求 DTO + /// + public class SaveFavoriteCollectionInputDto + { + /// + /// 乐观锁版本号,更新已有快照时必填 + /// + public long? RowVersion { get; set; } + + /// + /// 登录类型,前端可能传 admin 或 employee + /// + [MaxLength(32, ErrorMessage = "LoginType length cannot exceed 32 characters.")] + public string? LoginType { get; set; } + + /// + /// 前端当前账号 + /// + [MaxLength(128, ErrorMessage = "Account length cannot exceed 128 characters.")] + public string? Account { get; set; } + + /// + /// 当前完整收藏路由列表 + /// + [Required(ErrorMessage = "FavoriteRoutes is required.")] + public List FavoriteRoutes { get; set; } = new(); + + /// + /// 前端最后一次修改收藏夹的时间 + /// + public DateTime? UpdatedAt { get; set; } + + /// + /// 触发来源,例如 logout、pagehide、beforeunload、manual + /// + [MaxLength(32, ErrorMessage = "TriggeredBy length cannot exceed 32 characters.")] + public string? TriggeredBy { get; set; } + } +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionOutputDto.cs b/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..eaceb54edddc780725db26eeef037c438fe99c6e --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionOutputDto.cs @@ -0,0 +1,28 @@ +namespace EOM.TSHotelManagement.Contract +{ + /// + /// 保存收藏夹响应 DTO + /// + public class SaveFavoriteCollectionOutputDto + { + /// + /// 是否保存成功 + /// + public bool Saved { get; set; } + + /// + /// 保存后的收藏数量 + /// + public int RouteCount { get; set; } + + /// + /// 最终生效的更新时间 + /// + public DateTime UpdatedAt { get; set; } + + /// + /// 保存成功后的最新版本号 + /// + public long RowVersion { get; set; } + } +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Contract/Application/Profile/Dto/ChangePasswordInputDto.cs b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/ChangePasswordInputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..f91dc4d193d88475be5f1290eb358b27c1f606c6 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/ChangePasswordInputDto.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace EOM.TSHotelManagement.Contract +{ + public class ChangePasswordInputDto + { + [Required(ErrorMessage = "旧密码为必填字段")] + [MaxLength(256, ErrorMessage = "旧密码长度不能超过256字符")] + public string OldPassword { get; set; } + + [Required(ErrorMessage = "新密码为必填字段")] + [MaxLength(256, ErrorMessage = "新密码长度不能超过256字符")] + public string NewPassword { get; set; } + + [Required(ErrorMessage = "确认密码为必填字段")] + [MaxLength(256, ErrorMessage = "确认密码长度不能超过256字符")] + public string ConfirmPassword { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Application/Profile/Dto/CurrentProfileOutputDto.cs b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/CurrentProfileOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..ea9e96dbfc2a85668d68fc7b3241b23bd2da0e9f --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/CurrentProfileOutputDto.cs @@ -0,0 +1,57 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class CurrentProfileOutputDto + { + public string LoginType { get; set; } + + public string UserNumber { get; set; } + + public string Account { get; set; } + + public string DisplayName { get; set; } + + public string PhotoUrl { get; set; } + + public object? Profile { get; set; } + } + + public class CurrentProfileEmployeeDto + { + public string EmployeeId { get; set; } + + public string Name { get; set; } + + public string DepartmentName { get; set; } + + public string PositionName { get; set; } + + public string PhoneNumber { get; set; } + + public string EmailAddress { get; set; } + + public string Address { get; set; } + + public string HireDate { get; set; } + + public string DateOfBirth { get; set; } + + public string PhotoUrl { get; set; } + } + + public class CurrentProfileAdminDto + { + public string Number { get; set; } + + public string Account { get; set; } + + public string Name { get; set; } + + public string TypeName { get; set; } + + public string Type { get; set; } + + public int IsSuperAdmin { get; set; } + + public string PhotoUrl { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarInputDto.cs b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarInputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..52d50e2b3e0c751c00e8e8089d0459d843d5d245 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarInputDto.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace EOM.TSHotelManagement.Contract +{ + public class UploadAvatarInputDto + { + [MaxLength(32, ErrorMessage = "账号类型长度不能超过32字符")] + public string? LoginType { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarOutputDto.cs b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..750e186a69cfaea429ebb0deb6b26373fa734a97 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarOutputDto.cs @@ -0,0 +1,7 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class UploadAvatarOutputDto + { + public string PhotoUrl { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetInputDto.cs index 1970ca59bb38002ff7b27e2a6b5d24477b8c16e5..2ea99b49602907ef4b97d6248e1111f009797dff 100644 --- a/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetInputDto.cs @@ -1,4 +1,6 @@ -namespace EOM.TSHotelManagement.Contract +using EOM.TSHotelManagement.Common; + +namespace EOM.TSHotelManagement.Contract { public class ReadAssetInputDto : ListInputDto { @@ -6,8 +8,8 @@ public string? AssetName { get; set; } public string? DepartmentCode { get; set; } public string? AcquiredByEmployeeId { get; set; } - public DateOnly? AcquisitionDateStart { get; set; } - public DateOnly? AcquisitionDateEnd { get; set; } + + public DateRangeDto DateRangeDto { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetOutputDto.cs index 30a584e0a12c7d97af2f9fa8ce618e2671b3d1fb..c660305bfaf71fe4100d25dbc4c98215eea9cc02 100644 --- a/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetOutputDto.cs @@ -1,4 +1,4 @@ -namespace EOM.TSHotelManagement.Contract +namespace EOM.TSHotelManagement.Contract { public class ReadAssetOutputDto : BaseOutputDto { @@ -17,7 +17,7 @@ public string AssetSource { get; set; } public string AcquiredByEmployeeId { get; set; } - public string AcquiredByEmployeeName { get; set; } + public string AcquiredByName { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/CreateCustomerInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/CreateCustomerInputDto.cs index 5de097a00cfa8d8251ea74b95788bb6a396afacb..7faacf2b7c7ab4a0a102fb71ab4e80ee196f645a 100644 --- a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/CreateCustomerInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/CreateCustomerInputDto.cs @@ -8,16 +8,16 @@ namespace EOM.TSHotelManagement.Contract public string CustomerNumber { get; set; } [Required(ErrorMessage = "客户名称为必填字段"), MaxLength(250, ErrorMessage = "客户名称长度不超过250字符")] - public string CustomerName { get; set; } + public string Name { get; set; } [Required(ErrorMessage = "客户性别为必填字段")] - public int? CustomerGender { get; set; } + public int? Gender { get; set; } [Required(ErrorMessage = "证件类型为必填字段")] - public int PassportId { get; set; } + public int IdCardType { get; set; } [Required(ErrorMessage = "客户电话为必填字段"), MaxLength(256, ErrorMessage = "客户电话长度不超过256字符")] - public string CustomerPhoneNumber { get; set; } + public string PhoneNumber { get; set; } [Required(ErrorMessage = "出生日期为必填字段")] public DateTime DateOfBirth { get; set; } @@ -26,7 +26,7 @@ namespace EOM.TSHotelManagement.Contract public string IdCardNumber { get; set; } [MaxLength(256, ErrorMessage = "客户地址长度不超过256字符")] - public string CustomerAddress { get; set; } + public string Address { get; set; } [Required(ErrorMessage = "客户类型为必填字段")] public int CustomerType { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerInputDto.cs index 976b4c5421b9f5078009f70f9c863af9392ad0b6..18d1b949665fc91166d0f7fdede836f320adf336 100644 --- a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerInputDto.cs @@ -1,14 +1,14 @@ -using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common; namespace EOM.TSHotelManagement.Contract { public class ReadCustomerInputDto : ListInputDto { - public string? CustomerName { get; set; } + public string? Name { get; set; } public string? CustomerNumber { get; set; } - public string? CustomerPhoneNumber { get; set; } + public string? PhoneNumber { get; set; } public string? IdCardNumber { get; set; } - public int? CustomerGender { get; set; } + public int? Gender { get; set; } public int? CustomerType { get; set; } public DateRangeDto DateRangeDto { get; set; } } diff --git a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerOutputDto.cs index abd0fdd05cd25ddedc2fda784656969c11e168f0..9bb26096e9be57a9dcc623cebaf5951f1b0b5ba1 100644 --- a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/ReadCustomerOutputDto.cs @@ -1,4 +1,4 @@ - + using EOM.TSHotelManagement.Common; namespace EOM.TSHotelManagement.Contract @@ -12,19 +12,19 @@ namespace EOM.TSHotelManagement.Contract public string CustomerNumber { get; set; } [UIDisplay("客户姓名")] - public string CustomerName { get; set; } + public string Name { get; set; } [UIDisplay("性别", true, false)] - public int? CustomerGender { get; set; } + public int? Gender { get; set; } [UIDisplay("证件类型", true, false)] - public int PassportId { get; set; } + public int IdCardType { get; set; } [UIDisplay("性别", false, true)] public string GenderName { get; set; } [UIDisplay("联系方式")] - public string CustomerPhoneNumber { get; set; } + public string PhoneNumber { get; set; } [UIDisplay("出生日期")] public DateTime DateOfBirth { get; set; } @@ -42,7 +42,7 @@ namespace EOM.TSHotelManagement.Contract public string IdCardNumber { get; set; } [UIDisplay("客户地址")] - public string CustomerAddress { get; set; } + public string Address { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/UpdateCustomerInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/UpdateCustomerInputDto.cs index e982f8f15e56546ed67686927b3a180cf9f68800..f6a434895d714a9b9fb8fa7a7b2fc215a43a4f78 100644 --- a/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/UpdateCustomerInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Customer/Dto/Customer/UpdateCustomerInputDto.cs @@ -8,16 +8,16 @@ namespace EOM.TSHotelManagement.Contract public string CustomerNumber { get; set; } [Required(ErrorMessage = "客户名称为必填字段"), MaxLength(250, ErrorMessage = "客户名称长度不超过250字符")] - public string CustomerName { get; set; } + public string Name { get; set; } [Required(ErrorMessage = "客户性别为必填字段")] - public int? CustomerGender { get; set; } + public int? Gender { get; set; } [Required(ErrorMessage = "证件类型为必填字段")] - public int PassportId { get; set; } + public int IdCardType { get; set; } [Required(ErrorMessage = "客户电话为必填字段"), MaxLength(256, ErrorMessage = "客户电话长度不超过256字符")] - public string CustomerPhoneNumber { get; set; } + public string PhoneNumber { get; set; } [Required(ErrorMessage = "出生日期为必填字段")] public DateOnly DateOfBirth { get; set; } @@ -26,7 +26,7 @@ namespace EOM.TSHotelManagement.Contract public string IdCardNumber { get; set; } [MaxLength(256, ErrorMessage = "客户地址长度不超过256字符")] - public string CustomerAddress { get; set; } + public string Address { get; set; } [Required(ErrorMessage = "客户类型为必填字段")] public int CustomerType { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs index 6451a26239b4044e41a01f000c87605face54576..2cdb089f351132575d1bc839f4e74228d5f2dac9 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs @@ -4,29 +4,31 @@ namespace EOM.TSHotelManagement.Contract { public class CreateEnergyManagementInputDto : BaseInputDto { - [Required(ErrorMessage = "信息编号为必填字段"), MaxLength(128, ErrorMessage = "信息编号长度不超过128字符")] + [Required(ErrorMessage = "InformationNumber is required."), MaxLength(128, ErrorMessage = "InformationNumber max length is 128.")] public string InformationNumber { get; set; } - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "RoomNumber max length is 128.")] public string RoomNumber { get; set; } - [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] + [Required(ErrorMessage = "CustomerNumber is required."), MaxLength(128, ErrorMessage = "CustomerNumber max length is 128.")] public string CustomerNumber { get; set; } - [Required(ErrorMessage = "开始日期为必填字段")] + [Required(ErrorMessage = "StartDate is required.")] public DateTime StartDate { get; set; } - [Required(ErrorMessage = "结束日期为必填字段")] + [Required(ErrorMessage = "EndDate is required.")] public DateTime EndDate { get; set; } - [Required(ErrorMessage = "电费为必填字段")] + [Required(ErrorMessage = "PowerUsage is required.")] public decimal PowerUsage { get; set; } - [Required(ErrorMessage = "水费为必填字段")] + [Required(ErrorMessage = "WaterUsage is required.")] public decimal WaterUsage { get; set; } - [Required(ErrorMessage = "记录员为必填字段"), MaxLength(150, ErrorMessage = "记录员长度不超过150字符")] + [Required(ErrorMessage = "Recorder is required."), MaxLength(150, ErrorMessage = "Recorder max length is 150.")] public string Recorder { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs index 8cb9fe003ba4aa336ca546dfcfdd0fdc293339e4..a23f97f8f944882237e5299fdcbb8f7f48b20d18 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs @@ -5,11 +5,11 @@ namespace EOM.TSHotelManagement.Contract public class ReadEnergyManagementInputDto : ListInputDto { public string? CustomerNumber { get; set; } + public int? RoomId { get; set; } public string? RoomNumber { get; set; } - public DateOnly? StartDate { get; set; } + public DateTime? StartDate { get; set; } - public DateOnly? EndDate { get; set; } + public DateTime? EndDate { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs index 9f179e31f8ea4f12a7400411346e2d5278c64f14..7774867b51aa32215937b64ef9677572cf196331 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs @@ -6,8 +6,12 @@ namespace EOM.TSHotelManagement.Contract { public int? Id { get; set; } public string InformationId { get; set; } + public int? RoomId { get; set; } [UIDisplay("房间号")] public string RoomNumber { get; set; } + public string RoomArea { get; set; } + public int? RoomFloor { get; set; } + public string RoomLocator { get; set; } [UIDisplay("客户编号")] public string CustomerNumber { get; set; } [UIDisplay("开始日期")] diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs index 9dce1db215ab5ad6009e83e7592e133f828033a9..7d6029bba8ce05488f6ee9ed747e4bb43ae7f1fb 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs @@ -4,29 +4,31 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateEnergyManagementInputDto : BaseInputDto { - [Required(ErrorMessage = "信息编号为必填字段"), MaxLength(128, ErrorMessage = "信息编号长度不超过128字符")] + [Required(ErrorMessage = "InformationId is required."), MaxLength(128, ErrorMessage = "InformationId max length is 128.")] public string InformationId { get; set; } - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "RoomNumber max length is 128.")] public string RoomNumber { get; set; } - [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] + [Required(ErrorMessage = "CustomerNumber is required."), MaxLength(128, ErrorMessage = "CustomerNumber max length is 128.")] public string CustomerNumber { get; set; } - [Required(ErrorMessage = "开始日期为必填字段")] + [Required(ErrorMessage = "StartDate is required.")] public DateTime StartDate { get; set; } - [Required(ErrorMessage = "结束日期为必填字段")] + [Required(ErrorMessage = "EndDate is required.")] public DateTime EndDate { get; set; } - [Required(ErrorMessage = "电费为必填字段")] + [Required(ErrorMessage = "PowerUsage is required.")] public decimal PowerUsage { get; set; } - [Required(ErrorMessage = "水费为必填字段")] + [Required(ErrorMessage = "WaterUsage is required.")] public decimal WaterUsage { get; set; } - [Required(ErrorMessage = "记录员为必填字段"), MaxLength(150, ErrorMessage = "记录员长度不超过150字符")] + [Required(ErrorMessage = "Recorder is required."), MaxLength(150, ErrorMessage = "Recorder max length is 150.")] public string Recorder { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs index 187e6fe58ae4483ab0da3bde439dae0a8337054c..cf8287698a6e81285bad1dd9be67a802085073ea 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs @@ -4,26 +4,30 @@ namespace EOM.TSHotelManagement.Contract { public class CreateReserInputDto : BaseInputDto { - [Required(ErrorMessage = "预约编号为必填字段"), MaxLength(128, ErrorMessage = "预约编号长度不超过128字符")] + [Required(ErrorMessage = "ReservationId is required."), MaxLength(128, ErrorMessage = "ReservationId max length is 128.")] public string ReservationId { get; set; } - [Required(ErrorMessage = "客户名称为必填字段"), MaxLength(200, ErrorMessage = "客户名称长度不超过200字符")] + [Required(ErrorMessage = "CustomerName is required."), MaxLength(200, ErrorMessage = "CustomerName max length is 200.")] public string CustomerName { get; set; } - [Required(ErrorMessage = "预约电话为必填字段"), MaxLength(256, ErrorMessage = "预约电话长度不超过256字符")] + [Required(ErrorMessage = "ReservationPhoneNumber is required."), MaxLength(256, ErrorMessage = "ReservationPhoneNumber max length is 256.")] public string ReservationPhoneNumber { get; set; } - [Required(ErrorMessage = "预约房号为必填字段"), MaxLength(128, ErrorMessage = "预约房号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "ReservationRoomNumber max length is 128.")] public string ReservationRoomNumber { get; set; } - [Required(ErrorMessage = "预约渠道为必填字段"), MaxLength(50, ErrorMessage = "预约渠道长度不超过50字符")] + [Required(ErrorMessage = "ReservationChannel is required."), MaxLength(50, ErrorMessage = "ReservationChannel max length is 50.")] public string ReservationChannel { get; set; } - [Required(ErrorMessage = "预约起始日期为必填字段")] + [Required(ErrorMessage = "ReservationStartDate is required.")] public DateTime ReservationStartDate { get; set; } - [Required(ErrorMessage = "预约结束日期为必填字段")] + [Required(ErrorMessage = "ReservationEndDate is required.")] public DateTime ReservationEndDate { get; set; } + + public int ReservationStatus { get; set; } = 0; } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserInputDto.cs index 7dae6616f9f7e46254e94eccbfce1ba62da926bc..0466e2b3afa9d8ceb5dc4d92dd640721e32bc925 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserInputDto.cs @@ -2,6 +2,7 @@ { public class ReadReserInputDto : ListInputDto { + public int? RoomId { get; set; } public string? ReservationId { get; set; } public string? CustomerName { get; set; } public string? ReservationPhoneNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs index 9ce46d1a2a45e14b643d3a85d706870597cd0aba..8816cc452455d3918d6ad04f8965d93c62ab8d8a 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs @@ -5,6 +5,7 @@ namespace EOM.TSHotelManagement.Contract public class ReadReserOutputDto : BaseOutputDto { public int? Id { get; set; } + public int? RoomId { get; set; } [UIDisplay("预约编号")] public string ReservationId { get; set; } @@ -18,6 +19,12 @@ namespace EOM.TSHotelManagement.Contract [UIDisplay("预定房号")] public string ReservationRoomNumber { get; set; } + public string RoomArea { get; set; } + + public int? RoomFloor { get; set; } + + public string RoomLocator { get; set; } + [UIDisplay("预约渠道")] public string ReservationChannel { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs index c0ed9a9a3a598a617d36a9ae9179cf3b33cb3ee9..cb69d7b6dcb051d38d7f1f3a07121014051be6b6 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs @@ -4,26 +4,28 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateReserInputDto : BaseInputDto { - [Required(ErrorMessage = "预约编号为必填字段"), MaxLength(128, ErrorMessage = "预约编号长度不超过128字符")] + [Required(ErrorMessage = "ReservationId is required."), MaxLength(128, ErrorMessage = "ReservationId max length is 128.")] public string ReservationId { get; set; } - [Required(ErrorMessage = "客户名称为必填字段"), MaxLength(200, ErrorMessage = "客户名称长度不超过200字符")] + [Required(ErrorMessage = "CustomerName is required."), MaxLength(200, ErrorMessage = "CustomerName max length is 200.")] public string CustomerName { get; set; } - [Required(ErrorMessage = "预约电话为必填字段"), MaxLength(256, ErrorMessage = "预约电话长度不超过256字符")] + [Required(ErrorMessage = "ReservationPhoneNumber is required."), MaxLength(256, ErrorMessage = "ReservationPhoneNumber max length is 256.")] public string ReservationPhoneNumber { get; set; } - [Required(ErrorMessage = "预约房号为必填字段"), MaxLength(128, ErrorMessage = "预约房号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "ReservationRoomNumber max length is 128.")] public string ReservationRoomNumber { get; set; } - [Required(ErrorMessage = "预约渠道为必填字段"), MaxLength(50, ErrorMessage = "预约渠道长度不超过50字符")] + [Required(ErrorMessage = "ReservationChannel is required."), MaxLength(50, ErrorMessage = "ReservationChannel max length is 50.")] public string ReservationChannel { get; set; } - [Required(ErrorMessage = "预约起始日期为必填字段")] + [Required(ErrorMessage = "ReservationStartDate is required.")] public DateTime ReservationStartDate { get; set; } - [Required(ErrorMessage = "预约结束日期为必填字段")] + [Required(ErrorMessage = "ReservationEndDate is required.")] public DateTime ReservationEndDate { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs index e24472de331b2f980f01a0f289d6918e192ea500..2c634c673cce4594924ea0c985af124896536962 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs @@ -30,8 +30,14 @@ namespace EOM.TSHotelManagement.Contract [Required(ErrorMessage = "客户类型为必填字段")] public int CustomerType { get; set; } - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "房间ID为必填字段")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } + public string? PricingCode { get; set; } [Required(ErrorMessage = "预约编号为必填字段"), MaxLength(128, ErrorMessage = "预约编号长度不超过128字符")] public string ReservationId { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs index 96c653ad346ef7bb4e778ef74431485577deddd1..8453d241677c8f8c27d659c0b1d17f59db99b4f7 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs @@ -4,8 +4,12 @@ namespace EOM.TSHotelManagement.Contract { public class CheckoutRoomDto : BaseInputDto { - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "房间ID为必填字段")] + public int? RoomId { get; set; } + [MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] public string CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs index 12adeb5d4a25cc79da95c0ce9643bd8eaa514d91..5fa473af0b5972489fc22ff907b14e2368718304 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs @@ -3,6 +3,8 @@ namespace EOM.TSHotelManagement.Contract public class CreateRoomInputDto : BaseInputDto { public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int RoomTypeId { get; set; } public string CustomerNumber { get; set; } public DateTime? LastCheckInTime { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs index 508d6bd9f7a1255d94663920693be5c5b2de65f1..d7fc93592edf41701db11a057acb3fd58b104a01 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs @@ -2,11 +2,15 @@ { public class ReadRoomInputDto : ListInputDto { + public int? Id { get; set; } public string? RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int? RoomTypeId { get; set; } public int? RoomStateId { get; set; } public string? RoomName { get; set; } - public DateOnly? LastCheckInTime { get; set; } + public DateTime? LastCheckInTime { get; set; } public string? CustomerNumber { get; set; } + public string? PricingCode { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs index 281357c378f2867495054a346331bd25d5dbe6ff..1d7a3daaf9318d62dc285300bc6c055b15c17ae6 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs @@ -4,6 +4,9 @@ { public int? Id { get; set; } public string RoomNumber { get; set; } + public string RoomArea { get; set; } + public int? RoomFloor { get; set; } + public string RoomLocator { get; set; } public string RoomName { get; set; } public int RoomTypeId { get; set; } public string CustomerNumber { get; set; } @@ -14,6 +17,18 @@ public string RoomState { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public decimal StandardRoomRent { get; set; } + public decimal StandardRoomDeposit { get; set; } + public decimal AppliedRoomRent { get; set; } + public decimal AppliedRoomDeposit { get; set; } + public decimal EffectiveRoomRent { get; set; } + public decimal EffectiveRoomDeposit { get; set; } + public string EffectivePricingCode { get; set; } + public string EffectivePricingName { get; set; } + public string PricingCode { get; set; } + public string PricingName { get; set; } + public int? PricingStayHours { get; set; } + public bool IsPricingTimedOut { get; set; } public string RoomLocation { get; set; } public int StayDays { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..3175e3a56b9a372e1e17ec6444ebcf493008ea9e --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs @@ -0,0 +1,21 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class ReadRoomPricingOutputDto + { + public int? RoomId { get; set; } + public string RoomNumber { get; set; } + public string RoomLocator { get; set; } + public int RoomTypeId { get; set; } + public string RoomTypeName { get; set; } + public string CurrentPricingCode { get; set; } + public string CurrentPricingName { get; set; } + public int? PricingStayHours { get; set; } + public bool IsPricingTimedOut { get; set; } + public string EffectivePricingCode { get; set; } + public string EffectivePricingName { get; set; } + public DateTime? LastCheckInTime { get; set; } + public decimal EffectiveRoomRent { get; set; } + public decimal EffectiveRoomDeposit { get; set; } + public List PricingItems { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs index e5caef7e1d6f9d9a07e155daeebad80d03f1add9..a9e31421a16b23bec9ae8e8c6631b6425d24b782 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs @@ -2,16 +2,18 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateRoomInputDto : BaseInputDto { + public int? Id { get; set; } public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int RoomTypeId { get; set; } public string CustomerNumber { get; set; } - public DateOnly? LastCheckInTime { get; set; } - public DateOnly? LastCheckOutTime { get; set; } + public DateTime? LastCheckInTime { get; set; } + public DateTime? LastCheckOutTime { get; set; } public int RoomStateId { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } public string RoomLocation { get; set; } + public string? PricingCode { get; set; } } } - - diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs index 0b43976024543e180d25f61d873144f58f68c1a9..51c33bbb8852371316121381dc5d5e9ddfc21b15 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs @@ -6,6 +6,7 @@ namespace EOM.TSHotelManagement.Contract public string RoomTypeName { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public List? PricingItems { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs index 4e21263967831d60211f736d4bd5f86152196d59..43293503ec9b2f5c2e03cba13468ab9d91445a9a 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs @@ -2,7 +2,10 @@ { public class ReadRoomTypeInputDto : ListInputDto { + public int? Id { get; set; } public string? RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int? RoomTypeId { get; set; } public string? RoomTypeName { get; set; } public decimal? RoomRent { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs index cdf0356495ad72c55e8e470202dbb3b5dbe18eca..cb727342c5b781d3f8a2af56bfd2a13721ba4e61 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs @@ -7,6 +7,7 @@ public string RoomTypeName { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public List? PricingItems { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..d30fb97ad7780e90daa5af8b255c095d3dee4051 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs @@ -0,0 +1,13 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class RoomTypePricingItemDto + { + public string PricingCode { get; set; } + public string PricingName { get; set; } + public decimal RoomRent { get; set; } + public decimal RoomDeposit { get; set; } + public int? StayHours { get; set; } + public int Sort { get; set; } + public bool IsDefault { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs index 075b67d52ee28a1eff8fca9775595e66e1a6581a..18896fdc1b2b8a816099ded111b4051e80e95f3d 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs @@ -6,6 +6,7 @@ namespace EOM.TSHotelManagement.Contract public string RoomTypeName { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public List? PricingItems { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs index 4a4d3d5e921f624a85239ba2ca62dd7c69ce4c6b..52519aaf178bf0c10b17701b2d57e78d84165e3b 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs @@ -4,11 +4,19 @@ namespace EOM.TSHotelManagement.Contract { public class TransferRoomDto : BaseInputDto { - [Required(ErrorMessage = "源房间编号为必填字段"), MaxLength(128, ErrorMessage = "源房间编号长度不超过128字符")] + [Required(ErrorMessage = "源房间ID为必填字段")] + public int? OriginalRoomId { get; set; } + [MaxLength(128, ErrorMessage = "源房间编号长度不超过128字符")] public string OriginalRoomNumber { get; set; } + public string? OriginalRoomArea { get; set; } + public int? OriginalRoomFloor { get; set; } - [Required(ErrorMessage = "目标房间编号为必填字段"), MaxLength(128, ErrorMessage = "目标房间编号长度不超过128字符")] + [Required(ErrorMessage = "目标房间ID为必填字段")] + public int? TargetRoomId { get; set; } + [MaxLength(128, ErrorMessage = "目标房间编号长度不超过128字符")] public string TargetRoomNumber { get; set; } + public string? TargetRoomArea { get; set; } + public int? TargetRoomFloor { get; set; } [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] public string CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs index 6b0e286a45124b4be3cc794d61935bfe694887cb..e6cc2886f358766d90e30ea48caebe96144e1a96 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs @@ -1,25 +1,28 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace EOM.TSHotelManagement.Contract { public class AddCustomerSpendInputDto { - [Required(ErrorMessage = "房间编号为必填字段")] + [Required(ErrorMessage = "SpendNumber is required.")] + public string SpendNumber { get; set; } + public string RoomNumber { get; set; } - [Required(ErrorMessage = "商品编号为必填字段")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [Required(ErrorMessage = "ProductNumber is required.")] public string ProductNumber { get; set; } - [Required(ErrorMessage = "商品名称为必填字段")] + [Required(ErrorMessage = "ProductName is required.")] public string ProductName { get; set; } - [Required(ErrorMessage = "数量为必填字段")] - public int Quantity { get; set; } - - [Required(ErrorMessage = "价格为必填字段")] - public decimal Price { get; set; } + [Required(ErrorMessage = "ConsumptionQuantity is required.")] + public int ConsumptionQuantity { get; set; } - public string WorkerNo { get; set; } + [Required(ErrorMessage = "ProductPrice is required.")] + public decimal ProductPrice { get; set; } public string SoftwareVersion { get; set; } } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/CreateSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/CreateSpendInputDto.cs index c9fd6f01d4a8bfbc8cdc71060b7092f9150d3f40..a1eb1d84a7eace3304798800ec8ce9c3ad80fc0c 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/CreateSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/CreateSpendInputDto.cs @@ -4,28 +4,31 @@ namespace EOM.TSHotelManagement.Contract { public class CreateSpendInputDto : BaseInputDto { - [Required(ErrorMessage = "商品编号为必填字段")] + [Required(ErrorMessage = "ProductNumber is required.")] public string ProductNumber { get; set; } - [Required(ErrorMessage = "消费编号为必填字段")] + [Required(ErrorMessage = "SpendNumber is required.")] public string SpendNumber { get; set; } + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + public string RoomNumber { get; set; } public string CustomerNumber { get; set; } public string ProductName { get; set; } - [Required(ErrorMessage = "消费数量为必填字段")] + [Required(ErrorMessage = "ConsumptionQuantity is required.")] public int ConsumptionQuantity { get; set; } - [Required(ErrorMessage = "商品单价为必填字段")] + [Required(ErrorMessage = "ProductPrice is required.")] public decimal ProductPrice { get; set; } - [Required(ErrorMessage = "消费金额为必填字段")] + [Required(ErrorMessage = "ConsumptionAmount is required.")] public decimal ConsumptionAmount { get; set; } - [Required(ErrorMessage = "消费时间为必填字段")] + [Required(ErrorMessage = "ConsumptionTime is required.")] public DateTime ConsumptionTime { get; set; } public string SettlementStatus { get; set; } @@ -33,4 +36,3 @@ namespace EOM.TSHotelManagement.Contract public string ConsumptionType { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs index 9a87c94aa711a29ef0aecf03e45056e76a18e52f..74677518c61a5f3659aad9fb0df08521eef4f37d 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs @@ -6,6 +6,8 @@ namespace EOM.TSHotelManagement.Contract { public string? SpendNumber { get; set; } + public int? RoomId { get; set; } + public string? RoomNumber { get; set; } public string? CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs index 4c92a75eca0d47cf8f376d5238e65f042ece7a07..801a9f55ee14ba08e81f30c3880ef9938e796ba1 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs @@ -5,6 +5,7 @@ namespace EOM.TSHotelManagement.Contract public class ReadSpendOutputDto : BaseOutputDto { public int? Id { get; set; } + public int? RoomId { get; set; } [UIDisplay("消费编号", false, false)] public string SpendNumber { get; set; } @@ -12,6 +13,12 @@ namespace EOM.TSHotelManagement.Contract [UIDisplay("房间号")] public string RoomNumber { get; set; } + public string RoomArea { get; set; } + + public int? RoomFloor { get; set; } + + public string RoomLocator { get; set; } + [UIDisplay("客户编号")] public string CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UndoCustomerSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UndoCustomerSpendInputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..f9905c4dd65ac6c30ddb5f5a7cf9b0b40dd362ec --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UndoCustomerSpendInputDto.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EOM.TSHotelManagement.Contract; + +public class UndoCustomerSpendInputDto : BaseInputDto +{ +} diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs index 60413f8c7123718244b33a4a8701020f0e115227..f75fd45d1b048b851a9da3ce5d4a73adc905f34b 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs @@ -4,9 +4,12 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateSpendInputDto : BaseInputDto { - [Required(ErrorMessage = "消费编号为必填字段")] + [Required(ErrorMessage = "SpendNumber is required.")] public string SpendNumber { get; set; } + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + public string RoomNumber { get; set; } public string OriginalRoomNumber { get; set; } @@ -15,16 +18,16 @@ namespace EOM.TSHotelManagement.Contract public string ProductName { get; set; } - [Required(ErrorMessage = "消费数量为必填字段")] + [Required(ErrorMessage = "ConsumptionQuantity is required.")] public int ConsumptionQuantity { get; set; } - [Required(ErrorMessage = "商品单价为必填字段")] + [Required(ErrorMessage = "ProductPrice is required.")] public decimal ProductPrice { get; set; } - [Required(ErrorMessage = "消费金额为必填字段")] + [Required(ErrorMessage = "ConsumptionAmount is required.")] public decimal ConsumptionAmount { get; set; } - [Required(ErrorMessage = "消费时间为必填字段")] + [Required(ErrorMessage = "ConsumptionTime is required.")] public DateTime ConsumptionTime { get; set; } public string SettlementStatus { get; set; } @@ -32,4 +35,3 @@ namespace EOM.TSHotelManagement.Contract public string ConsumptionType { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Common/Dto/BaseDto.cs b/EOM.TSHotelManagement.Contract/Common/Dto/BaseDto.cs index fefb777b19c5e65d0dac582c574c8764301ca695..8c8132ed68221743a71940dd918eea2a0a505a67 100644 --- a/EOM.TSHotelManagement.Contract/Common/Dto/BaseDto.cs +++ b/EOM.TSHotelManagement.Contract/Common/Dto/BaseDto.cs @@ -4,8 +4,12 @@ { public int? Id { get; set; } /// - /// Token + /// Access Token /// public string UserToken { get; set; } + /// + /// Refresh Token + /// + public string RefreshToken { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/CreateEmployeeInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/CreateEmployeeInputDto.cs index c8d99c4e60ccc45df8367be57d1a69cdb83ea124..5fb8e54e245cc6deb5564039b37fa14fe3d6a289 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/CreateEmployeeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/CreateEmployeeInputDto.cs @@ -10,7 +10,7 @@ namespace EOM.TSHotelManagement.Contract [Required(ErrorMessage = "员工姓名为必填字段")] [MaxLength(250, ErrorMessage = "员工姓名长度不超过250字符")] - public string EmployeeName { get; set; } + public string Name { get; set; } [Required(ErrorMessage = "员工性别为必填字段")] public int Gender { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeInputDto.cs index f465bf22519545cebe8128dfece6fb9ebf113f67..f527745cbb6162379994f259c8d55a50f63c13e3 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeInputDto.cs @@ -1,11 +1,11 @@ -using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common; namespace EOM.TSHotelManagement.Contract { public class ReadEmployeeInputDto : ListInputDto { public string? EmployeeId { get; set; } - public string? EmployeeName { get; set; } + public string? Name { get; set; } public string? Gender { get; set; } public DateOnly? DateOfBirth { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeOutputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeOutputDto.cs index 66b0108424ba7de38a6684b0b35cc618e0a0aa07..ee59de2c87680799fa548d88d50f4a5dc3aba7ea 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/ReadEmployeeOutputDto.cs @@ -1,10 +1,10 @@ -namespace EOM.TSHotelManagement.Contract +namespace EOM.TSHotelManagement.Contract { public class ReadEmployeeOutputDto : BaseOutputDto { public string EmployeeId { get; set; } - public string EmployeeName { get; set; } + public string Name { get; set; } public int Gender { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/UpdateEmployeeInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/UpdateEmployeeInputDto.cs index f0364f535c18741289f6ad8a3526ddd20cdd5b52..7d12dc348bb7b31e43ce6e869bf97a0b9884a1f9 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/UpdateEmployeeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/UpdateEmployeeInputDto.cs @@ -10,7 +10,7 @@ namespace EOM.TSHotelManagement.Contract [Required(ErrorMessage = "员工姓名为必填字段")] [MaxLength(250, ErrorMessage = "员工姓名长度不超过250字符")] - public string EmployeeName { get; set; } + public string Name { get; set; } [Required(ErrorMessage = "员工性别为必填字段")] public int Gender { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/CreateEmployeeCheckInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/CreateEmployeeCheckInputDto.cs index 7f2e1f5106d08ed530728f4bce50019897f68779..d0d7526c7a1f8e7d24d2f2c701c8da992b2ec708 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/CreateEmployeeCheckInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/CreateEmployeeCheckInputDto.cs @@ -4,36 +4,12 @@ namespace EOM.TSHotelManagement.Contract { public class CreateEmployeeCheckInputDto : BaseInputDto { - /// - /// 打卡编号 (Check-in/Check-out Number) - /// - [Required(ErrorMessage = "打卡编号为必填字段")] - [MaxLength(128, ErrorMessage = "打卡编号长度不超过128字符")] - public string CheckNumber { get; set; } /// /// 员工工号 (Employee ID) /// [Required(ErrorMessage = "员工工号为必填字段")] [MaxLength(128, ErrorMessage = "员工工号长度不超过128字符")] public string EmployeeId { get; set; } - - /// - /// 打卡时间 (Check-in/Check-out Time) - /// - [Required(ErrorMessage = "打卡时间为必填字段")] - public DateTime CheckTime { get; set; } - - /// - /// 打卡方式 (Check-in/Check-out Method) - /// - [Required(ErrorMessage = "打卡方式为必填字段")] - public string CheckMethod { get; set; } - - /// - /// 打卡状态 (Check-in/Check-out Status) - /// - [Required(ErrorMessage = "打卡状态为必填字段")] - public int CheckStatus { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs index 0b2ec8c961e0950f551f59a6a7316e3f99ec8f95..792fc39358179468f2d02f7330878db16e90946a 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs @@ -1,18 +1,26 @@ -namespace EOM.TSHotelManagement.Contract +using SqlSugar; + +namespace EOM.TSHotelManagement.Contract { public class ReadEmployeeCheckOutputDto : BaseOutputDto { public string EmployeeId { get; set; } public DateTime CheckTime { get; set; } - public string CheckStatus { get; set; } + public int CheckStatus { get; set; } public string CheckMethod { get; set; } + public string CheckMethodDescription { get; set; } public bool MorningChecked { get; set; } public bool EveningChecked { get; set; } public int CheckDay { get; set; } + + /// + /// 打卡状态描述 (Check-in/Check-out Status Description) + /// + public string CheckStatusDescription { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs index 5a6f6f41aeaa919356bfb281ea6771a14a21ea62..ff36650e80aa40fbc77bca23bb6e7020ded08d50 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs @@ -4,19 +4,36 @@ namespace EOM.TSHotelManagement.Contract { public class CreateEmployeeHistoryInputDto : BaseInputDto { + [Required(ErrorMessage = "履历编号为必填字段")] + public string HistoryNumber { get; set; } + [Required(ErrorMessage = "员工工号为必填字段")] [MaxLength(128, ErrorMessage = "员工工号长度不超过128字符")] public string EmployeeId { get; set; } - [Required(ErrorMessage = "变更日期为必填字段")] - public DateTime ChangeDate { get; set; } + /// + /// 开始时间 (Start Date) + /// + [Required(ErrorMessage = "开始时间为必填字段")] + public DateOnly StartDate { get; set; } + + /// + /// 结束时间 (End Date) + /// + [Required(ErrorMessage = "结束时间为必填字段")] + public DateOnly EndDate { get; set; } - [Required(ErrorMessage = "变更类型为必填字段")] - [MaxLength(128, ErrorMessage = "变更类型长度不超过128字符")] - public string ChangeType { get; set; } + /// + /// 职位 (Position) + /// + [Required(ErrorMessage = "职位为必填字段")] + public string Position { get; set; } - [MaxLength(500, ErrorMessage = "变更描述长度不超过500字符")] - public string ChangeDescription { get; set; } + /// + /// 公司 (Company) + /// + [Required(ErrorMessage = "公司为必填字段")] + public string Company { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs index 84c425bae4ed757533781703ae5ede34a3fb41c5..94db020929c974a80133c432192bee436b861e6c 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs @@ -1,24 +1,33 @@ +using SqlSugar; using System.ComponentModel.DataAnnotations; namespace EOM.TSHotelManagement.Contract { public class UpdateEmployeeHistoryInputDto : BaseInputDto { - [Required(ErrorMessage = "履历ID为必填字段")] - public int HistoryId { get; set; } + /// + /// 开始时间 (Start Date) + /// + [Required(ErrorMessage = "开始时间为必填字段")] + public DateOnly StartDate { get; set; } - [Required(ErrorMessage = "员工工号为必填字段")] - [MaxLength(128, ErrorMessage = "员工工号长度不超过128字符")] - public string EmployeeId { get; set; } + /// + /// 结束时间 (End Date) + /// + [Required(ErrorMessage = "结束时间为必填字段")] + public DateOnly EndDate { get; set; } - [Required(ErrorMessage = "变更日期为必填字段")] - public DateTime ChangeDate { get; set; } + /// + /// 职位 (Position) + /// + [Required(ErrorMessage = "职位为必填字段")] + public string Position { get; set; } - [MaxLength(128, ErrorMessage = "变更类型长度不超过128字符")] - public string ChangeType { get; set; } - - [MaxLength(500, ErrorMessage = "变更描述长度不超过500字符")] - public string ChangeDescription { get; set; } + /// + /// 公司 (Company) + /// + [Required(ErrorMessage = "公司为必填字段")] + public string Company { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/GrantRolePermissionsInputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/GrantRolePermissionsInputDto.cs index f593d5a3e1083e2587bad89382f92fa829c98612..769edea31c9ac16c1407cbe6e8b6f6078b55496d 100644 --- a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/GrantRolePermissionsInputDto.cs +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/GrantRolePermissionsInputDto.cs @@ -10,5 +10,7 @@ namespace EOM.TSHotelManagement.Contract.SystemManagement.Dto.Permission [Required(ErrorMessage = "权限编码集合为必填字段")] public List PermissionNumbers { get; set; } = new List(); + + public List MenuIds { get; set; } = new List(); } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/ReadRoleGrantOutputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/ReadRoleGrantOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f5e87bbaaf31dd2c372cf28a2f6cc92e7e95574 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/ReadRoleGrantOutputDto.cs @@ -0,0 +1,14 @@ +namespace EOM.TSHotelManagement.Contract.SystemManagement.Dto.Permission +{ + /// + /// 角色授权读取结果(菜单与权限独立) + /// + public class ReadRoleGrantOutputDto : BaseOutputDto + { + public string RoleNumber { get; set; } = string.Empty; + + public List PermissionNumbers { get; set; } = new(); + + public List MenuIds { get; set; } = new(); + } +} diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/UserRolePermissionOutputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/UserRolePermissionOutputDto.cs index 2e22503f512d6a5b8c05e43b0ab87ef102c05ef0..59830c471cbd449960f1efe5921f16e2ae397b09 100644 --- a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/UserRolePermissionOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/UserRolePermissionOutputDto.cs @@ -16,7 +16,9 @@ namespace EOM.TSHotelManagement.Contract [MaxLength(256, ErrorMessage = "菜单键长度不超过256字符")] public string? MenuKey { get; set; } + public int? MenuId { get; set; } + [MaxLength(256, ErrorMessage = "菜单名称长度不超过256字符")] public string? MenuName { get; set; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Quartz/ReadQuartzJobOutputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Quartz/ReadQuartzJobOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..8c9d2abbd250d08787e92336595bf2c69434c6bc --- /dev/null +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Quartz/ReadQuartzJobOutputDto.cs @@ -0,0 +1,26 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class ReadQuartzJobOutputDto + { + public string JobName { get; set; } + public string JobGroup { get; set; } + public string JobDescription { get; set; } + public bool IsDurable { get; set; } + public bool RequestsRecovery { get; set; } + + public string TriggerName { get; set; } + public string TriggerGroup { get; set; } + public string TriggerType { get; set; } + public string TriggerState { get; set; } + + public string CronExpression { get; set; } + public string TimeZoneId { get; set; } + public int? RepeatCount { get; set; } + public double? RepeatIntervalMs { get; set; } + + public DateTime? StartTimeUtc { get; set; } + public DateTime? EndTimeUtc { get; set; } + public DateTime? NextFireTimeUtc { get; set; } + public DateTime? PreviousFireTimeUtc { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/CreateSupervisionStatisticsInputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/CreateSupervisionStatisticsInputDto.cs index 0d1c33a16a81c746f23473abdd71430cb9e87230..7ad812584381cc1ed08af4aa44096fbaa980dd92 100644 --- a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/CreateSupervisionStatisticsInputDto.cs +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/CreateSupervisionStatisticsInputDto.cs @@ -22,6 +22,9 @@ namespace EOM.TSHotelManagement.Contract public string SupervisionStatistician { get; set; } public string SupervisionAdvice { get; set; } + + [Required(ErrorMessage = "统计时间为必填字段")] + public DateTime? SupervisionTime { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsInputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsInputDto.cs index 4cd6a935656c322f67b34240dcc285800b431105..75607718ccd7ccb04adb9fc0f37802ae33b4d4e3 100644 --- a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsInputDto.cs +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsInputDto.cs @@ -1,4 +1,6 @@ -namespace EOM.TSHotelManagement.Contract +using EOM.TSHotelManagement.Common; + +namespace EOM.TSHotelManagement.Contract { public class ReadSupervisionStatisticsInputDto : ListInputDto { @@ -12,6 +14,8 @@ public string? SupervisionStatistician { get; set; } public string? SupervisionAdvice { get; set; } + + public DateRangeDto DateRangeDto { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsOutputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsOutputDto.cs index 3d4b66c6eccf766e0c6f0df5314813d5152aecaf..b6ebf801aa8032880b307c8f4a8ebb370016a463 100644 --- a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/ReadSupervisionStatisticsOutputDto.cs @@ -1,4 +1,4 @@ -namespace EOM.TSHotelManagement.Contract +namespace EOM.TSHotelManagement.Contract { public class ReadSupervisionStatisticsOutputDto : BaseOutputDto { @@ -12,6 +12,8 @@ public string SupervisionStatistician { get; set; } public string SupervisionAdvice { get; set; } + + public DateTime SupervisionTime { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/UpdateSupervisionStatisticsInputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/UpdateSupervisionStatisticsInputDto.cs index eaf48a93907e142069df0aa250e5f11c17562bd9..f4c8027549c9c26cd453ca982746a1e6a0d36101 100644 --- a/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/UpdateSupervisionStatisticsInputDto.cs +++ b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/UpdateSupervisionStatisticsInputDto.cs @@ -22,6 +22,9 @@ namespace EOM.TSHotelManagement.Contract public string SupervisionStatistician { get; set; } public string SupervisionAdvice { get; set; } + + [Required(ErrorMessage = "统计时间为必填字段")] + public DateTime? SupervisionTime { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/TokenRefreshRequestDto.cs b/EOM.TSHotelManagement.Contract/TokenRefreshRequestDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..af02dc9b5a06e37e0af667228f9b354b38e371c7 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/TokenRefreshRequestDto.cs @@ -0,0 +1,7 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class TokenRefreshRequestDto : BaseDto + { + public string RefreshToken { get; set; } + } +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Contract/TokenRefreshResponseDto.cs b/EOM.TSHotelManagement.Contract/TokenRefreshResponseDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..d880f7cb9dba4c2a9cf426400e768060a2e0a937 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/TokenRefreshResponseDto.cs @@ -0,0 +1,8 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class TokenRefreshResponseDto : BaseDto + { + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Contract/Util/Dto/Dashboard/LogisticsDataOutputDto.cs b/EOM.TSHotelManagement.Contract/Util/Dto/Dashboard/LogisticsDataOutputDto.cs index 1e88175f4c4a581e7c05550b6dcb4054ae28c019..8f6b74eb7e8963ef1c1fdfc1065512db458615ba 100644 --- a/EOM.TSHotelManagement.Contract/Util/Dto/Dashboard/LogisticsDataOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Util/Dto/Dashboard/LogisticsDataOutputDto.cs @@ -34,7 +34,6 @@ namespace EOM.TSHotelManagement.Contract public string RecordId { get; set; } [JsonPropertyName("type")] - [JsonConverter(typeof(JsonStringEnumConverter))] public TempInventoryOperationType OperationType { get; set; } [JsonPropertyName("productName")] diff --git a/EOM.TSHotelManagement.Data/DatabaseInitializer/DatabaseInitializer.cs b/EOM.TSHotelManagement.Data/DatabaseInitializer/DatabaseInitializer.cs index e86d9cf0e9153c4b339c74707da7d2c2f630dc36..5e931f85b6db2bb81c4441e8f5a723bbb8a10702 100644 --- a/EOM.TSHotelManagement.Data/DatabaseInitializer/DatabaseInitializer.cs +++ b/EOM.TSHotelManagement.Data/DatabaseInitializer/DatabaseInitializer.cs @@ -102,7 +102,6 @@ namespace EOM.TSHotelManagement.Data .ToArray(); db.CodeFirst.InitTables(needCreateTableTypes); - EnsureTwoFactorForeignKeys(db, dbSettings.DbType); Console.WriteLine("Database schema initialized"); @@ -259,58 +258,6 @@ namespace EOM.TSHotelManagement.Data return configString; } - private void EnsureTwoFactorForeignKeys(ISqlSugarClient db, DbType dbType) - { - try - { - if (dbType is DbType.MySql or DbType.MySqlConnector) - { - EnsureMySqlForeignKey(db, "two_factor_auth", "fk_2fa_employee", "employee_pk", "employee", "id"); - EnsureMySqlForeignKey(db, "two_factor_auth", "fk_2fa_administrator", "administrator_pk", "administrator", "id"); - EnsureMySqlForeignKey(db, "two_factor_auth", "fk_2fa_customer_account", "customer_account_pk", "customer_account", "id"); - EnsureMySqlForeignKey(db, "two_factor_recovery_code", "fk_2fa_recovery_auth", "two_factor_auth_pk", "two_factor_auth", "id", "CASCADE"); - } - } - catch (Exception ex) - { - Console.WriteLine($"EnsureTwoFactorForeignKeys skipped: {ex.Message}"); - } - } - - private static void EnsureMySqlForeignKey( - ISqlSugarClient db, - string tableName, - string constraintName, - string columnName, - string referenceTable, - string referenceColumn, - string onDeleteAction = "SET NULL") - { - var existsSql = @"SELECT COUNT(1) - FROM information_schema.TABLE_CONSTRAINTS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = @tableName - AND CONSTRAINT_NAME = @constraintName"; - - var exists = db.Ado.GetInt( - existsSql, - new SugarParameter("@tableName", tableName), - new SugarParameter("@constraintName", constraintName)) > 0; - if (exists) - { - return; - } - - var addConstraintSql = $@"ALTER TABLE `{tableName}` - ADD CONSTRAINT `{constraintName}` - FOREIGN KEY (`{columnName}`) - REFERENCES `{referenceTable}`(`{referenceColumn}`) - ON UPDATE RESTRICT - ON DELETE {onDeleteAction};"; - - db.Ado.ExecuteCommand(addConstraintSql); - } - private void SeedInitialData(ISqlSugarClient db) { Console.WriteLine("Initializing database data..."); @@ -333,10 +280,10 @@ namespace EOM.TSHotelManagement.Data PassportType _ => 6, Menu _ => 7, NavBar _ => 8, - SystemInformation _ => 9, - PromotionContent _ => 10, - Administrator _ => 11, - Employee _ => 12, + PromotionContent _ => 9, + Administrator _ => 10, + Employee _ => 11, + UserFavoriteCollection _ => 12, _ => 99 }) .ToList(); @@ -373,10 +320,6 @@ namespace EOM.TSHotelManagement.Data alreadyExists = db.Queryable().Any(a => a.PromotionContentMessage == promoContent.PromotionContentMessage); break; - case SystemInformation sysInfo: - alreadyExists = db.Queryable().Any(a => a.UrlAddress == sysInfo.UrlAddress); - break; - case Nation nation: alreadyExists = db.Queryable().Any(a => a.NationName == nation.NationName); break; @@ -401,6 +344,10 @@ namespace EOM.TSHotelManagement.Data alreadyExists = db.Queryable().Any(a => a.EmployeeId == employee.EmployeeId); break; + case UserFavoriteCollection favoriteCollection: + alreadyExists = db.Queryable().Any(a => a.UserNumber == favoriteCollection.UserNumber); + break; + case Permission permission: alreadyExists = db.Queryable().Any(a => a.PermissionNumber == permission.PermissionNumber); break; @@ -511,7 +458,7 @@ namespace EOM.TSHotelManagement.Data if (adminUser != null) { var hasBind = db.Queryable() - .Any(ur => ur.UserNumber == adminUser.Number && ur.RoleNumber == adminRoleNumber && ur.IsDelete != 1); + .Any(ur => ur.UserNumber == adminUser.Number && ur.RoleNumber == adminRoleNumber); if (!hasBind) { @@ -519,7 +466,6 @@ namespace EOM.TSHotelManagement.Data { UserNumber = adminUser.Number, RoleNumber = adminRoleNumber, - IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }).ExecuteCommand(); @@ -533,12 +479,13 @@ namespace EOM.TSHotelManagement.Data // 3) Grant ALL permissions to admin role var allPermNumbers = db.Queryable() - .Where(p => p.IsDelete != 1) .Select(p => p.PermissionNumber) .ToList(); var existingRolePerms = db.Queryable() - .Where(rp => rp.RoleNumber == adminRoleNumber && rp.IsDelete != 1) + .Where(rp => rp.RoleNumber == adminRoleNumber + && rp.PermissionNumber != null + && rp.PermissionNumber != "") .Select(rp => rp.PermissionNumber) .ToList(); @@ -554,7 +501,6 @@ namespace EOM.TSHotelManagement.Data { RoleNumber = adminRoleNumber, PermissionNumber = p!, - IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }).ToList(); @@ -650,4 +596,3 @@ namespace EOM.TSHotelManagement.Data #endregion } } - diff --git a/EOM.TSHotelManagement.Data/EOM.TSHotelManagement.Data.csproj b/EOM.TSHotelManagement.Data/EOM.TSHotelManagement.Data.csproj index bf70ab1b27d447da3e1e08a5ce210eed7478c331..ba5ee0de0f43c5c9e648384d6107e58d8bb4ce4b 100644 --- a/EOM.TSHotelManagement.Data/EOM.TSHotelManagement.Data.csproj +++ b/EOM.TSHotelManagement.Data/EOM.TSHotelManagement.Data.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/EOM.TSHotelManagement.Data/Repository/GenericRepository.cs b/EOM.TSHotelManagement.Data/Repository/GenericRepository.cs index 415449c1f36aea27d5911dd554c870cd14d5ccec..36992f4d5c498d4764d1f035a5f11769c64d493e 100644 --- a/EOM.TSHotelManagement.Data/Repository/GenericRepository.cs +++ b/EOM.TSHotelManagement.Data/Repository/GenericRepository.cs @@ -45,7 +45,7 @@ namespace EOM.TSHotelManagement.Data public override bool Insert(T entity) { - if (entity is BaseEntity baseEntity) + if (entity is AuditEntity baseEntity) { var currentUser = GetCurrentUser(); if (!baseEntity.DataInsDate.HasValue) @@ -62,7 +62,7 @@ namespace EOM.TSHotelManagement.Data { Expression>? rowVersionWhere = null; - if (entity is BaseEntity baseEntity) + if (entity is AuditEntity baseEntity) { var currentUser = GetCurrentUser(); if (!baseEntity.DataChgDate.HasValue) @@ -110,7 +110,7 @@ namespace EOM.TSHotelManagement.Data foreach (var entity in updateObjs) { - if (entity is BaseEntity baseEntity) + if (entity is AuditEntity baseEntity) { var currentUser = GetCurrentUser(); if (!baseEntity.DataChgDate.HasValue) @@ -146,7 +146,7 @@ namespace EOM.TSHotelManagement.Data public bool SoftDelete(T entity) { - if (entity is BaseEntity baseEntity) + if (entity is SoftDeleteEntity baseEntity) { var currentUser = GetCurrentUser(); if (!baseEntity.DataChgDate.HasValue) @@ -220,7 +220,7 @@ namespace EOM.TSHotelManagement.Data // 更新内存中的实体状态 foreach (var entity in entities) { - if (entity is BaseEntity baseEntity) + if (entity is SoftDeleteEntity baseEntity) { hasBaseEntity = true; baseEntity.IsDelete = 1; @@ -242,9 +242,9 @@ namespace EOM.TSHotelManagement.Data totalAffected += base.Context.Updateable(batch) .UpdateColumns(new[] { - nameof(BaseEntity.IsDelete), - nameof(BaseEntity.DataChgUsr), - nameof(BaseEntity.DataChgDate) + nameof(SoftDeleteEntity.IsDelete), + nameof(AuditEntity.DataChgUsr), + nameof(AuditEntity.DataChgDate) }) .ExecuteCommand(); } diff --git a/EOM.TSHotelManagement.Domain/Application/FavoriteCollection/UserFavoriteCollection.cs b/EOM.TSHotelManagement.Domain/Application/FavoriteCollection/UserFavoriteCollection.cs new file mode 100644 index 0000000000000000000000000000000000000000..3e8412ab1297361bf9731463bc6a9ec4462c3f48 --- /dev/null +++ b/EOM.TSHotelManagement.Domain/Application/FavoriteCollection/UserFavoriteCollection.cs @@ -0,0 +1,94 @@ +using SqlSugar; + +namespace EOM.TSHotelManagement.Domain +{ + /// + /// 用户收藏夹快照实体 + /// + [SugarTable("user_favorite_collection", "User favorite collection snapshot", true)] + public class UserFavoriteCollection : AuditEntity + { + /// + /// 主键 + /// + [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "Id")] + public int Id { get; set; } + + /// + /// JWT 中的用户编号 + /// + [SugarColumn( + ColumnName = "user_number", + Length = 128, + IsNullable = false, + UniqueGroupNameList = new[] { "UK_user_favorite_collection_user_number" }, + ColumnDescription = "User number resolved from JWT" + )] + public string UserNumber { get; set; } = string.Empty; + + /// + /// 登录类型 + /// + [SugarColumn( + ColumnName = "login_type", + Length = 32, + IsNullable = true, + ColumnDescription = "Login type" + )] + public string? LoginType { get; set; } + + /// + /// 当前账号 + /// + [SugarColumn( + ColumnName = "account", + Length = 128, + IsNullable = true, + ColumnDescription = "Account" + )] + public string? Account { get; set; } + + /// + /// 收藏路由 JSON 快照 + /// + [SugarColumn( + ColumnName = "favorite_routes_json", + ColumnDataType = "text", + IsNullable = false, + ColumnDescription = "Favorite routes JSON snapshot" + )] + public string FavoriteRoutesJson { get; set; } = "[]"; + + /// + /// 收藏数量 + /// + [SugarColumn( + ColumnName = "route_count", + IsNullable = false, + DefaultValue = "0", + ColumnDescription = "Favorite route count" + )] + public int RouteCount { get; set; } + + /// + /// 业务更新时间 + /// + [SugarColumn( + ColumnName = "updated_at", + IsNullable = false, + ColumnDescription = "Snapshot updated time from client" + )] + public DateTime UpdatedAt { get; set; } + + /// + /// 最后触发来源 + /// + [SugarColumn( + ColumnName = "triggered_by", + Length = 32, + IsNullable = true, + ColumnDescription = "Last trigger source" + )] + public string? TriggeredBy { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Domain/Application/NavBar/NavBar.cs b/EOM.TSHotelManagement.Domain/Application/NavBar/NavBar.cs index b264880f45638f9d67b6d2b1f6b5fa107b44b3e1..d38eda20f455c917faf772afa19a5acaff2c0876 100644 --- a/EOM.TSHotelManagement.Domain/Application/NavBar/NavBar.cs +++ b/EOM.TSHotelManagement.Domain/Application/NavBar/NavBar.cs @@ -1,4 +1,4 @@ -using SqlSugar; +using SqlSugar; namespace EOM.TSHotelManagement.Domain { @@ -6,7 +6,7 @@ namespace EOM.TSHotelManagement.Domain /// 导航栏配置表 (Navigation Bar Configuration) /// [SugarTable("nav_bar", "导航栏配置表 (Navigation Bar Configuration)", true)] - public class NavBar : BaseEntity + public class NavBar : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/BaseEntity.cs b/EOM.TSHotelManagement.Domain/BaseEntity.cs deleted file mode 100644 index 68954f79cd610901814f60bbe159981e32d5b17b..0000000000000000000000000000000000000000 --- a/EOM.TSHotelManagement.Domain/BaseEntity.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; - -namespace EOM.TSHotelManagement.Domain -{ - public class BaseEntity - { - /// - /// 删除标识 - /// - [SqlSugar.SugarColumn(ColumnName = "delete_mk", Length = 11, IsNullable = false, DefaultValue = "0")] - public int? IsDelete { get; set; } = 0; - /// - /// 资料创建人 - /// - [SqlSugar.SugarColumn(ColumnName = "datains_usr", Length = 128, IsOnlyIgnoreUpdate = true, IsNullable = true)] - public string? DataInsUsr { get; set; } - /// - /// 资料创建时间 - /// - [SqlSugar.SugarColumn(ColumnName = "datains_date", IsOnlyIgnoreUpdate = true, IsNullable = true)] - public DateTime? DataInsDate { get; set; } - /// - /// 资料更新人 - /// - [SqlSugar.SugarColumn(ColumnName = "datachg_usr", Length = 128, IsOnlyIgnoreInsert = true, IsNullable = true)] - public string? DataChgUsr { get; set; } - /// - /// 资料更新时间 - /// - [SqlSugar.SugarColumn(ColumnName = "datachg_date", IsOnlyIgnoreInsert = true, IsNullable = true)] - public DateTime? DataChgDate { get; set; } - /// - /// 行版本(乐观锁) - /// - [SqlSugar.SugarColumn(ColumnName = "row_version", IsNullable = false, DefaultValue = "1")] - public long RowVersion { get; set; } = 1; - /// - /// Token - /// - [SqlSugar.SugarColumn(IsIgnore = true)] - public string? UserToken { get; set; } - } -} diff --git a/EOM.TSHotelManagement.Domain/Business/Asset/Asset.cs b/EOM.TSHotelManagement.Domain/Business/Asset/Asset.cs index 7f79ea6e4e685a1e92ff4d8e0233ceaa08f54b21..4435c37776b820c6d3e2df9f975b8a2eb10a0a41 100644 --- a/EOM.TSHotelManagement.Domain/Business/Asset/Asset.cs +++ b/EOM.TSHotelManagement.Domain/Business/Asset/Asset.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 资产管理 /// [SqlSugar.SugarTable("asset")] - public class Asset : BaseEntity + public class Asset : SoftDeleteEntity { /// /// 编号 (ID) @@ -105,7 +105,7 @@ namespace EOM.TSHotelManagement.Domain /// 资产经办人姓名 (Acquired By - Employee Name) /// [SqlSugar.SugarColumn(IsIgnore = true)] - public string AcquiredByEmployeeName { get; set; } + public string AcquiredByName { get; set; } } diff --git a/EOM.TSHotelManagement.Domain/Business/Customer/CustoSpend.cs b/EOM.TSHotelManagement.Domain/Business/Customer/CustoSpend.cs deleted file mode 100644 index b189e5b313af9564a96cd0d2dea9c319bf8cfd51..0000000000000000000000000000000000000000 --- a/EOM.TSHotelManagement.Domain/Business/Customer/CustoSpend.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - *模块说明:酒店盈利情况类 - */ -namespace EOM.TSHotelManagement.Domain -{ - /// - /// 酒店盈利情况 - /// - public class CustoSpend - { - /// - /// 年 - /// - public string Years { get; set; } - /// - /// 总金额 - /// - public decimal Money { get; set; } - - } -} diff --git a/EOM.TSHotelManagement.Domain/Business/Customer/CustoType.cs b/EOM.TSHotelManagement.Domain/Business/Customer/CustoType.cs index 311987e5c4d8ec68f8c7c4eedc4a687dd4a967e6..0f3f379f511ba4c7c5d3259aedf2127081731859 100644 --- a/EOM.TSHotelManagement.Domain/Business/Customer/CustoType.cs +++ b/EOM.TSHotelManagement.Domain/Business/Customer/CustoType.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 客户类型 /// [SqlSugar.SugarTable("custo_type")] - public class CustoType : BaseEntity + public class CustoType : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Business/Customer/Customer.cs b/EOM.TSHotelManagement.Domain/Business/Customer/Customer.cs index ec5865384ee92b8c55e5ee71f5f2806fc9315be8..6c49b2e5fe742917cd1f8181d4023264a042faed 100644 --- a/EOM.TSHotelManagement.Domain/Business/Customer/Customer.cs +++ b/EOM.TSHotelManagement.Domain/Business/Customer/Customer.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 客户信息 /// [SqlSugar.SugarTable("customer")] - public class Customer : BaseEntity + public class Customer : Personnel //SoftDeleteEntity { /// /// 编号 (ID) @@ -45,66 +45,6 @@ namespace EOM.TSHotelManagement.Domain [SqlSugar.SugarColumn(ColumnName = "custo_no", IsPrimaryKey = true, IsNullable = false, Length = 128, ColumnDescription = "客户编号 (Customer Number)")] public string CustomerNumber { get; set; } - /// - /// 客户名称 (Customer Name) - /// - [SqlSugar.SugarColumn(ColumnName = "custo_name", IsNullable = false, Length = 250, ColumnDescription = "客户名称 (Customer Name)")] - public string CustomerName { get; set; } - - /// - /// 客户性别 (Customer Gender) - /// - [SqlSugar.SugarColumn(ColumnName = "custo_gender", IsNullable = false, ColumnDescription = "客户性别 (Customer Gender)")] - public int? CustomerGender { get; set; } - - /// - /// 证件类型 (Passport Type) - /// - [SqlSugar.SugarColumn(ColumnName = "passport_type", IsNullable = false, ColumnDescription = "客户性别 (Customer Gender)")] - public int PassportId { get; set; } - - /// - /// 性别 (Gender) - /// - [SqlSugar.SugarColumn(IsIgnore = true)] - public string GenderName { get; set; } - - /// - /// 客户电话 (Customer Phone Number) - /// - [SqlSugar.SugarColumn(ColumnName = "custo_tel", IsNullable = false, Length = 256, ColumnDescription = "客户电话 (Customer Phone Number)")] - public string CustomerPhoneNumber { get; set; } - - /// - /// 出生日期 (Date of Birth) - /// - [SqlSugar.SugarColumn(ColumnName = "custo_birth", IsNullable = false, ColumnDescription = "出生日期 (Date of Birth)")] - public DateOnly DateOfBirth { get; set; } - - /// - /// 客户类型名称 (Customer Type Name) - /// - [SqlSugar.SugarColumn(IsIgnore = true)] - public string CustomerTypeName { get; set; } - - /// - /// 证件类型名称 (Passport Type Name) - /// - [SqlSugar.SugarColumn(IsIgnore = true)] - public string PassportName { get; set; } - - /// - /// 证件号码 (Passport ID) - /// - [SqlSugar.SugarColumn(ColumnName = "passport_id", IsNullable = false, Length = 256, ColumnDescription = "证件号码 (Passport ID)")] - public string IdCardNumber { get; set; } - - /// - /// 居住地址 (Customer Address) - /// - [SqlSugar.SugarColumn(ColumnName = "custo_address", IsNullable = true, Length = 256, ColumnDescription = "居住地址 (Customer Address)")] - public string CustomerAddress { get; set; } - /// /// 客户类型 (Customer Type) /// diff --git a/EOM.TSHotelManagement.Domain/Business/Customer/CustomerAccount.cs b/EOM.TSHotelManagement.Domain/Business/Customer/CustomerAccount.cs index 957fe625782f6fa655d3fbd8a8a146f957bf1043..79046f0e5dd53623b8e244bd71152dd3113e1918 100644 --- a/EOM.TSHotelManagement.Domain/Business/Customer/CustomerAccount.cs +++ b/EOM.TSHotelManagement.Domain/Business/Customer/CustomerAccount.cs @@ -3,7 +3,7 @@ using System; namespace EOM.TSHotelManagement.Domain { [SqlSugar.SugarTable("customer_account", "客户账号表")] - public class CustomerAccount : BaseEntity + public class CustomerAccount : SoftDeleteEntity { /// /// ID (索引ID) diff --git a/EOM.TSHotelManagement.Domain/Business/Customer/PassPortType.cs b/EOM.TSHotelManagement.Domain/Business/Customer/PassPortType.cs index 66a071716106580b8975eac0a26995871c9ed72e..79dccabeb769e56973673e67521e02db2b9331cf 100644 --- a/EOM.TSHotelManagement.Domain/Business/Customer/PassPortType.cs +++ b/EOM.TSHotelManagement.Domain/Business/Customer/PassPortType.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 证件类型 /// [SqlSugar.SugarTable("passport_type")] - public class PassportType : BaseEntity + public class PassportType : SoftDeleteEntity { [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "编号 (ID)")] public int Id { get; set; } diff --git a/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs b/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs index 84bbe0675e0156a8136e286b509c825bf19b48ca..32c2891b152540086c9921fdc643eea053a01ba6 100644 --- a/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs +++ b/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 水电信息 /// [SqlSugar.SugarTable("energy_management", "水电信息")] - public class EnergyManagement : BaseEntity + public class EnergyManagement : SoftDeleteEntity { /// /// 编号 (ID) @@ -51,17 +51,20 @@ namespace EOM.TSHotelManagement.Domain [SqlSugar.SugarColumn(ColumnName = "room_no", Length = 128, IsNullable = false, ColumnDescription = "房间编号 (Room Number)")] public string RoomNumber { get; set; } + [SqlSugar.SugarColumn(ColumnName = "room_id", IsNullable = true, ColumnDescription = "Room ID")] + public int? RoomId { get; set; } + /// /// 开始使用时间 (Start Date) /// [SqlSugar.SugarColumn(ColumnName = "use_date", IsNullable = false, ColumnDescription = "开始使用时间 (Start Date)")] - public DateOnly StartDate { get; set; } + public DateTime StartDate { get; set; } /// /// 结束使用时间 (End Date) /// [SqlSugar.SugarColumn(ColumnName = "end_date", IsNullable = false, ColumnDescription = "结束使用时间 (End Date)")] - public DateOnly EndDate { get; set; } + public DateTime EndDate { get; set; } /// /// 水费 (Water Usage) diff --git a/EOM.TSHotelManagement.Domain/Business/News/News.cs b/EOM.TSHotelManagement.Domain/Business/News/News.cs index fe1706e664c8eee8a4b4465b3cc427889c9bdd0f..57c66bbe88ac54b1c6d1fcbccff161e45d54484f 100644 --- a/EOM.TSHotelManagement.Domain/Business/News/News.cs +++ b/EOM.TSHotelManagement.Domain/Business/News/News.cs @@ -1,9 +1,9 @@ -using System; +using System; namespace EOM.TSHotelManagement.Domain { [SqlSugar.SugarTable("news", "新闻动态")] - public class News : BaseEntity + public class News : SoftDeleteEntity { [SqlSugar.SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "索引ID")] public int Id { get; set; } diff --git a/EOM.TSHotelManagement.Domain/Business/PromotionContent/PromotionContent.cs b/EOM.TSHotelManagement.Domain/Business/PromotionContent/PromotionContent.cs index d6eec3e3890ccda2a6e9d886538be53486b70d77..211ff73570abe57138d316c45931af0f9f6645b1 100644 --- a/EOM.TSHotelManagement.Domain/Business/PromotionContent/PromotionContent.cs +++ b/EOM.TSHotelManagement.Domain/Business/PromotionContent/PromotionContent.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// APP横幅配置表 (APP Banner Configuration) /// [SugarTable("app_banner", "APP横幅配置表 (APP Banner Configuration)")] - public class PromotionContent : BaseEntity + public class PromotionContent : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs b/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs index cf497f24e1732492d954b3ebe368894ecf59654f..93bbf613e0c20fdf48cfb24dafa52e88562a6564 100644 --- a/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs +++ b/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 预约信息表 (Reservation Information) /// [SugarTable("reser", "预约信息表 (Reservation Information)")] - public class Reser : BaseEntity + public class Reser : SoftDeleteEntity { /// /// 编号 (ID) @@ -96,6 +96,9 @@ namespace EOM.TSHotelManagement.Domain )] public string ReservationRoomNumber { get; set; } + [SugarColumn(ColumnName = "room_id", ColumnDescription = "Room ID", IsNullable = true)] + public int? RoomId { get; set; } + /// /// 预约起始日期 (Reservation Start Date) /// diff --git a/EOM.TSHotelManagement.Domain/Business/Room/Room.cs b/EOM.TSHotelManagement.Domain/Business/Room/Room.cs index b161cef714cfc7cd040478118fff434cf6461f4c..00ca7b29d672b4f06f719a6d7047a7855d6381e3 100644 --- a/EOM.TSHotelManagement.Domain/Business/Room/Room.cs +++ b/EOM.TSHotelManagement.Domain/Business/Room/Room.cs @@ -1,177 +1,165 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - *模块说明:房间类 - */ - using SqlSugar; using System; namespace EOM.TSHotelManagement.Domain { - /// - /// 酒店房间信息表 (Hotel Room Information) - /// - [SugarTable("room", "酒店房间信息表 (Hotel Room Information)")] - public class Room : BaseEntity + [SugarTable("room", "Hotel room information")] + public class Room : SoftDeleteEntity { - /// - /// 编号 (ID) - /// - [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "编号 (ID)")] + [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "ID")] public int Id { get; set; } - /// - /// 房间编号 (Room Number) - /// [SugarColumn( ColumnName = "room_no", - IsPrimaryKey = true, - ColumnDescription = "房间唯一编号 (Unique Room Number)", + ColumnDescription = "Room number", IsNullable = false, - Length = 128 + Length = 128, + UniqueGroupNameList = new[] { "UK_room_number_area_floor" } )] - public string RoomNumber { get; set; } - /// - /// 房间类型ID (Room Type ID) - /// + [SugarColumn( + ColumnName = "room_area", + ColumnDescription = "Room area", + IsNullable = true, + Length = 128, + UniqueGroupNameList = new[] { "UK_room_number_area_floor" } + )] + public string RoomArea { get; set; } + + [SugarColumn( + ColumnName = "room_floor", + ColumnDescription = "Room floor", + IsNullable = true, + UniqueGroupNameList = new[] { "UK_room_number_area_floor" } + )] + public int? RoomFloor { get; set; } + [SugarColumn( ColumnName = "room_type", - ColumnDescription = "房间类型ID (关联房间类型表)", + ColumnDescription = "Room type ID", IsNullable = false )] - public int RoomTypeId { get; set; } - /// - /// 客户编号 (Customer Number) - /// [SugarColumn( ColumnName = "custo_no", - ColumnDescription = "当前入住客户编号 (Linked Customer ID)", + ColumnDescription = "Linked customer number", IsNullable = true, Length = 128 )] public string CustomerNumber { get; set; } - /// - /// 客户姓名 (Customer Name)(不存储到数据库) - /// [SugarColumn(IsIgnore = true)] public string CustomerName { get; set; } - /// - /// 最后一次入住时间 (Last Check-In Time) - /// [SugarColumn( ColumnName = "check_in_time", - ColumnDescription = "最后一次入住时间 (Last Check-In Time)", + ColumnDescription = "Last check-in time", IsNullable = true )] - public DateOnly? LastCheckInTime { get; set; } + public DateTime? LastCheckInTime { get; set; } - /// - /// 最后一次退房时间 (Last Check-Out Time) - /// [SugarColumn( ColumnName = "check_out_time", - ColumnDescription = "最后一次退房时间 (Last Check-Out Time)", + ColumnDescription = "Last check-out time", IsNullable = true )] - public DateOnly LastCheckOutTime { get; set; } + public DateTime? LastCheckOutTime { get; set; } - /// - /// 房间状态ID (Room State ID) - /// [SugarColumn( ColumnName = "room_state_id", - ColumnDescription = "房间状态ID (如0-空闲/1-已入住)", + ColumnDescription = "Room state ID", IsNullable = false )] - public int RoomStateId { get; set; } - /// - /// 房间状态名称 (Room State Name)(不存储到数据库) - /// [SugarColumn(IsIgnore = true)] public string RoomState { get; set; } - /// - /// 房间单价 (Room Rent) - /// [SugarColumn( ColumnName = "room_rent", - ColumnDescription = "房间单价(单位:元) (Price per Night in CNY)", + ColumnDescription = "Room rent", IsNullable = false, DecimalDigits = 2 )] - public decimal RoomRent { get; set; } - /// - /// 房间押金 (Room Deposit) - /// [SugarColumn( ColumnName = "room_deposit", - ColumnDescription = "房间押金(单位:元) (Deposit Amount in CNY)", + ColumnDescription = "Room deposit", IsNullable = false, DecimalDigits = 2, DefaultValue = "0.00" )] - public decimal RoomDeposit { get; set; } - /// - /// 房间位置 (Room Location) - /// [SugarColumn( - ColumnName = "room_position", - ColumnDescription = "房间位置描述 (如楼层+门牌号)", + ColumnName = "applied_room_rent", + ColumnDescription = "Applied room rent for current stay", IsNullable = false, - Length = 200 + DecimalDigits = 2, + DefaultValue = "0.00" + )] + public decimal AppliedRoomRent { get; set; } + + [SugarColumn( + ColumnName = "applied_room_deposit", + ColumnDescription = "Applied room deposit for current stay", + IsNullable = false, + DecimalDigits = 2, + DefaultValue = "0.00" )] + public decimal AppliedRoomDeposit { get; set; } + [SugarColumn( + ColumnName = "pricing_code", + ColumnDescription = "Applied pricing code", + IsNullable = true, + Length = 64 + )] + public string RoomPricingCode { get; set; } + + [SugarColumn( + ColumnName = "pricing_name", + ColumnDescription = "Applied pricing name", + IsNullable = true, + Length = 128 + )] + public string RoomPricingName { get; set; } + + [SugarColumn( + ColumnName = "pricing_stay_hours", + ColumnDescription = "Applied pricing allowed stay hours", + IsNullable = true + )] + public int? PricingStayHours { get; set; } + + [SugarColumn( + ColumnName = "pricing_start_time", + ColumnDescription = "Applied pricing timing start time", + IsNullable = true + )] + public DateTime? PricingStartTime { get; set; } + + [SugarColumn( + ColumnName = "room_location", + ColumnDescription = "Room location", + IsNullable = false, + Length = 200 + )] public string RoomLocation { get; set; } - /// - /// 客户类型名称 (Customer Type Name)(不存储到数据库) - /// [SugarColumn(IsIgnore = true)] public string CustomerTypeName { get; set; } - /// - /// 房间名称 (Room Name)(不存储到数据库) - /// [SugarColumn(IsIgnore = true)] public string RoomName { get; set; } - /// - /// 最后一次入住时间(格式化字符串) (Last Check-In Time Formatted) - /// + [SugarColumn(IsIgnore = true)] + public string RoomLocator { get; set; } + [SugarColumn(IsIgnore = true)] public string LastCheckInTimeFormatted { get; set; } } - } diff --git a/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs b/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs index 783f7c2e73d2892eb34b7e64d746e39e6cb4a9b7..3812a5fdf697030f5d3cda3d46084214ed864523 100644 --- a/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs +++ b/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 房间类型配置表 (Room Type Configuration) /// [SugarTable("room_type", "房间类型配置表 (Room Type Configuration)")] - public class RoomType : BaseEntity + public class RoomType : SoftDeleteEntity { /// /// 编号 (ID) @@ -90,6 +90,14 @@ namespace EOM.TSHotelManagement.Domain public decimal RoomDeposit { get; set; } + [SugarColumn( + ColumnName = "pricing_items_json", + ColumnDataType = "text", + IsNullable = true, + ColumnDescription = "Additional Pricing Items JSON" + )] + public string PricingItemsJson { get; set; } + /// /// 删除标记描述 (Delete Mark Description)(不存储到数据库) /// diff --git a/EOM.TSHotelManagement.Domain/Business/Sellthing/SellThing.cs b/EOM.TSHotelManagement.Domain/Business/Sellthing/SellThing.cs index 8039a86f016d9ff85e50233303b56cede0a86d75..80ebc57e9e32abcb3e338c6c9a175a6029906d33 100644 --- a/EOM.TSHotelManagement.Domain/Business/Sellthing/SellThing.cs +++ b/EOM.TSHotelManagement.Domain/Business/Sellthing/SellThing.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -32,7 +32,7 @@ namespace EOM.TSHotelManagement.Domain /// 商品信息表 (Product Information) /// [SugarTable("sellthing", "商品信息表 (Product Information)")] - public class SellThing : BaseEntity + public class SellThing : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs b/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs index 514641fe30c2334dd84903a28abea66ff5a9d3c2..412fd44f67714fb1a25427564a45457ea731eff6 100644 --- a/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs +++ b/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 消费信息 (Consumption Information) /// [SugarTable("customer_spend")] - public class Spend : BaseEntity + public class Spend : SoftDeleteEntity { [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "编号 (ID)")] public int Id { get; set; } @@ -46,6 +46,9 @@ namespace EOM.TSHotelManagement.Domain [SugarColumn(ColumnName = "room_no", ColumnDescription = "房间编号")] public string RoomNumber { get; set; } + [SugarColumn(ColumnName = "room_id", ColumnDescription = "Room ID", IsNullable = true)] + public int? RoomId { get; set; } + /// /// 客户编号 (Customer Number) /// diff --git a/EOM.TSHotelManagement.Domain/Common/AuditEntity.cs b/EOM.TSHotelManagement.Domain/Common/AuditEntity.cs new file mode 100644 index 0000000000000000000000000000000000000000..b2e854eb10cd7184f0fc74d5f1b48dde26b4f2d1 --- /dev/null +++ b/EOM.TSHotelManagement.Domain/Common/AuditEntity.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EOM.TSHotelManagement.Domain; + +public class AuditEntity: BaseEntity +{ + /// + /// 资料创建人 + /// + [SqlSugar.SugarColumn(ColumnName = "datains_usr", Length = 128, IsOnlyIgnoreUpdate = true, IsNullable = true)] + public string? DataInsUsr { get; set; } + /// + /// 资料创建时间 + /// + [SqlSugar.SugarColumn(ColumnName = "datains_date", IsOnlyIgnoreUpdate = true, IsNullable = true)] + public DateTime? DataInsDate { get; set; } + /// + /// 资料更新人 + /// + [SqlSugar.SugarColumn(ColumnName = "datachg_usr", Length = 128, IsOnlyIgnoreInsert = true, IsNullable = true)] + public string? DataChgUsr { get; set; } + /// + /// 资料更新时间 + /// + [SqlSugar.SugarColumn(ColumnName = "datachg_date", IsOnlyIgnoreInsert = true, IsNullable = true)] + public DateTime? DataChgDate { get; set; } +} diff --git a/EOM.TSHotelManagement.Domain/Common/BaseEntity.cs b/EOM.TSHotelManagement.Domain/Common/BaseEntity.cs new file mode 100644 index 0000000000000000000000000000000000000000..7a0b7af276f2ee8cba0ec17fb93104078fdcf85d --- /dev/null +++ b/EOM.TSHotelManagement.Domain/Common/BaseEntity.cs @@ -0,0 +1,18 @@ +using System; + +namespace EOM.TSHotelManagement.Domain +{ + public class BaseEntity + { + /// + /// 行版本(乐观锁) + /// + [SqlSugar.SugarColumn(ColumnName = "row_version", IsNullable = false, DefaultValue = "1")] + public long RowVersion { get; set; } = 1; + /// + /// Token + /// + [SqlSugar.SugarColumn(IsIgnore = true)] + public string? UserToken { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Domain/Common/Personnel.cs b/EOM.TSHotelManagement.Domain/Common/Personnel.cs new file mode 100644 index 0000000000000000000000000000000000000000..f383a2625a9915314bacf61d7a1edf0b6f4f439b --- /dev/null +++ b/EOM.TSHotelManagement.Domain/Common/Personnel.cs @@ -0,0 +1,30 @@ +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Text; + +namespace EOM.TSHotelManagement.Domain; + +public class Personnel : SoftDeleteEntity +{ + [SugarColumn(ColumnName = "name", IsNullable = false, ColumnDescription = "姓名", Length = 250)] + public string Name { get; set; } = string.Empty; + [SugarColumn(ColumnName = "phone_number", IsNullable = false, ColumnDescription = "电话号码", Length = 256)] + public string PhoneNumber { get; set; } = string.Empty; + [SugarColumn(ColumnName = "id_number", IsNullable = false, ColumnDescription = "证件号码", Length = 256)] + public string IdCardNumber { get; set; } = string.Empty; + [SugarColumn(ColumnName = "address", IsNullable = false, ColumnDescription = "联系地址", Length = 500)] + public string Address { get; set; } = string.Empty; + [SugarColumn(ColumnName = "date_of_birth", IsNullable = false, ColumnDescription = "出生日期")] + public DateOnly DateOfBirth { get; set; } = DateOnly.MinValue; + [SugarColumn(ColumnName = "gender", IsNullable = false, ColumnDescription = "性别(0/女,1/男)")] + public int Gender { get; set; } = 0; + [SugarColumn(ColumnName = "id_type", IsNullable = false, ColumnDescription = "证件类型")] + public int IdCardType { get; set; } = 0; + [SugarColumn(ColumnName = "ethnicity", IsNullable = false, ColumnDescription = "民族", Length = 128)] + public string Ethnicity { get; set; } = string.Empty; + [SugarColumn(ColumnName = "education_level", IsNullable = false, ColumnDescription = "教育程度", Length = 128)] + public string EducationLevel { get; set; } = string.Empty; + [SugarColumn(ColumnName = "email_address", IsNullable = false, Length = 256, ColumnDescription = "邮箱地址")] + public string EmailAddress { get; set; } = string.Empty; +} diff --git a/EOM.TSHotelManagement.Domain/Common/SoftDeleteEntity.cs b/EOM.TSHotelManagement.Domain/Common/SoftDeleteEntity.cs new file mode 100644 index 0000000000000000000000000000000000000000..e8a8663b9620c3d3bb0af9a9477cc34fcf5789af --- /dev/null +++ b/EOM.TSHotelManagement.Domain/Common/SoftDeleteEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EOM.TSHotelManagement.Domain; + +public class SoftDeleteEntity : AuditEntity +{ + /// + /// 删除标识 + /// + [SqlSugar.SugarColumn(ColumnName = "delete_mk", Length = 11, IsNullable = false, DefaultValue = "0")] + public int? IsDelete { get; set; } = 0; +} diff --git a/EOM.TSHotelManagement.Domain/EOM.TSHotelManagement.Domain.csproj b/EOM.TSHotelManagement.Domain/EOM.TSHotelManagement.Domain.csproj index befa5d96705b7aae64858c5e4f3df64f211d4c61..9a50f0f8b089cc06273ca89c2dbc00862b243015 100644 --- a/EOM.TSHotelManagement.Domain/EOM.TSHotelManagement.Domain.csproj +++ b/EOM.TSHotelManagement.Domain/EOM.TSHotelManagement.Domain.csproj @@ -12,7 +12,7 @@ - + diff --git a/EOM.TSHotelManagement.Domain/Employee/Employee.cs b/EOM.TSHotelManagement.Domain/Employee/Employee.cs index 77db18547700769da750e5b205b98a70693e54fa..1b167468e8edea7f6acb0e7722663b215dd65a4f 100644 --- a/EOM.TSHotelManagement.Domain/Employee/Employee.cs +++ b/EOM.TSHotelManagement.Domain/Employee/Employee.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 员工信息 (Employee Information) /// [SugarTable("employee")] - public class Employee : BaseEntity + public class Employee : Personnel //SoftDeleteEntity { /// /// 编号 (ID) @@ -45,90 +45,18 @@ namespace EOM.TSHotelManagement.Domain [SugarColumn(ColumnName = "employee_number", IsPrimaryKey = true, IsNullable = false, Length = 128, ColumnDescription = "员工账号/工号 (Employee Account/ID)")] public string EmployeeId { get; set; } - /// - /// 员工姓名 (Employee Name) - /// - [SugarColumn(ColumnName = "employee_name", IsNullable = false, Length = 250, ColumnDescription = "员工姓名 (Employee Name)")] - public string EmployeeName { get; set; } - - /// - /// 出生日期 (Date of Birth) - /// - [SugarColumn(ColumnName = "employee_date_of_birth", IsNullable = false, ColumnDescription = "出生日期 (Date of Birth)")] - public DateOnly DateOfBirth { get; set; } - - /// - /// 员工性别 (Employee Gender) - /// - [SugarColumn(ColumnName = "employee_gender", IsNullable = false, ColumnDescription = "员工性别 (Employee Gender)")] - public int Gender { get; set; } - - /// - /// 员工性别(名称描述) (Employee Gender (Name Description)) - /// - [SugarColumn(IsIgnore = true)] - public string GenderName { get; set; } - - /// - /// 民族类型 (Ethnicity) - /// - [SugarColumn(ColumnName = "employee_nation", IsNullable = false, Length = 128, ColumnDescription = "民族类型 (Ethnicity)")] - public string Ethnicity { get; set; } - - /// - /// 民族名称 (Ethnicity Name) - /// - [SugarColumn(IsIgnore = true)] - public string EthnicityName { get; set; } - - /// - /// 员工电话 (Employee Phone Number) - /// - [SugarColumn(ColumnName = "employee_tel", IsNullable = false, Length = 256, ColumnDescription = "员工电话 (Employee Phone Number)")] - public string PhoneNumber { get; set; } - /// /// 所属部门 (Department) /// [SugarColumn(ColumnName = "employee_department", IsNullable = false, Length = 128, ColumnDescription = "所属部门 (Department)")] public string Department { get; set; } - /// - /// 部门名称 (Department Name) - /// - [SugarColumn(IsIgnore = true)] - public string DepartmentName { get; set; } - - /// - /// 居住地址 (Residential Address) - /// - [SugarColumn(ColumnName = "employee_address", IsNullable = true, Length = 500, ColumnDescription = "居住地址 (Residential Address)")] - public string Address { get; set; } - /// /// 员工职位 (Employee Position) /// [SugarColumn(ColumnName = "employee_postion", IsNullable = false, Length = 128, ColumnDescription = "员工职位 (Employee Position)")] public string Position { get; set; } - /// - /// 职位名称 (Position Name) - /// - [SugarColumn(IsIgnore = true)] - public string PositionName { get; set; } - - /// - /// 证件类型 (ID Card Type) - /// - [SugarColumn(ColumnName = "card_type", IsNullable = false, ColumnDescription = "证件类型 (ID Card Type)")] - public int IdCardType { get; set; } - - /// - /// 证件号码 (ID Card Number) - /// - [SugarColumn(ColumnName = "card_number", IsNullable = false, Length = 256, ColumnDescription = "证件号码 (ID Card Number)")] - public string IdCardNumber { get; set; } - /// /// 员工密码 (Employee Password) /// @@ -147,30 +75,6 @@ namespace EOM.TSHotelManagement.Domain [SugarColumn(ColumnName = "employee_political", IsNullable = false, Length = 128, ColumnDescription = "员工面貌 (Political Affiliation)")] public string PoliticalAffiliation { get; set; } - /// - /// 群众面貌描述 (Political Affiliation Description) - /// - [SugarColumn(IsIgnore = true)] - public string PoliticalAffiliationName { get; set; } - - /// - /// 证件类型 (ID Card Type) - /// - [SugarColumn(IsIgnore = true)] - public string IdCardTypeName { get; set; } - - /// - /// 教育程度 (Education Level) - /// - [SugarColumn(ColumnName = "employee_quality", IsNullable = false, Length = 128, ColumnDescription = "教育程度 (Education Level)")] - public string EducationLevel { get; set; } - - /// - /// 教育程度名称 (Education Level Name) - /// - [SugarColumn(IsIgnore = true)] - public string EducationLevelName { get; set; } - /// /// 禁用标记 /// @@ -182,11 +86,5 @@ namespace EOM.TSHotelManagement.Domain /// [SugarColumn(ColumnName = "initialize_mk", IsNullable = false, ColumnDescription = "初始化标记")] public int IsInitialize { get; set; } - - /// - /// 邮箱地址 - /// - [SugarColumn(ColumnName = "email_address", IsNullable = false, Length = 256, ColumnDescription = "邮箱地址")] - public string EmailAddress { get; set; } } } diff --git a/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs b/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs index 92e40db7c3dbee7254824e7c10cf53c07ce4c57a..dcb79fc40a002070896d275d8fad23ed6c8386a7 100644 --- a/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs +++ b/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 员工打卡考勤 (Employee Check-in/Check-out Record) /// [SugarTable("employee_check", "员工打卡考勤 (Employee Check-in/Check-out Record)")] - public class EmployeeCheck : BaseEntity + public class EmployeeCheck : SoftDeleteEntity { /// /// 编号 (ID) @@ -67,11 +67,5 @@ namespace EOM.TSHotelManagement.Domain /// [SugarColumn(ColumnName = "check_state", ColumnDescription = "打卡状态 (Check-in/Check-out Status)", IsNullable = false)] public int CheckStatus { get; set; } - - /// - /// 打卡状态描述 (Check-in/Check-out Status Description) - /// - [SugarColumn(IsIgnore = true)] - public string CheckStatusDescription { get; set; } } } diff --git a/EOM.TSHotelManagement.Domain/Employee/EmployeeHistory.cs b/EOM.TSHotelManagement.Domain/Employee/EmployeeHistory.cs index b1a93b0562953aedfd4172e1324eda29b099c786..56ccbbc2cd2f08263a776f8f5968da46cc5d374f 100644 --- a/EOM.TSHotelManagement.Domain/Employee/EmployeeHistory.cs +++ b/EOM.TSHotelManagement.Domain/Employee/EmployeeHistory.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 员工履历 (Employee History) /// [SugarTable("employee_history")] - public class EmployeeHistory : BaseEntity + public class EmployeeHistory : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Employee/EmployeePhoto.cs b/EOM.TSHotelManagement.Domain/Employee/EmployeePhoto.cs index 2a86f21bd035cb93d9c9d3d6f62e17ebc96529d8..b216028d28fcfecf4e1d80119d63742f9ae39a74 100644 --- a/EOM.TSHotelManagement.Domain/Employee/EmployeePhoto.cs +++ b/EOM.TSHotelManagement.Domain/Employee/EmployeePhoto.cs @@ -1,4 +1,4 @@ -using SqlSugar; +using SqlSugar; namespace EOM.TSHotelManagement.Domain { @@ -6,7 +6,7 @@ namespace EOM.TSHotelManagement.Domain /// 员工照片 (Employee Photo) /// [SugarTable("employee_pic", "员工照片 (Employee Photo)")] - public class EmployeePhoto : BaseEntity + public class EmployeePhoto : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Employee/EmployeeRewardPunishment.cs b/EOM.TSHotelManagement.Domain/Employee/EmployeeRewardPunishment.cs index e88ee2e0fa2e26e1b43064aede62414e3b0e849f..38d311a32ddac9bb351d07f585bf383b248b0170 100644 --- a/EOM.TSHotelManagement.Domain/Employee/EmployeeRewardPunishment.cs +++ b/EOM.TSHotelManagement.Domain/Employee/EmployeeRewardPunishment.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 员工奖惩 (Employee Rewards/Punishments) /// [SugarTable("reward_punishment", "员工奖惩 (Employee Rewards/Punishments)")] - public class EmployeeRewardPunishment : BaseEntity + public class EmployeeRewardPunishment : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Employee/RewardPunishmentType.cs b/EOM.TSHotelManagement.Domain/Employee/RewardPunishmentType.cs index 028cbaa5a2de3af05bc396a682cc18a4f97f1eab..2e97cd3183f8f1fe769c4056d786286bc61ad0ec 100644 --- a/EOM.TSHotelManagement.Domain/Employee/RewardPunishmentType.cs +++ b/EOM.TSHotelManagement.Domain/Employee/RewardPunishmentType.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 奖惩类型配置表 (Reward/Punishment Type Configuration) /// [SugarTable("reward_punishment_type", "奖惩类型配置表 (Reward/Punishment Type Configuration)")] - public class RewardPunishmentType : BaseEntity + public class RewardPunishmentType : SoftDeleteEntity { /// /// 编号 (ID) @@ -62,4 +62,4 @@ namespace EOM.TSHotelManagement.Domain )] public string RewardPunishmentTypeName { get; set; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Administrator.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Administrator.cs index fb52e734c76c5371719a6b8b5ddae7b63aea5dc5..1b52c3dcb4df9e54392bd850eef1bbd4eb228ff6 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Administrator.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Administrator.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 管理员实体类 (Administrator Entity) /// [SugarTable("administrator", "管理员实体类 (Administrator Entity)", true)] - public class Administrator : BaseEntity + public class Administrator : Personnel //SoftDeleteEntity { /// /// 编号 (ID) @@ -67,35 +67,10 @@ namespace EOM.TSHotelManagement.Domain public string Type { get; set; } - /// - /// 管理员名称 (Administrator Name) - /// - [SugarColumn(ColumnName = "admin_name", IsNullable = false, Length = 200, ColumnDescription = "管理员名称 (Administrator Name)")] - - public string Name { get; set; } - /// /// 是否为超级管理员 (Is Super Administrator) /// [SugarColumn(ColumnName = "is_admin", IsNullable = false, ColumnDescription = "是否为超级管理员 (Is Super Administrator)")] public int IsSuperAdmin { get; set; } - - /// - /// 是否为超级管理员描述 (Is Super Administrator Description) - /// - [SugarColumn(IsIgnore = true)] - public string IsSuperAdminDescription { get; set; } - - /// - /// 管理员类型名称 (Administrator Type Name) - /// - [SugarColumn(IsIgnore = true)] - public string TypeName { get; set; } - - /// - /// 删除标记描述 (Delete Flag Description) - /// - [SugarColumn(IsIgnore = true)] - public string DeleteDescription { get; set; } } } diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorPhoto.cs b/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorPhoto.cs new file mode 100644 index 0000000000000000000000000000000000000000..d0eebf4388abcdd1f08f21f48e2ecec37d4b5fdb --- /dev/null +++ b/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorPhoto.cs @@ -0,0 +1,20 @@ +using SqlSugar; + +namespace EOM.TSHotelManagement.Domain +{ + /// + /// 管理员头像 + /// + [SugarTable("administrator_pic", "管理员头像")] + public class AdministratorPhoto : SoftDeleteEntity + { + [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "编号")] + public int Id { get; set; } + + [SugarColumn(ColumnName = "admin_number", ColumnDescription = "管理员编号", Length = 128, IsNullable = false, IsPrimaryKey = true)] + public string AdminNumber { get; set; } + + [SugarColumn(ColumnName = "pic_url", ColumnDescription = "头像地址", Length = 256, IsNullable = true)] + public string PhotoPath { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorType.cs b/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorType.cs index d8d04c536aa39101a04c61b535c3b7e5d48a475a..b46121660e8a24b136299d2b57b67191b8c322de 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorType.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/AdministratorType.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 管理员类型 (Administrator Type) /// [SugarTable("administrator_type", "管理员类型 (Administrator Type)", true)] - public class AdministratorType : BaseEntity + public class AdministratorType : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNotice.cs b/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNotice.cs index d9acdd4a55485a908d8fdb81ee4c42203cb780c9..f639ed72f6cccbdbe77a99d4217b091df54dc21d 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNotice.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNotice.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 任命公告 (Appointment AppointmentNotice) /// [SugarTable("appointment_notice", "任命公告 (Appointment AppointmentNotice)")] - public class AppointmentNotice : BaseEntity + public class AppointmentNotice : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNoticeType.cs b/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNoticeType.cs index 1afdeec95c233949b65b9df065a3b84d3c1cb569..4bd65db8feddc2ad6a322ed85be25f703cf80fd4 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNoticeType.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/AppointmentNoticeType.cs @@ -1,9 +1,9 @@ -using SqlSugar; +using SqlSugar; namespace EOM.TSHotelManagement.Domain { [SugarTable("appointment_notice_type", "任命公告类型 (Appointment AppointmentNotice Type)")] - public class AppointmentNoticeType : BaseEntity + public class AppointmentNoticeType : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Department.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Department.cs index 00e5a5eac8d690560764de9fd831bcab83a6360c..98721a73b3ec19c54fb8f8fafff1267e1a3dce46 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Department.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Department.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 部门表 (Department Table) /// [SugarTable("department")] - public class Department : BaseEntity + public class Department : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Education.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Education.cs index e96f210145de3099c780750c84847fd1b9727082..6a7d0879b5d7079dcc9d1c91949b5aba5ae93073 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Education.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Education.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 学历 (Education) /// [SugarTable("qualification", "学历 (Education)")] - public class Education : BaseEntity + public class Education : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Menu.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Menu.cs index ad8152ce4021ff3d97fe724f5c1bef7d9069fcf7..c0468ce5e0232ad6704e9f864a3e9a69e0ca978b 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Menu.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Menu.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -29,7 +29,7 @@ namespace EOM.TSHotelManagement.Domain /// 菜单表 (Menu Table) /// [SugarTable("menu", "菜单表 (Menu Table)", true)] - public class Menu : BaseEntity + public class Menu : SoftDeleteEntity { /// /// 编号 (ID) @@ -72,4 +72,4 @@ namespace EOM.TSHotelManagement.Domain public string Icon { get; set; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Nation.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Nation.cs index 3f3ce961a9fc00b0c8143698ab6db45c9751ef77..168efa172b29deff6cddc6d0eacf7eb8299ad9ad 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Nation.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Nation.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 民族 (Nation) /// [SugarTable("nation", "民族信息表 (Nation Information)")] - public class Nation : BaseEntity + public class Nation : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Permission.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Permission.cs index 9f96ad8f67162dc333f2e3eba14a03a111b104cc..8bc0bad79723ed21bb4a5ddfa39d8fce603155d2 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Permission.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Permission.cs @@ -6,7 +6,7 @@ namespace EOM.TSHotelManagement.Domain /// 系统权限定义表 (Permission Definition) /// [SugarTable("permission", "系统权限定义表 (Permission Definition)")] - public class Permission : BaseEntity + public class Permission : AuditEntity { /// /// 编号 (ID) @@ -81,4 +81,4 @@ namespace EOM.TSHotelManagement.Domain )] public string? ParentNumber { get; set; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Position.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Position.cs index de74ca00575b4a0992830a43f78c0a125d74823c..2f1f31076515e5e927dc502984dec7280a9fd3bf 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Position.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Position.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 职位信息表 (Position Information) /// [SugarTable("position", "职位信息表 (Position Information)")] - public class Position : BaseEntity + public class Position : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/Role.cs b/EOM.TSHotelManagement.Domain/SystemManagement/Role.cs index faf24143ed810e3f4820228b67dfca062270241c..5dceac4e75a6f881b04093579e0c50a0001ba9d3 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/Role.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/Role.cs @@ -1,11 +1,11 @@ -using SqlSugar; +using SqlSugar; namespace EOM.TSHotelManagement.Domain { /// /// 系统角色配置表 (System Role Configuration) /// [SugarTable("role", "系统角色配置表 (System Role Configuration)")] - public class Role : BaseEntity + public class Role : SoftDeleteEntity { /// /// 编号 (ID) @@ -48,4 +48,4 @@ namespace EOM.TSHotelManagement.Domain public string? RoleDescription { get; set; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/RolePermission.cs b/EOM.TSHotelManagement.Domain/SystemManagement/RolePermission.cs index b2737df70f9becba27e4814b15c13e6449ec9005..38a73783f8771055643edd9a6ed39896718166d6 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/RolePermission.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/RolePermission.cs @@ -1,11 +1,11 @@ -using SqlSugar; +using SqlSugar; namespace EOM.TSHotelManagement.Domain { /// /// 角色权限关联表 (Role-Permission Mapping) /// [SugarTable("role_permission", "角色权限关联表 (Role-Permission Mapping)")] - public class RolePermission : BaseEntity + public class RolePermission : AuditEntity { /// /// 编号 (ID) @@ -18,7 +18,6 @@ namespace EOM.TSHotelManagement.Domain /// [SugarColumn( ColumnName = "role_number", - IsPrimaryKey = true, ColumnDescription = "关联角色编码 (Linked Role Code)", IsNullable = false, Length = 128, @@ -31,13 +30,23 @@ namespace EOM.TSHotelManagement.Domain /// [SugarColumn( ColumnName = "permission_number", - IsPrimaryKey = true, ColumnDescription = "关联权限编码 (Linked Permission Code)", - IsNullable = false, + IsNullable = true, Length = 128, IndexGroupNameList = new[] { "IX_permission_number" } )] - public string PermissionNumber { get; set; } = null!; + public string? PermissionNumber { get; set; } + + /// + /// 菜单主键(关联菜单表) (Menu Id) + /// + [SugarColumn( + ColumnName = "menu_id", + ColumnDescription = "关联菜单主键 (Linked Menu Id)", + IsNullable = true, + IndexGroupNameList = new[] { "IX_menu_id" } + )] + public int? MenuId { get; set; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/SupervisionStatistics.cs b/EOM.TSHotelManagement.Domain/SystemManagement/SupervisionStatistics.cs index 63594098cd3936f54deafb6d73c67d0274f74312..3bd6c0eca45410e7b4636a62da05486da4cfc7b9 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/SupervisionStatistics.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/SupervisionStatistics.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 监管统计信息表 (Supervision Statistics) /// [SugarTable("supervision_statistics", "监管统计信息表 (Supervision Statistics)")] - public class SupervisionStatistics : BaseEntity + public class SupervisionStatistics : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/SystemInformation.cs b/EOM.TSHotelManagement.Domain/SystemManagement/SystemInformation.cs deleted file mode 100644 index ec4876b71152e375f20e829a0a7c822124fa5527..0000000000000000000000000000000000000000 --- a/EOM.TSHotelManagement.Domain/SystemManagement/SystemInformation.cs +++ /dev/null @@ -1,53 +0,0 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - *模块说明:系统信息静态类 - */ -using SqlSugar; - -namespace EOM.TSHotelManagement.Domain -{ - /// - /// 系统信息 (System Information) - /// - [SugarTable("app_config_base", "系统信息 (System Information)", true)] - public class SystemInformation - { - /// - /// 编号 (ID) - /// - [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "编号 (ID)")] - public int Id { get; set; } - - /// - /// 地址编号 (URL Number) - /// - [SugarColumn(ColumnName = "url_no", IsPrimaryKey = true, ColumnDescription = "地址编号 (URL Number)")] - public int UrlNumber { get; set; } - - /// - /// 地址 (URL Address) - /// - [SugarColumn(ColumnName = "url_addr", Length = 256, ColumnDescription = "地址 (URL Address)")] - public string UrlAddress { get; set; } - } -} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorAuth.cs b/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorAuth.cs index a87512de1263ed621183552e44b024e7fb96bf0a..db9225396e3ba0cb855ce449d1ca89b15d926d54 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorAuth.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorAuth.cs @@ -6,7 +6,7 @@ namespace EOM.TSHotelManagement.Domain [SugarIndex("ux_2fa_employee_pk", nameof(EmployeePk), OrderByType.Asc, true)] [SugarIndex("ux_2fa_administrator_pk", nameof(AdministratorPk), OrderByType.Asc, true)] [SugarIndex("ux_2fa_customer_account_pk", nameof(CustomerAccountPk), OrderByType.Asc, true)] - public class TwoFactorAuth : BaseEntity + public class TwoFactorAuth : SoftDeleteEntity { [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "索引ID")] public int Id { get; set; } diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorRecoveryCode.cs b/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorRecoveryCode.cs index 108125ac2cde198de047cf1728a83f7b53ce9978..5ddc562d883419dfcc2c7a8c88625b47ba723e77 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorRecoveryCode.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/TwoFactorRecoveryCode.cs @@ -5,7 +5,7 @@ namespace EOM.TSHotelManagement.Domain [SugarTable("two_factor_recovery_code", "2FA recovery codes")] [SugarIndex("idx_2fa_recovery_auth", nameof(TwoFactorAuthPk), OrderByType.Asc)] [SugarIndex("idx_2fa_recovery_used", nameof(IsUsed), OrderByType.Asc)] - public class TwoFactorRecoveryCode : BaseEntity + public class TwoFactorRecoveryCode : SoftDeleteEntity { [SugarColumn(ColumnName = "id", IsIdentity = true, IsPrimaryKey = true, IsNullable = false, ColumnDescription = "Primary key")] public int Id { get; set; } diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/UserRole.cs b/EOM.TSHotelManagement.Domain/SystemManagement/UserRole.cs index 11e2f3600041978505959c11bb3c81afe9863209..df93d59b9e438240d32791c761e2770cfbf6eb3b 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/UserRole.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/UserRole.cs @@ -1,11 +1,11 @@ -using SqlSugar; +using SqlSugar; namespace EOM.TSHotelManagement.Domain { // /// 用户角色关联表 (User-Role Mapping) /// [SugarTable("user_role", "用户角色关联表 (User-Role Mapping)")] - public class UserRole : BaseEntity + public class UserRole : AuditEntity { /// /// 编号 (ID) @@ -40,4 +40,4 @@ namespace EOM.TSHotelManagement.Domain public string UserNumber { get; set; } = null!; } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Domain/SystemManagement/VipLevelRule.cs b/EOM.TSHotelManagement.Domain/SystemManagement/VipLevelRule.cs index 31b9546cccf6bea5e5abfb4cf26e9fccee20cf32..87d5c61325c475135c35086cb058418459d19a8c 100644 --- a/EOM.TSHotelManagement.Domain/SystemManagement/VipLevelRule.cs +++ b/EOM.TSHotelManagement.Domain/SystemManagement/VipLevelRule.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Domain /// 会员等级规则表 (VIP Level Rules) /// [SugarTable("vip_rule", "会员等级规则配置表 (VIP Level Rule Configuration)")] - public class VipLevelRule : BaseEntity + public class VipLevelRule : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Util/OperationLog.cs b/EOM.TSHotelManagement.Domain/Util/OperationLog.cs index 995bfbab3821fdf6de4a5a7ab0fc2f70db729ed6..01be2b39718ab0353ff449e3a8e514cd7c73a437 100644 --- a/EOM.TSHotelManagement.Domain/Util/OperationLog.cs +++ b/EOM.TSHotelManagement.Domain/Util/OperationLog.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -31,7 +31,7 @@ namespace EOM.TSHotelManagement.Domain /// 操作日志表 (Operation Log) /// [SugarTable("operation_log", "操作日志表 (Operation Log)")] - public class OperationLog : BaseEntity + public class OperationLog : AuditEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Util/RequestLog.cs b/EOM.TSHotelManagement.Domain/Util/RequestLog.cs index c2531657972fdf2fcd5f1806348b8e0c4cf5e9ee..03c2ad8bc802cf996cec222e1457d8a080ecc160 100644 --- a/EOM.TSHotelManagement.Domain/Util/RequestLog.cs +++ b/EOM.TSHotelManagement.Domain/Util/RequestLog.cs @@ -1,10 +1,10 @@ -using SqlSugar; +using SqlSugar; using System; namespace EOM.TSHotelManagement.Domain { [SugarTable("request_log", "请求日志表 (Request Log)")] - public class RequestLog : BaseEntity + public class RequestLog : AuditEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Infrastructure/Config/JwtConfig.cs b/EOM.TSHotelManagement.Infrastructure/Config/JwtConfig.cs index 9f402a825f98b9e1446d78ec9851fc0447e1898d..7a96adbc44619cfbad4c89d42d5668483bf51a23 100644 --- a/EOM.TSHotelManagement.Infrastructure/Config/JwtConfig.cs +++ b/EOM.TSHotelManagement.Infrastructure/Config/JwtConfig.cs @@ -4,5 +4,6 @@ { public string Key { get; set; } public int ExpiryMinutes { get; set; } + public int RefreshTokenExpiryDays { get; set; } } } diff --git a/EOM.TSHotelManagement.Infrastructure/Config/MailConfig.cs b/EOM.TSHotelManagement.Infrastructure/Config/MailConfig.cs index 078f1c04d7452cb87f8ac4cee3fb003e87f00774..ace9a51bcfa3e821570dc07fece152f9dfb8eb91 100644 --- a/EOM.TSHotelManagement.Infrastructure/Config/MailConfig.cs +++ b/EOM.TSHotelManagement.Infrastructure/Config/MailConfig.cs @@ -1,4 +1,4 @@ -namespace EOM.TSHotelManagement.Infrastructure +namespace EOM.TSHotelManagement.Infrastructure { public class MailConfig { @@ -6,6 +6,7 @@ /// 是否启用邮件服务 /// public bool Enabled { get; set; } = false; + /// /// SMTP服务器地址 /// diff --git a/EOM.TSHotelManagement.Infrastructure/Config/RedisConfig.cs b/EOM.TSHotelManagement.Infrastructure/Config/RedisConfig.cs index 967ad81bb3c4c85e77aa4eca7dfa7ef5ab8fe41f..c50896be0b907bef17a10c4508bfcae663405ef3 100644 --- a/EOM.TSHotelManagement.Infrastructure/Config/RedisConfig.cs +++ b/EOM.TSHotelManagement.Infrastructure/Config/RedisConfig.cs @@ -5,5 +5,13 @@ namespace EOM.TSHotelManagement.Infrastructure public string ConnectionString { get; set; } public bool Enable { get; set; } public int? DefaultDatabase { get; set; } + public int? ConnectTimeoutMs { get; set; } + public int? AsyncTimeoutMs { get; set; } + public int? SyncTimeoutMs { get; set; } + public int? KeepAliveSeconds { get; set; } + public int? ConnectRetry { get; set; } + public int? ReconnectRetryBaseDelayMs { get; set; } + public int? OperationTimeoutMs { get; set; } + public int? FailureCooldownSeconds { get; set; } } } diff --git a/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs b/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs index 9da800693db7de1afb4d654449ae384c2a40442f..f9e2bd6ddbf0cc08d5f1d2a55e26b054aa77ee16 100644 --- a/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs +++ b/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs @@ -5,7 +5,7 @@ public string Code { get; } public string Description { get; } - private static List _constants = new List(); + private static readonly List _constants = new List(); protected CodeConstantBase(string code, string description) { @@ -16,25 +16,64 @@ public static IEnumerable GetAll() { + EnsureInitialized(); return _constants; } public static string GetDescriptionByCode(string code) { - var constant = _constants.SingleOrDefault(c => c.Code == code); - return constant?.Description ?? string.Empty; + return GetConstantByCode(code)?.Description ?? string.Empty; } public static string GetCodeByDescription(string description) { - var constant = _constants.SingleOrDefault(c => c.Description == description); - return constant?.Code ?? string.Empty; + return GetConstantByDescription(description)?.Code ?? string.Empty; } public static T? GetConstantByCode(string code) { - var constant = _constants.FirstOrDefault(c => c.Code == code); - return constant ?? null; + EnsureInitialized(); + var normalizedCode = NormalizeValue(code); + if (string.IsNullOrWhiteSpace(normalizedCode)) + { + return null; + } + + return _constants.FirstOrDefault(c => string.Equals(c.Code, normalizedCode, StringComparison.OrdinalIgnoreCase)); + } + + public static T? GetConstantByDescription(string description) + { + EnsureInitialized(); + var normalizedDescription = NormalizeValue(description); + if (string.IsNullOrWhiteSpace(normalizedDescription)) + { + return null; + } + + return _constants.FirstOrDefault(c => string.Equals(c.Description, normalizedDescription, StringComparison.OrdinalIgnoreCase)); + } + + public static bool TryGetDescriptionByCode(string code, out string description) + { + description = GetDescriptionByCode(code); + return !string.IsNullOrWhiteSpace(description); + } + + public static bool TryGetCodeByDescription(string description, out string code) + { + code = GetCodeByDescription(description); + return !string.IsNullOrWhiteSpace(code); + } + + private static string NormalizeValue(string value) + { + return value?.Trim() ?? string.Empty; + } + + private static void EnsureInitialized() + { + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); } } } diff --git a/EOM.TSHotelManagement.Infrastructure/EOM.TSHotelManagement.Infrastructure.csproj b/EOM.TSHotelManagement.Infrastructure/EOM.TSHotelManagement.Infrastructure.csproj index d6e07bfdc3270ae58329f8dd6cd3d85ee351a183..a60e237a19e7e6ee2cdf43931b131686f3d7538a 100644 --- a/EOM.TSHotelManagement.Infrastructure/EOM.TSHotelManagement.Infrastructure.csproj +++ b/EOM.TSHotelManagement.Infrastructure/EOM.TSHotelManagement.Infrastructure.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/EOM.TSHotelManagement.Infrastructure/Factory/JwtConfigFactory.cs b/EOM.TSHotelManagement.Infrastructure/Factory/JwtConfigFactory.cs index 216268add7e014947d21afa451e5087f1c423295..b70d2c1acac977abb195df8d00931096213c626b 100644 --- a/EOM.TSHotelManagement.Infrastructure/Factory/JwtConfigFactory.cs +++ b/EOM.TSHotelManagement.Infrastructure/Factory/JwtConfigFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; namespace EOM.TSHotelManagement.Infrastructure { @@ -13,10 +13,16 @@ namespace EOM.TSHotelManagement.Infrastructure public JwtConfig GetJwtConfig() { + var key = _configuration["Jwt:Key"]; + if (string.IsNullOrWhiteSpace(key) || key.Length < 32) + { + throw new InvalidOperationException("JWT密钥长度不足,必须至少32个字符"); + } var jwtConfig = new JwtConfig { - Key = _configuration["Jwt:Key"], - ExpiryMinutes = int.Parse(_configuration["Jwt:ExpiryMinutes"]) + Key = key, + ExpiryMinutes = int.Parse(_configuration["Jwt:ExpiryMinutes"]), + RefreshTokenExpiryDays = int.Parse(_configuration["Jwt:RefreshTokenExpiryDays"] ?? "7") }; return jwtConfig; } diff --git a/EOM.TSHotelManagement.Infrastructure/Factory/LskyConfigFactory.cs b/EOM.TSHotelManagement.Infrastructure/Factory/LskyConfigFactory.cs index f36a3d5d97b2eab4084736eca908b7bebe70cd1d..729e2e88d5da6758f1b0b280e0065fedd93268e3 100644 --- a/EOM.TSHotelManagement.Infrastructure/Factory/LskyConfigFactory.cs +++ b/EOM.TSHotelManagement.Infrastructure/Factory/LskyConfigFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; namespace EOM.TSHotelManagement.Infrastructure { @@ -16,6 +16,7 @@ namespace EOM.TSHotelManagement.Infrastructure var lskySection = _configuration.GetSection("Lsky"); var lskyConfig = new LskyConfig { + Enabled = lskySection.GetValue("Enabled") ?? false, BaseAddress = lskySection.GetValue("BaseAddress") ?? string.Empty, Email = lskySection.GetValue("Email") ?? string.Empty, Password = lskySection.GetValue("Password") ?? string.Empty, @@ -25,4 +26,4 @@ namespace EOM.TSHotelManagement.Infrastructure return lskyConfig; } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Infrastructure/Factory/MailConfigFactory.cs b/EOM.TSHotelManagement.Infrastructure/Factory/MailConfigFactory.cs index 63a8bef3abff09d8e55e0e66765397c6bfd96597..b2939eb37be0cee9d0203c0ed57af18acc480ff3 100644 --- a/EOM.TSHotelManagement.Infrastructure/Factory/MailConfigFactory.cs +++ b/EOM.TSHotelManagement.Infrastructure/Factory/MailConfigFactory.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; namespace EOM.TSHotelManagement.Infrastructure { @@ -20,7 +20,8 @@ namespace EOM.TSHotelManagement.Infrastructure UserName = _configuration.GetSection("Mail").GetValue("UserName") ?? string.Empty, Password = _configuration.GetSection("Mail").GetValue("Password") ?? string.Empty, EnableSsl = _configuration.GetSection("Mail").GetValue("EnableSsl") ?? false, - DisplayName = _configuration.GetSection("Mail").GetValue("DisplayName") ?? string.Empty + DisplayName = _configuration.GetSection("Mail").GetValue("DisplayName") ?? string.Empty, + Enabled = _configuration.GetSection("Mail").GetValue("Enabled") ?? false, }; return mailConfig; } diff --git a/EOM.TSHotelManagement.Infrastructure/Factory/RedisConfigFactory.cs b/EOM.TSHotelManagement.Infrastructure/Factory/RedisConfigFactory.cs index 981eeafaadc5f92b953104d26cce9ed66041c6e7..056ae5bfa52d175b37193be75039d81c6ece5752 100644 --- a/EOM.TSHotelManagement.Infrastructure/Factory/RedisConfigFactory.cs +++ b/EOM.TSHotelManagement.Infrastructure/Factory/RedisConfigFactory.cs @@ -24,7 +24,16 @@ namespace EOM.TSHotelManagement.Infrastructure var redisConfig = new RedisConfig { ConnectionString = redisSection.GetValue("ConnectionString"), - Enable = enable + Enable = enable, + DefaultDatabase = redisSection.GetValue("DefaultDatabase"), + ConnectTimeoutMs = redisSection.GetValue("ConnectTimeoutMs"), + AsyncTimeoutMs = redisSection.GetValue("AsyncTimeoutMs"), + SyncTimeoutMs = redisSection.GetValue("SyncTimeoutMs"), + KeepAliveSeconds = redisSection.GetValue("KeepAliveSeconds"), + ConnectRetry = redisSection.GetValue("ConnectRetry"), + ReconnectRetryBaseDelayMs = redisSection.GetValue("ReconnectRetryBaseDelayMs"), + OperationTimeoutMs = redisSection.GetValue("OperationTimeoutMs"), + FailureCooldownSeconds = redisSection.GetValue("FailureCooldownSeconds") }; return redisConfig; } diff --git a/EOM.TSHotelManagement.Migration/EntityBuilder.cs b/EOM.TSHotelManagement.Migration/EntityBuilder.cs index 97c460d66e9be526707281945cdb484d39cff22b..01aa42a37649c36baab01464ba31d1677b828c79 100644 --- a/EOM.TSHotelManagement.Migration/EntityBuilder.cs +++ b/EOM.TSHotelManagement.Migration/EntityBuilder.cs @@ -34,6 +34,7 @@ namespace EOM.TSHotelManagement.Migration { typeof(Administrator), typeof(AdministratorType), + typeof(AdministratorPhoto), typeof(AppointmentNotice), typeof(AppointmentNoticeType), typeof(Asset), @@ -52,6 +53,7 @@ namespace EOM.TSHotelManagement.Migration typeof(Menu), typeof(Nation), typeof(NavBar), + typeof(UserFavoriteCollection), typeof(OperationLog), typeof(Position), typeof(PromotionContent), @@ -65,7 +67,6 @@ namespace EOM.TSHotelManagement.Migration typeof(SellThing), typeof(Spend), typeof(SupervisionStatistics), - typeof(SystemInformation), typeof(UserRole), typeof(VipLevelRule), typeof(RequestLog), @@ -87,11 +88,20 @@ namespace EOM.TSHotelManagement.Migration }, new Administrator { - Number = "1263785187301658678", + Number = "AD-202005060001", Account = "admin", Password = string.Empty, - Name = "Administrator", + Name = "超级管理员", Type = "Admin", + Address = "广东珠海", + DateOfBirth = DateOnly.FromDateTime(new DateTime(1990,1,1,0,0,0)), + EducationLevel = "E-000001", + EmailAddress = string.Empty, + Ethnicity = "N-000001", + Gender = 1, + IdCardNumber = "666", + IdCardType = 0, + PhoneNumber = "666", IsSuperAdmin = 1, IsDelete = 0, DataInsUsr = "System", @@ -483,6 +493,17 @@ namespace EOM.TSHotelManagement.Migration DataInsDate = DateTime.Now, }, new Menu // 36 + { + Key = "quartzjoblist", + Title = "Quartz任务列表", + Path = "/quartzjoblist", + Parent = 31, + Icon = "OrderedListOutlined", + IsDelete = 0, + DataInsUsr = "System", + DataInsDate = DateTime.Now, + }, + new Menu // 37 { Key = "my", Title = "我的", @@ -493,7 +514,7 @@ namespace EOM.TSHotelManagement.Migration DataInsUsr = "System", DataInsDate = DateTime.Now, }, - new Menu // 37 + new Menu // 38 { Key = "dashboard", Title = "仪表盘", @@ -504,7 +525,7 @@ namespace EOM.TSHotelManagement.Migration DataInsUsr = "System", DataInsDate = DateTime.Now, }, - new Menu // 38 + new Menu // 39 { Key = "promotioncontent", Title = "宣传联动内容", @@ -515,7 +536,7 @@ namespace EOM.TSHotelManagement.Migration DataInsUsr = "System", DataInsDate = DateTime.Now, }, - new Menu // 39 + new Menu // 40 { Key = "requestlog", Title = "请求日志", @@ -530,7 +551,7 @@ namespace EOM.TSHotelManagement.Migration { NavigationBarName = "客房管理", NavigationBarOrder = 1, - NavigationBarImage = null, + NavigationBarImage = string.Empty, NavigationBarEvent = "RoomManager_Event", IsDelete = 0, MarginLeft = 0, @@ -541,7 +562,7 @@ namespace EOM.TSHotelManagement.Migration { NavigationBarName = "客户管理", NavigationBarOrder = 2, - NavigationBarImage = null, + NavigationBarImage = string.Empty, NavigationBarEvent = "CustomerManager_Event", IsDelete = 0, MarginLeft = 120, @@ -552,7 +573,7 @@ namespace EOM.TSHotelManagement.Migration { NavigationBarName = "商品消费", NavigationBarOrder = 3, - NavigationBarImage = null, + NavigationBarImage = string.Empty, NavigationBarEvent = "SellManager_Event", IsDelete = 0, MarginLeft = 120, @@ -605,7 +626,7 @@ namespace EOM.TSHotelManagement.Migration new Employee { EmployeeId = "WK010", - EmployeeName = "阿杰", + Name = "阿杰", DateOfBirth = DateOnly.FromDateTime(new DateTime(1999,7,20,0,0,0)), Password = string.Empty, Department = "D-000001", @@ -625,10 +646,17 @@ namespace EOM.TSHotelManagement.Migration DataInsUsr = "System", DataInsDate = DateTime.Now }, - new SystemInformation + new UserFavoriteCollection { - UrlNumber = 1, - UrlAddress = "https://gitee.com/java-and-net/TopskyHotelManagerSystem/releases", + UserNumber = "WK010", + LoginType = "employee", + Account = "WK010", + FavoriteRoutesJson = "[\"/roommap\"]", + RouteCount = 1, + UpdatedAt = DateTime.UtcNow, + TriggeredBy = "seed", + DataInsUsr = "System", + DataInsDate = DateTime.Now }, new PromotionContent { @@ -666,251 +694,289 @@ namespace EOM.TSHotelManagement.Migration , // ===== Permission seeds synced from controller [RequirePermission] ===== - // 客户信息 - new Permission { PermissionNumber = "customer.dci", PermissionName = "删除客户信息", Module = "customer", Description = "删除客户信息", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customer.ici", PermissionName = "添加客户信息", Module = "customer", Description = "添加客户信息", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customer.scbi", PermissionName = "查询指定客户信息", Module = "customer", Description = "查询指定客户信息", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customer.scs", PermissionName = "查询所有客户信息", Module = "customer", Description = "查询所有客户信息", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customer.uci", PermissionName = "更新客户信息", Module = "customer", Description = "更新客户信息", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customer.uctbcn", PermissionName = "更新会员等级", Module = "customer", Description = "更新会员等级", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - - // 客户消费信息 - new Permission { PermissionNumber = "customerspend.acs", PermissionName = "添加客户消费信息", Module = "customerspend", Description = "添加客户消费信息", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customerspend.ssbrn", PermissionName = "查询房间消费信息", Module = "customerspend", Description = "查询房间消费信息", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customerspend.ssia", PermissionName = "查询所有消费信息", Module = "customerspend", Description = "查询所有消费信息", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customerspend.shsia", PermissionName = "查询客户历史消费信息", Module = "customerspend", Description = "查询客户历史消费信息", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customerspend.sca", PermissionName = "查询消费总金额", Module = "customerspend", Description = "查询消费总金额", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customerspend.ucs", PermissionName = "撤回客户消费信息", Module = "customerspend", Description = "撤回客户消费信息", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customerspend.usi", PermissionName = "更新消费信息", Module = "customerspend", Description = "更新消费信息", MenuKey = "customerspend", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - - // 客户类型 - new Permission { PermissionNumber = "customertype.create", PermissionName = "新增客户类型", Module = "customertype", Description = "基础信息-客户类型-新增", MenuKey = "customertype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customertype.delete", PermissionName = "删除客户类型", Module = "customertype", Description = "基础信息-客户类型-删除", MenuKey = "customertype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customertype.update", PermissionName = "更新客户类型", Module = "customertype", Description = "基础信息-客户类型-更新", MenuKey = "customertype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "customertype.view", PermissionName = "查询客户类型列表", Module = "customertype", Description = "基础信息-客户类型-查询列表", MenuKey = "customertype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + // Basic (基础信息管理) // 部门 - new Permission { PermissionNumber = "department.create", PermissionName = "新增部门", Module = "department", Description = "基础信息-部门-新增", MenuKey = "department", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "department.delete", PermissionName = "删除部门", Module = "department", Description = "基础信息-部门-删除", MenuKey = "department", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "department.update", PermissionName = "更新部门", Module = "department", Description = "基础信息-部门-更新", MenuKey = "department", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "department.view", PermissionName = "查询部门列表", Module = "department", Description = "基础信息-部门-查询列表", MenuKey = "department", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "department.create", PermissionName = "新增部门", Module = "basic", Description = "基础信息-部门-新增", MenuKey = "department", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "department.delete", PermissionName = "删除部门", Module = "basic", Description = "基础信息-部门-删除", MenuKey = "department", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "department.export", PermissionName = "导出部门", Module = "basic", Description = "基础信息-部门-导出列表", MenuKey = "department", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "department.update", PermissionName = "更新部门", Module = "basic", Description = "基础信息-部门-更新", MenuKey = "department", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "department.view", PermissionName = "查询部门列表", Module = "basic", Description = "基础信息-部门-查询列表", MenuKey = "department", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 民族 - new Permission { PermissionNumber = "nation.create", PermissionName = "新增民族", Module = "nation", Description = "基础信息-民族-新增", MenuKey = "nation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "nation.delete", PermissionName = "删除民族", Module = "nation", Description = "基础信息-民族-删除", MenuKey = "nation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "nation.update", PermissionName = "更新民族", Module = "nation", Description = "基础信息-民族-更新", MenuKey = "nation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "nation.view", PermissionName = "查询民族列表", Module = "nation", Description = "基础信息-民族-查询列表", MenuKey = "nation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "nation.create", PermissionName = "新增民族", Module = "basic", Description = "基础信息-民族-新增", MenuKey = "nation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "nation.delete", PermissionName = "删除民族", Module = "basic", Description = "基础信息-民族-删除", MenuKey = "nation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "nation.export", PermissionName = "导出民族", Module = "basic", Description = "基础信息-民族-导出列表", MenuKey = "nation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "nation.update", PermissionName = "更新民族", Module = "basic", Description = "基础信息-民族-更新", MenuKey = "nation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "nation.view", PermissionName = "查询民族列表", Module = "basic", Description = "基础信息-民族-查询列表", MenuKey = "nation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 证件类型 - new Permission { PermissionNumber = "passport.create", PermissionName = "新增证件类型", Module = "passport", Description = "基础信息-证件类型-新增", MenuKey = "passport", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "passport.delete", PermissionName = "删除证件类型", Module = "passport", Description = "基础信息-证件类型-删除", MenuKey = "passport", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "passport.update", PermissionName = "更新证件类型", Module = "passport", Description = "基础信息-证件类型-更新", MenuKey = "passport", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "passport.view", PermissionName = "查询证件类型列表", Module = "passport", Description = "基础信息-证件类型-查询列表", MenuKey = "passport", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "passport.create", PermissionName = "新增证件类型", Module = "basic", Description = "基础信息-证件类型-新增", MenuKey = "passport", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "passport.delete", PermissionName = "删除证件类型", Module = "basic", Description = "基础信息-证件类型-删除", MenuKey = "passport", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "passport.export", PermissionName = "导出证件类型", Module = "basic", Description = "基础信息-证件类型-导出列表", MenuKey = "passport", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "passport.update", PermissionName = "更新证件类型", Module = "basic", Description = "基础信息-证件类型-更新", MenuKey = "passport", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "passport.view", PermissionName = "查询证件类型列表", Module = "basic", Description = "基础信息-证件类型-查询列表", MenuKey = "passport", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 职位 - new Permission { PermissionNumber = "position.create", PermissionName = "新增职位", Module = "position", Description = "基础信息-职位-新增", MenuKey = "position", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "position.delete", PermissionName = "删除职位", Module = "position", Description = "基础信息-职位-删除", MenuKey = "position", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "position.update", PermissionName = "更新职位", Module = "position", Description = "基础信息-职位-更新", MenuKey = "position", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "position.view", PermissionName = "查询职位列表", Module = "position", Description = "基础信息-职位-查询列表", MenuKey = "position", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "position.create", PermissionName = "新增职位", Module = "basic", Description = "基础信息-职位-新增", MenuKey = "position", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "position.delete", PermissionName = "删除职位", Module = "basic", Description = "基础信息-职位-删除", MenuKey = "position", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "position.export", PermissionName = "导出职位", Module = "basic", Description = "基础信息-职位-导出列表", MenuKey = "position", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "position.update", PermissionName = "更新职位", Module = "basic", Description = "基础信息-职位-更新", MenuKey = "position", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "position.view", PermissionName = "查询职位列表", Module = "basic", Description = "基础信息-职位-查询列表", MenuKey = "position", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 学历 - new Permission { PermissionNumber = "qualification.create", PermissionName = "新增学历", Module = "qualification", Description = "基础信息-学历-新增", MenuKey = "qualification", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "qualification.delete", PermissionName = "删除学历", Module = "qualification", Description = "基础信息-学历-删除", MenuKey = "qualification", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "qualification.update", PermissionName = "更新学历", Module = "qualification", Description = "基础信息-学历-更新", MenuKey = "qualification", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "qualification.view", PermissionName = "查询学历列表", Module = "qualification", Description = "基础信息-学历-查询列表", MenuKey = "qualification", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "qualification.create", PermissionName = "新增学历", Module = "basic", Description = "基础信息-学历-新增", MenuKey = "qualification", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "qualification.delete", PermissionName = "删除学历", Module = "basic", Description = "基础信息-学历-删除", MenuKey = "qualification", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "qualification.export", PermissionName = "导出学历", Module = "basic", Description = "基础信息-学历-导出列表", MenuKey = "qualification", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "qualification.update", PermissionName = "更新学历", Module = "basic", Description = "基础信息-学历-更新", MenuKey = "qualification", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "qualification.view", PermissionName = "查询学历列表", Module = "basic", Description = "基础信息-学历-查询列表", MenuKey = "qualification", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 会员等级规则管理 - new Permission { PermissionNumber = "viplevel.addviprule", PermissionName = "添加会员等级规则", Module = "viplevel", Description = "添加会员等级规则", MenuKey = "viplevel", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "viplevel.delviprule", PermissionName = "删除会员等级规则", Module = "viplevel", Description = "删除会员等级规则", MenuKey = "viplevel", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "viplevel.svr", PermissionName = "查询会员等级规则", Module = "viplevel", Description = "查询会员等级规则", MenuKey = "viplevel", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "viplevel.svrlist", PermissionName = "查询会员等级规则列表", Module = "viplevel", Description = "查询会员等级规则列表", MenuKey = "viplevel", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "viplevel.updviprule", PermissionName = "更新会员等级规则", Module = "viplevel", Description = "更新会员等级规则", MenuKey = "viplevel", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - - // 仪表盘 - new Permission { PermissionNumber = "dashboard.bs", PermissionName = "获取业务统计信息", Module = "dashboard", Description = "获取业务统计信息", MenuKey = "dashboard", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "dashboard.hrs", PermissionName = "获取人事统计信息", Module = "dashboard", Description = "获取人事统计信息", MenuKey = "dashboard", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "dashboard.ls", PermissionName = "获取后勤统计信息", Module = "dashboard", Description = "获取后勤统计信息", MenuKey = "dashboard", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "dashboard.rs", PermissionName = "获取房间统计信息", Module = "dashboard", Description = "获取房间统计信息", MenuKey = "dashboard", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - - // 商品管理 - new Permission { PermissionNumber = "goodsmanagement.dst", PermissionName = "删除商品信息", Module = "goodsmanagement", Description = "删除商品信息", MenuKey = "goodsmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "goodsmanagement.ist", PermissionName = "添加商品", Module = "goodsmanagement", Description = "添加商品", MenuKey = "goodsmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "goodsmanagement.ssta", PermissionName = "查询所有商品", Module = "goodsmanagement", Description = "查询所有商品", MenuKey = "goodsmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "goodsmanagement.sstbnap", PermissionName = "根据商品名称和价格查询商品编号", Module = "goodsmanagement", Description = "根据商品名称和价格查询商品编号", MenuKey = "goodsmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "goodsmanagement.ust", PermissionName = "修改商品", Module = "goodsmanagement", Description = "修改商品", MenuKey = "goodsmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - - // 水电费信息管理 - new Permission { PermissionNumber = "hydroelectricinformation.demi", PermissionName = "删除水电费信息", Module = "hydroelectricinformation", Description = "删除水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "hydroelectricinformation.iemi", PermissionName = "添加水电费信息", Module = "hydroelectricinformation", Description = "添加水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "hydroelectricinformation.semi", PermissionName = "查询水电费信息", Module = "hydroelectricinformation", Description = "查询水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "hydroelectricinformation.uemi", PermissionName = "修改水电费信息", Module = "hydroelectricinformation", Description = "修改水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + // 公告类型 + new Permission { PermissionNumber = "noticetype.create", PermissionName = "添加公告类型", Module = "basic", Description = "添加公告类型", MenuKey = "noticetype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "noticetype.delete", PermissionName = "删除公告类型", Module = "basic", Description = "删除公告类型", MenuKey = "noticetype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "noticetype.export", PermissionName = "导出公告类型", Module = "basic", Description = "导出公告类型列表", MenuKey = "noticetype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "noticetype.update", PermissionName = "更新公告类型", Module = "basic", Description = "更新公告类型", MenuKey = "noticetype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "noticetype.view", PermissionName = "查询所有公告类型", Module = "basic", Description = "查询所有公告类型", MenuKey = "noticetype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // 宣传联动内容 + new Permission { PermissionNumber = "promotioncontent.apc", PermissionName = "添加宣传联动内容", Module = "basic", Description = "添加宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "promotioncontent.dpc", PermissionName = "删除宣传联动内容", Module = "basic", Description = "删除宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "promotioncontent.export", PermissionName = "导出宣传联动内容", Module = "basic", Description = "导出宣传联动内容列表", MenuKey = "promotioncontent", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "promotioncontent.spca", PermissionName = "查询所有宣传联动内容", Module = "basic", Description = "查询所有宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "promotioncontent.spcs", PermissionName = "查询所有宣传联动内容(跑马灯)", Module = "basic", Description = "查询所有宣传联动内容(跑马灯)", MenuKey = "promotioncontent", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "promotioncontent.upc", PermissionName = "更新宣传联动内容", Module = "basic", Description = "更新宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // Finance (财务信息管理) // 资产信息管理 - new Permission { PermissionNumber = "internalfinance.aai", PermissionName = "添加资产信息", Module = "internalfinance", Description = "添加资产信息", MenuKey = "internalfinance", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "internalfinance.dai", PermissionName = "删除资产信息", Module = "internalfinance", Description = "删除资产信息", MenuKey = "internalfinance", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "internalfinance.saia", PermissionName = "查询资产信息", Module = "internalfinance", Description = "查询资产信息", MenuKey = "internalfinance", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "internalfinance.uai", PermissionName = "更新资产信息", Module = "internalfinance", Description = "更新资产信息", MenuKey = "internalfinance", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - - // 菜单管理 - new Permission { PermissionNumber = "menumanagement.bma", PermissionName = "构建菜单树", Module = "menumanagement", Description = "构建菜单树", MenuKey = "menumanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "menumanagement.deletemenu", PermissionName = "删除菜单", Module = "menumanagement", Description = "删除菜单", MenuKey = "menumanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "menumanagement.insertmenu", PermissionName = "插入菜单", Module = "menumanagement", Description = "插入菜单", MenuKey = "menumanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "menumanagement.sma", PermissionName = "查询所有菜单信息", Module = "menumanagement", Description = "查询所有菜单信息", MenuKey = "menumanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "menumanagement.updatemenu", PermissionName = "更新菜单", Module = "menumanagement", Description = "更新菜单", MenuKey = "menumanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "internalfinance.aai", PermissionName = "添加资产信息", Module = "internalfinance", Description = "添加资产信息", MenuKey = "internalfinance", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "internalfinance.dai", PermissionName = "删除资产信息", Module = "internalfinance", Description = "删除资产信息", MenuKey = "internalfinance", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "internalfinance.export", PermissionName = "导出资产信息", Module = "internalfinance", Description = "导出资产信息列表", MenuKey = "internalfinance", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "internalfinance.saia", PermissionName = "查询资产信息", Module = "internalfinance", Description = "查询资产信息", MenuKey = "internalfinance", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "internalfinance.uai", PermissionName = "更新资产信息", Module = "internalfinance", Description = "更新资产信息", MenuKey = "internalfinance", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // Nav Bar (导航栏管理) // 导航控件管理 - new Permission { PermissionNumber = "navbar.addnavbar", PermissionName = "添加导航控件", Module = "navbar", Description = "添加导航控件", MenuKey = "navbar", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "navbar.dn", PermissionName = "删除导航控件", Module = "navbar", Description = "删除导航控件", MenuKey = "navbar", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "navbar.navbarlist", PermissionName = "导航控件列表", Module = "navbar", Description = "导航控件列表", MenuKey = "navbar", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "navbar.un", PermissionName = "更新导航控件", Module = "navbar", Description = "更新导航控件", MenuKey = "navbar", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "navbar.addnavbar", PermissionName = "添加导航控件", Module = "client", Description = "添加导航控件", MenuKey = "navbar", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "navbar.dn", PermissionName = "删除导航控件", Module = "client", Description = "删除导航控件", MenuKey = "navbar", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "navbar.navbarlist", PermissionName = "导航控件列表", Module = "client", Description = "导航控件列表", MenuKey = "navbar", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "navbar.un", PermissionName = "更新导航控件", Module = "client", Description = "更新导航控件", MenuKey = "navbar", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 公告类型 - new Permission { PermissionNumber = "noticetype.create", PermissionName = "添加公告类型", Module = "noticetype", Description = "添加公告类型", MenuKey = "noticetype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "noticetype.delete", PermissionName = "删除公告类型", Module = "noticetype", Description = "删除公告类型", MenuKey = "noticetype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "noticetype.update", PermissionName = "更新公告类型", Module = "noticetype", Description = "更新公告类型", MenuKey = "noticetype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "noticetype.view", PermissionName = "查询所有公告类型", Module = "noticetype", Description = "查询所有公告类型", MenuKey = "noticetype", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 操作日志 - new Permission { PermissionNumber = "operationlog.delete", PermissionName = "删除时间范围的操作日志", Module = "operationlog", Description = "删除时间范围的操作日志", MenuKey = "operationlog", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "operationlog.view", PermissionName = "查询所有操作日志", Module = "operationlog", Description = "查询所有操作日志", MenuKey = "operationlog", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // Hydroelectricity (水电信息管理) + // 水电费信息管理 + new Permission { PermissionNumber = "hydroelectricinformation.demi", PermissionName = "删除水电费信息", Module = "hydroelectricity", Description = "删除水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "hydroelectricinformation.export", PermissionName = "导出水电费信息", Module = "hydroelectricity", Description = "导出水电费信息列表", MenuKey = "hydroelectricinformation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "hydroelectricinformation.iemi", PermissionName = "添加水电费信息", Module = "hydroelectricity", Description = "添加水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "hydroelectricinformation.semi", PermissionName = "查询水电费信息", Module = "hydroelectricity", Description = "查询水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "hydroelectricinformation.uemi", PermissionName = "修改水电费信息", Module = "hydroelectricity", Description = "修改水电费信息", MenuKey = "hydroelectricinformation", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 宣传联动内容 - new Permission { PermissionNumber = "promotioncontent.apc", PermissionName = "添加宣传联动内容", Module = "promotioncontent", Description = "添加宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "promotioncontent.dpc", PermissionName = "删除宣传联动内容", Module = "promotioncontent", Description = "删除宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "promotioncontent.spca", PermissionName = "查询所有宣传联动内容", Module = "promotioncontent", Description = "查询所有宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "promotioncontent.spcs", PermissionName = "查询所有宣传联动内容(跑马灯)", Module = "promotioncontent", Description = "查询所有宣传联动内容(跑马灯)", MenuKey = "promotioncontent", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "promotioncontent.upc", PermissionName = "更新宣传联动内容", Module = "promotioncontent", Description = "更新宣传联动内容", MenuKey = "promotioncontent", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 请求日志 - new Permission { PermissionNumber = "requestlog.delete", PermissionName = "删除时间范围的请求日志", Module = "requestlog", Description = "删除时间范围的请求日志", MenuKey = "requestlog", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "requestlog.view", PermissionName = "查询所有请求日志", Module = "requestlog", Description = "查询所有请求日志", MenuKey = "requestlog", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // Supervision (监管统计管理) + // 监管统计信息管理 + new Permission { PermissionNumber = "supervisioninfo.dss", PermissionName = "删除监管统计信息", Module = "supervision", Description = "删除监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "supervisioninfo.export", PermissionName = "导出监管统计信息", Module = "supervision", Description = "导出监管统计信息列表", MenuKey = "supervisioninfo", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "supervisioninfo.iss", PermissionName = "插入监管统计信息", Module = "supervision", Description = "插入监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "supervisioninfo.sssa", PermissionName = "查询所有监管统计信息", Module = "supervision", Description = "查询所有监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "supervisioninfo.uss", PermissionName = "更新监管统计信息", Module = "supervision", Description = "更新监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // Room information (客房信息管理) + // 房间管理 + new Permission { PermissionNumber = "roommap.view", PermissionName = "房态图-查看", Module = "room", Description = "房态图一览-查看", MenuKey = "roommap", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.crbr", PermissionName = "根据预约信息办理入住", Module = "room", Description = "根据预约信息办理入住", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.cr", PermissionName = "退房操作", Module = "room", Description = "退房操作", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.dbrn", PermissionName = "根据房间编号查询截止到今天住了多少天", Module = "room", Description = "根据房间编号查询截止到今天住了多少天", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.deleteroom", PermissionName = "删除房间", Module = "room", Description = "删除房间", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.export", PermissionName = "导出房间信息", Module = "room", Description = "导出房间信息列表", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.insertroom", PermissionName = "添加房间", Module = "room", Description = "添加房间", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.scura", PermissionName = "根据房间状态来查询可使用的房间", Module = "room", Description = "根据房间状态来查询可使用的房间", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.scurabrs", PermissionName = "查询可入住房间数量", Module = "room", Description = "查询可入住房间数量", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.sfrabrs", PermissionName = "查询维修房数量", Module = "room", Description = "查询维修房数量", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.sncrabrs", PermissionName = "查询脏房数量", Module = "room", Description = "查询脏房数量", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.snurabrs", PermissionName = "查询已入住房间数量", Module = "room", Description = "查询已入住房间数量", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.srrabrs", PermissionName = "查询预约房数量", Module = "room", Description = "查询预约房数量", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.sra", PermissionName = "获取所有房间信息", Module = "room", Description = "获取所有房间信息", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.srbrn", PermissionName = "根据房间编号查询房间信息", Module = "room", Description = "根据房间编号查询房间信息", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.srbrp", PermissionName = "根据房间编号查询房间价格", Module = "room", Description = "根据房间编号查询房间价格", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.srbrs", PermissionName = "根据房间状态获取相应状态的房间信息", Module = "room", Description = "根据房间状态获取相应状态的房间信息", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.srbtn", PermissionName = "获取房间分区的信息", Module = "room", Description = "获取房间分区的信息", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.tr", PermissionName = "转房操作", Module = "room", Description = "转房操作", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.updateroom", PermissionName = "更新房间", Module = "room", Description = "更新房间", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.uri", PermissionName = "根据房间编号修改房间信息(入住)", Module = "room", Description = "根据房间编号修改房间信息(入住)", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.uriwr", PermissionName = "根据房间编号修改房间信息(预约)", Module = "room", Description = "根据房间编号修改房间信息(预约)", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roommanagement.ursbrn", PermissionName = "根据房间编号更改房间状态", Module = "room", Description = "根据房间编号更改房间状态", MenuKey = "roommanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 房间配置管理 + new Permission { PermissionNumber = "roomconfig.drt", PermissionName = "删除房间配置", Module = "room", Description = "删除房间配置", MenuKey = "roomconfig", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roomconfig.export", PermissionName = "导出房间配置", Module = "room", Description = "导出房间配置列表", MenuKey = "roomconfig", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roomconfig.irt", PermissionName = "添加房间配置", Module = "room", Description = "添加房间配置", MenuKey = "roomconfig", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roomconfig.srtbrn", PermissionName = "根据房间编号查询房间类型名称", Module = "room", Description = "根据房间编号查询房间类型名称", MenuKey = "roomconfig", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roomconfig.srta", PermissionName = "获取所有房间类型", Module = "room", Description = "获取所有房间类型", MenuKey = "roomconfig", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "roomconfig.urt", PermissionName = "更新房间配置", Module = "room", Description = "更新房间配置", MenuKey = "roomconfig", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 预约信息管理 - new Permission { PermissionNumber = "resermanagement.dri", PermissionName = "删除预约信息", Module = "resermanagement", Description = "删除预约信息", MenuKey = "resermanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "resermanagement.iri", PermissionName = "添加预约信息", Module = "resermanagement", Description = "添加预约信息", MenuKey = "resermanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "resermanagement.sra", PermissionName = "获取所有预约信息", Module = "resermanagement", Description = "获取所有预约信息", MenuKey = "resermanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "resermanagement.sribrn", PermissionName = "根据房间编号获取预约信息", Module = "resermanagement", Description = "根据房间编号获取预约信息", MenuKey = "resermanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "resermanagement.srta", PermissionName = "查询所有预约类型", Module = "resermanagement", Description = "查询所有预约类型", MenuKey = "resermanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "resermanagement.uri", PermissionName = "更新预约信息", Module = "resermanagement", Description = "更新预约信息", MenuKey = "resermanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.dri", PermissionName = "删除预约信息", Module = "room", Description = "删除预约信息", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.export", PermissionName = "导出预约信息", Module = "room", Description = "导出预约信息列表", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.iri", PermissionName = "添加预约信息", Module = "room", Description = "添加预约信息", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.sra", PermissionName = "获取所有预约信息", Module = "room", Description = "获取所有预约信息", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.sribrn", PermissionName = "根据房间编号获取预约信息", Module = "room", Description = "根据房间编号获取预约信息", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.srta", PermissionName = "查询所有预约类型", Module = "room", Description = "查询所有预约类型", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "resermanagement.uri", PermissionName = "更新预约信息", Module = "room", Description = "更新预约信息", MenuKey = "resermanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 房间配置管理 - new Permission { PermissionNumber = "roomconfig.drt", PermissionName = "删除房间配置", Module = "roomconfig", Description = "删除房间配置", MenuKey = "roomconfig", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roomconfig.irt", PermissionName = "添加房间配置", Module = "roomconfig", Description = "添加房间配置", MenuKey = "roomconfig", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roomconfig.srtbrn", PermissionName = "根据房间编号查询房间类型名称", Module = "roomconfig", Description = "根据房间编号查询房间类型名称", MenuKey = "roomconfig", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roomconfig.srta", PermissionName = "获取所有房间类型", Module = "roomconfig", Description = "获取所有房间类型", MenuKey = "roomconfig", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roomconfig.urt", PermissionName = "更新房间配置", Module = "roomconfig", Description = "更新房间配置", MenuKey = "roomconfig", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 房间管理 - new Permission { PermissionNumber = "roommanagement.crbr", PermissionName = "根据预约信息办理入住", Module = "roommanagement", Description = "根据预约信息办理入住", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.cr", PermissionName = "退房操作", Module = "roommanagement", Description = "退房操作", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.dbrn", PermissionName = "根据房间编号查询截止到今天住了多少天", Module = "roommanagement", Description = "根据房间编号查询截止到今天住了多少天", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.deleteroom", PermissionName = "删除房间", Module = "roommanagement", Description = "删除房间", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.insertroom", PermissionName = "添加房间", Module = "roommanagement", Description = "添加房间", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.scura", PermissionName = "根据房间状态来查询可使用的房间", Module = "roommanagement", Description = "根据房间状态来查询可使用的房间", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.scurabrs", PermissionName = "查询可入住房间数量", Module = "roommanagement", Description = "查询可入住房间数量", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.sfrabrs", PermissionName = "查询维修房数量", Module = "roommanagement", Description = "查询维修房数量", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.sncrabrs", PermissionName = "查询脏房数量", Module = "roommanagement", Description = "查询脏房数量", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.snurabrs", PermissionName = "查询已入住房间数量", Module = "roommanagement", Description = "查询已入住房间数量", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.srrabrs", PermissionName = "查询预约房数量", Module = "roommanagement", Description = "查询预约房数量", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.sra", PermissionName = "获取所有房间信息", Module = "roommanagement", Description = "获取所有房间信息", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.srbrn", PermissionName = "根据房间编号查询房间信息", Module = "roommanagement", Description = "根据房间编号查询房间信息", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.srbrp", PermissionName = "根据房间编号查询房间价格", Module = "roommanagement", Description = "根据房间编号查询房间价格", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.srbrs", PermissionName = "根据房间状态获取相应状态的房间信息", Module = "roommanagement", Description = "根据房间状态获取相应状态的房间信息", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.srbtn", PermissionName = "获取房间分区的信息", Module = "roommanagement", Description = "获取房间分区的信息", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.tr", PermissionName = "转房操作", Module = "roommanagement", Description = "转房操作", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.updateroom", PermissionName = "更新房间", Module = "roommanagement", Description = "更新房间", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.uri", PermissionName = "根据房间编号修改房间信息(入住)", Module = "roommanagement", Description = "根据房间编号修改房间信息(入住)", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.uriwr", PermissionName = "根据房间编号修改房间信息(预约)", Module = "roommanagement", Description = "根据房间编号修改房间信息(预约)", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "roommanagement.ursbrn", PermissionName = "根据房间编号更改房间状态", Module = "roommanagement", Description = "根据房间编号更改房间状态", MenuKey = "roommanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // Customer management (客户管理) + // 会员等级规则管理 + new Permission { PermissionNumber = "viplevel.addviprule", PermissionName = "添加会员等级规则", Module = "customer", Description = "添加会员等级规则", MenuKey = "viplevel", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "viplevel.delviprule", PermissionName = "删除会员等级规则", Module = "customer", Description = "删除会员等级规则", MenuKey = "viplevel", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "viplevel.export", PermissionName = "导出会员等级规则", Module = "customer", Description = "导出会员等级规则列表", MenuKey = "viplevel", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "viplevel.svr", PermissionName = "查询会员等级规则", Module = "customer", Description = "查询会员等级规则", MenuKey = "viplevel", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "viplevel.svrlist", PermissionName = "查询会员等级规则列表", Module = "customer", Description = "查询会员等级规则列表", MenuKey = "viplevel", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "viplevel.updviprule", PermissionName = "更新会员等级规则", Module = "customer", Description = "更新会员等级规则", MenuKey = "viplevel", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 客户信息 + new Permission { PermissionNumber = "customer.dci", PermissionName = "删除客户信息", Module = "customer", Description = "删除客户信息", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customer.export", PermissionName = "导出客户信息", Module = "customer", Description = "导出客户信息列表", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customer.ici", PermissionName = "添加客户信息", Module = "customer", Description = "添加客户信息", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customer.scbi", PermissionName = "查询指定客户信息", Module = "customer", Description = "查询指定客户信息", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customer.scs", PermissionName = "查询所有客户信息", Module = "customer", Description = "查询所有客户信息", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customer.uci", PermissionName = "更新客户信息", Module = "customer", Description = "更新客户信息", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customer.uctbcn", PermissionName = "更新会员等级", Module = "customer", Description = "更新会员等级", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 客户消费信息 + new Permission { PermissionNumber = "customerspend.acs", PermissionName = "添加客户消费信息", Module = "customer", Description = "添加客户消费信息", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.export", PermissionName = "导出客户消费信息", Module = "customer", Description = "导出客户消费信息列表", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.ssbrn", PermissionName = "查询房间消费信息", Module = "customer", Description = "查询房间消费信息", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.ssia", PermissionName = "查询所有消费信息", Module = "customer", Description = "查询所有消费信息", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.shsia", PermissionName = "查询客户历史消费信息", Module = "customer", Description = "查询客户历史消费信息", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.sca", PermissionName = "查询消费总金额", Module = "customer", Description = "查询消费总金额", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.ucs", PermissionName = "撤回客户消费信息", Module = "customer", Description = "撤回客户消费信息", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customerspend.usi", PermissionName = "更新消费信息", Module = "customer", Description = "更新消费信息", MenuKey = "customerspend", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 客户类型 + new Permission { PermissionNumber = "customertype.create", PermissionName = "新增客户类型", Module = "customer", Description = "基础信息-客户类型-新增", MenuKey = "customertype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customertype.delete", PermissionName = "删除客户类型", Module = "customer", Description = "基础信息-客户类型-删除", MenuKey = "customertype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customertype.export", PermissionName = "导出客户类型", Module = "customer", Description = "基础信息-客户类型-导出列表", MenuKey = "customertype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customertype.update", PermissionName = "更新客户类型", Module = "customer", Description = "基础信息-客户类型-更新", MenuKey = "customertype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "customertype.view", PermissionName = "查询客户类型列表", Module = "customer", Description = "基础信息-客户类型-查询列表", MenuKey = "customertype", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // Human resource (酒店人事管理) // 员工管理 - new Permission { PermissionNumber = "staffmanagement.ae", PermissionName = "添加员工信息", Module = "staffmanagement", Description = "添加员工信息", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.mea", PermissionName = "员工账号禁/启用", Module = "staffmanagement", Description = "员工账号禁/启用", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.reap", PermissionName = "重置员工账号密码", Module = "staffmanagement", Description = "重置员工账号密码", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.sea", PermissionName = "获取所有工作人员信息", Module = "staffmanagement", Description = "获取所有工作人员信息", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.seibei", PermissionName = "根据登录名称查询员工信息", Module = "staffmanagement", Description = "根据登录名称查询员工信息", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.ue", PermissionName = "修改员工信息", Module = "staffmanagement", Description = "修改员工信息", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + new Permission { PermissionNumber = "staffmanagement.ae", PermissionName = "添加员工信息", Module = "humanresource", Description = "添加员工信息", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.export", PermissionName = "导出员工信息", Module = "humanresource", Description = "导出员工信息列表", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.mea", PermissionName = "员工账号禁/启用", Module = "humanresource", Description = "员工账号禁/启用", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.reap", PermissionName = "重置员工账号密码", Module = "humanresource", Description = "重置员工账号密码", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.sea", PermissionName = "获取所有工作人员信息", Module = "humanresource", Description = "获取所有工作人员信息", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.seibei", PermissionName = "根据登录名称查询员工信息", Module = "humanresource", Description = "根据登录名称查询员工信息", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.ue", PermissionName = "修改员工信息", Module = "humanresource", Description = "修改员工信息", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 员工履历管理 - new Permission { PermissionNumber = "staffmanagement.shbei", PermissionName = "根据工号查询履历信息", Module = "staffmanagement", Description = "根据工号查询履历信息", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.ahbei", PermissionName = "根据工号添加员工履历", Module = "staffmanagement", Description = "根据工号添加员工履历", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + new Permission { PermissionNumber = "staffmanagement.shbei", PermissionName = "根据工号查询履历信息", Module = "humanresource", Description = "根据工号查询履历信息", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.ahbei", PermissionName = "根据工号添加员工履历", Module = "humanresource", Description = "根据工号添加员工履历", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.update", PermissionName = "根据工号更新员工履历", Module = "humanresource", Description = "根据工号更新员工履历", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 员工打卡管理 - new Permission { PermissionNumber = "staffmanagement.stcfobwn", PermissionName = "查询今天员工是否已签到", Module = "staffmanagement", Description = "查询今天员工是否已签到", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.swcdsbei", PermissionName = "查询员工签到天数", Module = "staffmanagement", Description = "查询员工签到天数", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.acfo", PermissionName = "添加员工打卡数据", Module = "staffmanagement", Description = "添加员工打卡数据", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.scfobei", PermissionName = "根据员工编号查询其所有的打卡记录", Module = "staffmanagement", Description = "根据员工编号查询其所有的打卡记录", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + new Permission { PermissionNumber = "staffmanagement.stcfobwn", PermissionName = "查询今天员工是否已签到", Module = "humanresource", Description = "查询今天员工是否已签到", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.swcdsbei", PermissionName = "查询员工签到天数", Module = "humanresource", Description = "查询员工签到天数", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.acfo", PermissionName = "添加员工打卡数据", Module = "humanresource", Description = "添加员工打卡数据", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.scfobei", PermissionName = "根据员工编号查询其所有的打卡记录", Module = "humanresource", Description = "根据员工编号查询其所有的打卡记录", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 员工照片管理 - new Permission { PermissionNumber = "staffmanagement.ueap", PermissionName = "修改员工账号密码", Module = "staffmanagement", Description = "修改员工账号密码", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.uwp", PermissionName = "更新员工照片", Module = "staffmanagement", Description = "更新员工照片", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.dwp", PermissionName = "删除员工照片", Module = "staffmanagement", Description = "删除员工照片", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.iwp", PermissionName = "添加员工照片", Module = "staffmanagement", Description = "添加员工照片", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.ep", PermissionName = "查询员工照片", Module = "staffmanagement", Description = "查询员工照片", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + new Permission { PermissionNumber = "staffmanagement.ueap", PermissionName = "修改员工账号密码", Module = "humanresource", Description = "修改员工账号密码", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.uwp", PermissionName = "更新员工照片", Module = "humanresource", Description = "更新员工照片", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.dwp", PermissionName = "删除员工照片", Module = "humanresource", Description = "删除员工照片", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.iwp", PermissionName = "添加员工照片", Module = "humanresource", Description = "添加员工照片", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.ep", PermissionName = "查询员工照片", Module = "humanresource", Description = "查询员工照片", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 员工两步验证管理 - new Permission { PermissionNumber = "staffmanagement.dtf", PermissionName = "关闭当前员工账号 2FA", Module = "staffmanagement", Description = "关闭当前员工账号 2FA", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.etf", PermissionName = "启用当前员工账号 2FA", Module = "staffmanagement", Description = "启用当前员工账号 2FA", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.gtfs", PermissionName = "生成当前员工账号的 2FA 绑定信息", Module = "staffmanagement", Description = "生成当前员工账号的 2FA 绑定信息", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.gtfse", PermissionName = "获取当前员工账号的 2FA 状态", Module = "staffmanagement", Description = "获取当前员工账号的 2FA 状态", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "staffmanagement.rtfrc", PermissionName = "重置当前员工账号恢复备用码", Module = "staffmanagement", Description = "重置当前员工账号恢复备用码", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.dtf", PermissionName = "关闭当前员工账号 2FA", Module = "humanresource", Description = "关闭当前员工账号 2FA", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.etf", PermissionName = "启用当前员工账号 2FA", Module = "humanresource", Description = "启用当前员工账号 2FA", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.gtfs", PermissionName = "生成当前员工账号的 2FA 绑定信息", Module = "humanresource", Description = "生成当前员工账号的 2FA 绑定信息", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.gtfse", PermissionName = "获取当前员工账号的 2FA 状态", Module = "humanresource", Description = "获取当前员工账号的 2FA 状态", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "staffmanagement.rtfrc", PermissionName = "重置当前员工账号恢复备用码", Module = "humanresource", Description = "重置当前员工账号恢复备用码", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 监管统计信息管理 - new Permission { PermissionNumber = "supervisioninfo.dss", PermissionName = "删除监管统计信息", Module = "supervisioninfo", Description = "删除监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "supervisioninfo.iss", PermissionName = "插入监管统计信息", Module = "supervisioninfo", Description = "插入监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "supervisioninfo.sssa", PermissionName = "查询所有监管统计信息", Module = "supervisioninfo", Description = "查询所有监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "supervisioninfo.uss", PermissionName = "更新监管统计信息", Module = "supervisioninfo", Description = "更新监管统计信息", MenuKey = "supervisioninfo", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // Material management (酒店物资管理) + // 商品管理 + new Permission { PermissionNumber = "goodsmanagement.dst", PermissionName = "删除商品信息", Module = "material", Description = "删除商品信息", MenuKey = "goodsmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "goodsmanagement.export", PermissionName = "导出商品信息", Module = "material", Description = "导出商品信息列表", MenuKey = "goodsmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "goodsmanagement.ist", PermissionName = "添加商品", Module = "material", Description = "添加商品", MenuKey = "goodsmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "goodsmanagement.ssta", PermissionName = "查询所有商品", Module = "material", Description = "查询所有商品", MenuKey = "goodsmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "goodsmanagement.sstbnap", PermissionName = "根据商品名称和价格查询商品编号", Module = "material", Description = "根据商品名称和价格查询商品编号", MenuKey = "goodsmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "goodsmanagement.ust", PermissionName = "修改商品", Module = "material", Description = "修改商品", MenuKey = "goodsmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 管理员管理 - new Permission { PermissionNumber = "system:admin:addadmin", PermissionName = "添加管理员", Module = "system", Description = "添加管理员", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:deladmin", PermissionName = "删除管理员", Module = "system", Description = "删除管理员", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:gaal", PermissionName = "获取所有管理员列表", Module = "system", Description = "获取所有管理员列表", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:updadmin", PermissionName = "更新管理员", Module = "system", Description = "更新管理员", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 管理员类型管理 - new Permission { PermissionNumber = "system:admintype:aat", PermissionName = "添加管理员类型", Module = "system", Description = "添加管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admintype:dat", PermissionName = "删除管理员类型", Module = "system", Description = "删除管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admintype:gaat", PermissionName = "获取所有管理员类型", Module = "system", Description = "获取所有管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admintype:uat", PermissionName = "更新管理员类型", Module = "system", Description = "更新管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // Operation management (行为操作管理) + // 操作日志 + new Permission { PermissionNumber = "operationlog.delete", PermissionName = "删除时间范围的操作日志", Module = "operation", Description = "删除时间范围的操作日志", MenuKey = "operationlog", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "operationlog.export", PermissionName = "导出操作日志", Module = "operation", Description = "导出操作日志列表", MenuKey = "operationlog", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "operationlog.view", PermissionName = "查询所有操作日志", Module = "operation", Description = "查询所有操作日志", MenuKey = "operationlog", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 请求日志 + new Permission { PermissionNumber = "requestlog.delete", PermissionName = "删除时间范围的请求日志", Module = "operation", Description = "删除时间范围的请求日志", MenuKey = "requestlog", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "requestlog.export", PermissionName = "导出请求日志", Module = "operation", Description = "导出请求日志列表", MenuKey = "requestlog", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "requestlog.view", PermissionName = "查询所有请求日志", Module = "operation", Description = "查询所有请求日志", MenuKey = "requestlog", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // System management (系统管理) + // 管理员管理 + new Permission { PermissionNumber = "system:admin:addadmin", PermissionName = "添加管理员", Module = "system", Description = "添加管理员", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:deladmin", PermissionName = "删除管理员", Module = "system", Description = "删除管理员", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:export", PermissionName = "导出管理员", Module = "system", Description = "导出管理员列表", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:gaal", PermissionName = "获取所有管理员列表", Module = "system", Description = "获取所有管理员列表", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:updadmin", PermissionName = "更新管理员", Module = "system", Description = "更新管理员", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 管理员类型管理 + new Permission { PermissionNumber = "system:admintype:aat", PermissionName = "添加管理员类型", Module = "system", Description = "添加管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admintype:dat", PermissionName = "删除管理员类型", Module = "system", Description = "删除管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admintype:export", PermissionName = "导出管理员类型", Module = "system", Description = "导出管理员类型列表", MenuKey = "admintypemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admintype:gaat", PermissionName = "获取所有管理员类型", Module = "system", Description = "获取所有管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admintype:uat", PermissionName = "更新管理员类型", Module = "system", Description = "更新管理员类型", MenuKey = "admintypemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 管理员两步验证管理 - new Permission { PermissionNumber = "system:admin:gtfs", PermissionName = "获取当前管理员账号的 2FA 状态", Module = "system", Description = "获取当前管理员账号的 2FA 状态", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:dtf", PermissionName = "关闭当前管理员账号 2FA", Module = "system", Description = "关闭当前管理员账号 2FA", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:etf", PermissionName = "启用当前管理员账号 2FA", Module = "system", Description = "启用当前管理员账号 2FA", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:gtfsu", PermissionName = "生成当前管理员账号的 2FA 绑定信息", Module = "system", Description = "生成当前管理员账号的 2FA 绑定信息", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:admin:rtfrc", PermissionName = "重置当前管理员账号恢复备用码", Module = "system", Description = "重置当前管理员账号恢复备用码", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - + new Permission { PermissionNumber = "system:admin:gtfs", PermissionName = "获取当前管理员账号的 2FA 状态", Module = "system", Description = "获取当前管理员账号的 2FA 状态", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:dtf", PermissionName = "关闭当前管理员账号 2FA", Module = "system", Description = "关闭当前管理员账号 2FA", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:etf", PermissionName = "启用当前管理员账号 2FA", Module = "system", Description = "启用当前管理员账号 2FA", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:gtfsu", PermissionName = "生成当前管理员账号的 2FA 绑定信息", Module = "system", Description = "生成当前管理员账号的 2FA 绑定信息", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:admin:rtfrc", PermissionName = "重置当前管理员账号恢复备用码", Module = "system", Description = "重置当前管理员账号恢复备用码", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 角色管理 - new Permission { PermissionNumber = "system:role:aru", PermissionName = "为角色分配管理员(全量覆盖)", Module = "system", Description = "为角色分配管理员(全量覆盖)", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:deleterole", PermissionName = "删除角色", Module = "system", Description = "删除角色", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:grp", PermissionName = "为角色授予权限(全量覆盖)", Module = "system", Description = "为角色授予权限(全量覆盖)", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:insertrole", PermissionName = "添加角色", Module = "system", Description = "添加角色", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:rrp", PermissionName = "读取指定角色已授予的权限编码集合", Module = "system", Description = "读取指定角色已授予的权限编码集合", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:rru", PermissionName = "读取隶属于指定角色的管理员用户编码集合", Module = "system", Description = "读取隶属于指定角色的管理员用户编码集合", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:srl", PermissionName = "查询角色列表", Module = "system", Description = "查询角色列表", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:role:updaterole", PermissionName = "更新角色", Module = "system", Description = "更新角色", MenuKey = "rolemanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:aru", PermissionName = "为角色分配管理员(全量覆盖)", Module = "system", Description = "为角色分配管理员(全量覆盖)", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:deleterole", PermissionName = "删除角色", Module = "system", Description = "删除角色", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:export", PermissionName = "导出角色", Module = "system", Description = "导出角色列表", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:grp", PermissionName = "为角色授予权限(全量覆盖)", Module = "system", Description = "为角色授予权限(全量覆盖)", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:insertrole", PermissionName = "添加角色", Module = "system", Description = "添加角色", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:rrp", PermissionName = "读取指定角色已授予的权限编码集合", Module = "system", Description = "读取指定角色已授予的权限编码集合", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:rrg", PermissionName = "读取指定角色菜单和权限授权(菜单与权限独立)", Module = "system", Description = "读取指定角色菜单和权限授权(菜单与权限独立)", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:rru", PermissionName = "读取隶属于指定角色的管理员用户编码集合", Module = "system", Description = "读取隶属于指定角色的管理员用户编码集合", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:srl", PermissionName = "查询角色列表", Module = "system", Description = "查询角色列表", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:role:updaterole", PermissionName = "更新角色", Module = "system", Description = "更新角色", MenuKey = "rolemanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 菜单管理 + new Permission { PermissionNumber = "menumanagement.bma", PermissionName = "构建菜单树", Module = "system", Description = "构建菜单树", MenuKey = "menumanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "menumanagement.deletemenu", PermissionName = "删除菜单", Module = "system", Description = "删除菜单", MenuKey = "menumanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "menumanagement.export", PermissionName = "导出菜单", Module = "system", Description = "导出菜单列表", MenuKey = "menumanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "menumanagement.insertmenu", PermissionName = "插入菜单", Module = "system", Description = "插入菜单", MenuKey = "menumanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "menumanagement.sma", PermissionName = "查询所有菜单信息", Module = "system", Description = "查询所有菜单信息", MenuKey = "menumanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "menumanagement.updatemenu", PermissionName = "更新菜单", Module = "system", Description = "更新菜单", MenuKey = "menumanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:quartzjob:export", PermissionName = "导出Quartz任务", Module = "system", Description = "导出Quartz任务列表", MenuKey = "quartzjoblist", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 管理员-角色权限管理(网页端) - new Permission { PermissionNumber = "system:user:admin.rudp", PermissionName = "读取指定用户的“直接权限”", Module = "system", Description = "读取指定用户的“直接权限”", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:admin.rurp", PermissionName = "读取指定用户的“角色-权限”明细", Module = "system", Description = "读取指定用户的“角色-权限”明细", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:admin.rur", PermissionName = "读取指定用户已分配的角色编码集合", Module = "system", Description = "读取指定用户已分配的角色编码集合", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:admin:aup", PermissionName = "为指定用户分配“直接权限”", Module = "system", Description = "为指定用户分配“直接权限”", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:admin:aur", PermissionName = "为用户分配角色(全量覆盖)", Module = "system", Description = "为用户分配角色(全量覆盖)", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:assign.spl", PermissionName = "查询权限列表(支持条件过滤与分页/忽略分页)", Module = "system", Description = "查询权限列表(支持条件过滤与分页/忽略分页)", MenuKey = "administratormanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - // 客户-角色权限管理(网页端) - new Permission { PermissionNumber = "system:user:customer.rudp", PermissionName = "读取客户“直接权限”权限编码集合", Module = "system", Description = "读取客户“直接权限”权限编码集合", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:customer.rurp", PermissionName = "读取客户“角色-权限”明细", Module = "system", Description = "读取客户“角色-权限”明细", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:customer.rur", PermissionName = "读取客户已分配的角色编码集合", Module = "system", Description = "读取客户已分配的角色编码集合", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:customer:aup", PermissionName = "为客户分配“直接权限”", Module = "system", Description = "为客户分配“直接权限”", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:customer:aur", PermissionName = "为客户分配角色(全量覆盖)", Module = "system", Description = "为客户分配角色(全量覆盖)", MenuKey = "customer", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 主页 + // 仪表盘 + new Permission { PermissionNumber = "dashboard.view", PermissionName = "仪表盘-查看", Module = "home", Description = "仪表盘-查看", MenuKey = "dashboard", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "home.view", PermissionName = "首页-查看", Module = "home", Description = "首页-查看", MenuKey = "home", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "dashboard.bs", PermissionName = "获取业务统计信息", Module = "dashboard", Description = "获取业务统计信息", MenuKey = "dashboard", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "dashboard.hrs", PermissionName = "获取人事统计信息", Module = "dashboard", Description = "获取人事统计信息", MenuKey = "dashboard", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "dashboard.ls", PermissionName = "获取后勤统计信息", Module = "dashboard", Description = "获取后勤统计信息", MenuKey = "dashboard", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "dashboard.rs", PermissionName = "获取房间统计信息", Module = "dashboard", Description = "获取房间统计信息", MenuKey = "dashboard", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + + // 权限分配 + // 管理员-角色权限管理(网页端) + new Permission { PermissionNumber = "system:user:admin.rudp", PermissionName = "读取指定用户的“直接权限”", Module = "system", Description = "读取指定用户的“直接权限”", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:admin.rurp", PermissionName = "读取指定用户的“角色-权限”明细", Module = "system", Description = "读取指定用户的“角色-权限”明细", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:admin.rur", PermissionName = "读取指定用户已分配的角色编码集合", Module = "system", Description = "读取指定用户已分配的角色编码集合", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:admin:aup", PermissionName = "为指定用户分配“直接权限”", Module = "system", Description = "为指定用户分配“直接权限”", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:admin:aur", PermissionName = "为用户分配角色(全量覆盖)", Module = "system", Description = "为用户分配角色(全量覆盖)", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:assign.spl", PermissionName = "查询权限列表(支持条件过滤与分页/忽略分页)", Module = "system", Description = "查询权限列表(支持条件过滤与分页/忽略分页)", MenuKey = "administratormanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + // 客户-角色权限管理(网页端) + new Permission { PermissionNumber = "system:user:customer.rudp", PermissionName = "读取客户“直接权限”权限编码集合", Module = "system", Description = "读取客户“直接权限”权限编码集合", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:customer.rurp", PermissionName = "读取客户“角色-权限”明细", Module = "system", Description = "读取客户“角色-权限”明细", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:customer.rur", PermissionName = "读取客户已分配的角色编码集合", Module = "system", Description = "读取客户已分配的角色编码集合", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:customer:aup", PermissionName = "为客户分配“直接权限”", Module = "system", Description = "为客户分配“直接权限”", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:customer:aur", PermissionName = "为客户分配角色(全量覆盖)", Module = "system", Description = "为客户分配角色(全量覆盖)", MenuKey = "customer", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, // 员工-角色权限管理(网页端) - new Permission { PermissionNumber = "system:user:employee.rudp", PermissionName = "读取员工“直接权限”权限编码集合", Module = "system", Description = "读取员工“直接权限”权限编码集合", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:employee.rurp", PermissionName = "读取员工“角色-权限”明细", Module = "system", Description = "读取员工“角色-权限”明细", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:employee.rur", PermissionName = "读取员工已分配的角色编码集合", Module = "system", Description = "读取员工已分配的角色编码集合", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:employee:aup", PermissionName = "为员工分配“直接权限”", Module = "system", Description = "为员工分配“直接权限”", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, - new Permission { PermissionNumber = "system:user:employee:aur", PermissionName = "为员工分配角色(全量覆盖)", Module = "system", Description = "为员工分配角色(全量覆盖)", MenuKey = "staffmanagement", ParentNumber = null, IsDelete = 0, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:employee.rudp", PermissionName = "读取员工“直接权限”权限编码集合", Module = "system", Description = "读取员工“直接权限”权限编码集合", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:employee.rurp", PermissionName = "读取员工“角色-权限”明细", Module = "system", Description = "读取员工“角色-权限”明细", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:employee.rur", PermissionName = "读取员工已分配的角色编码集合", Module = "system", Description = "读取员工已分配的角色编码集合", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:employee:aup", PermissionName = "为员工分配“直接权限”", Module = "system", Description = "为员工分配“直接权限”", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, + new Permission { PermissionNumber = "system:user:employee:aur", PermissionName = "为员工分配角色(全量覆盖)", Module = "system", Description = "为员工分配角色(全量覆盖)", MenuKey = "staffmanagement", ParentNumber = null, DataInsUsr = "System", DataInsDate = DateTime.Now }, }; diff --git a/EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs new file mode 100644 index 0000000000000000000000000000000000000000..643d7baf50879ccb85077c859ae23409c137bba8 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs @@ -0,0 +1,433 @@ +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Data; +using EOM.TSHotelManagement.Domain; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System.Security.Claims; +using System.Text.Json; + +namespace EOM.TSHotelManagement.Service +{ + /// + /// 收藏夹服务实现 + /// + public class FavoriteCollectionService : IFavoriteCollectionService + { + private static readonly JsonSerializerOptions JsonSerializerOptions = new(JsonSerializerDefaults.Web); + + private readonly GenericRepository _favoriteCollectionRepository; + private readonly GenericRepository _administratorRepository; + private readonly GenericRepository _employeeRepository; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + + /// + /// 构造收藏夹服务 + /// + /// 收藏夹仓储 + /// 管理员仓储 + /// 员工仓储 + /// HTTP 上下文访问器 + /// 日志组件 + public FavoriteCollectionService( + GenericRepository favoriteCollectionRepository, + GenericRepository administratorRepository, + GenericRepository employeeRepository, + IHttpContextAccessor httpContextAccessor, + ILogger logger) + { + _favoriteCollectionRepository = favoriteCollectionRepository; + _administratorRepository = administratorRepository; + _employeeRepository = employeeRepository; + _httpContextAccessor = httpContextAccessor; + _logger = logger; + } + + /// + /// 保存当前登录用户的收藏夹快照 + /// + /// 收藏夹保存请求 + /// 保存结果 + public SingleOutputDto SaveFavoriteCollection(SaveFavoriteCollectionInputDto input) + { + input ??= new SaveFavoriteCollectionInputDto(); + + try + { + var currentUser = ResolveCurrentUser(); + if (currentUser == null) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Unauthorized, + Message = LocalizationHelper.GetLocalizedString("Unauthorized.", "Unauthorized."), + Data = null + }; + } + + if (!TryValidateRequestedIdentity(currentUser, input, out var forbiddenResponse)) + { + return forbiddenResponse!; + } + + var normalizedRoutes = NormalizeRoutes(input.FavoriteRoutes); + var normalizedUpdatedAt = NormalizeUpdatedAt(input.UpdatedAt); + var normalizedTriggeredBy = NormalizeText(input.TriggeredBy, 32); + var favoriteRoutesJson = JsonSerializer.Serialize(normalizedRoutes, JsonSerializerOptions); + var saveResult = TrySaveSnapshot(currentUser, input.RowVersion, favoriteRoutesJson, normalizedRoutes.Count, normalizedUpdatedAt, normalizedTriggeredBy); + + if (saveResult.Outcome == SaveSnapshotOutcome.Conflict) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Conflict, + Message = LocalizationHelper.GetLocalizedString( + "Data has been modified by another user. Please refresh and retry.", + "数据已被其他用户修改,请刷新后重试。"), + Data = null + }; + } + + if (saveResult.Outcome == SaveSnapshotOutcome.Failed) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString("Failed to save favorite collection.", "Failed to save favorite collection."), + Data = null + }; + } + + return new SingleOutputDto + { + Message = LocalizationHelper.GetLocalizedString("Favorite collection saved.", "Favorite collection saved."), + Data = new SaveFavoriteCollectionOutputDto + { + Saved = true, + RouteCount = normalizedRoutes.Count, + UpdatedAt = normalizedUpdatedAt, + RowVersion = saveResult.RowVersion + } + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to save favorite collection."); + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString("Failed to save favorite collection.", "Failed to save favorite collection."), + Data = null + }; + } + } + + /// + /// 获取当前登录用户的收藏夹快照 + /// + /// 收藏夹读取结果 + public SingleOutputDto GetFavoriteCollection() + { + try + { + var currentUser = ResolveCurrentUser(); + if (currentUser == null) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Unauthorized, + Message = LocalizationHelper.GetLocalizedString("Unauthorized.", "Unauthorized."), + Data = null + }; + } + + var collection = _favoriteCollectionRepository.GetFirst(x => x.UserNumber == currentUser.UserNumber); + if (collection == null) + { + return new SingleOutputDto + { + Message = "OK", + Data = new ReadFavoriteCollectionOutputDto() + }; + } + + return new SingleOutputDto + { + Message = "OK", + Data = new ReadFavoriteCollectionOutputDto + { + FavoriteRoutes = DeserializeRoutes(collection.FavoriteRoutesJson), + UpdatedAt = DateTime.SpecifyKind(collection.UpdatedAt, DateTimeKind.Utc), + RowVersion = collection.RowVersion + } + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get favorite collection."); + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString("Failed to get favorite collection.", "Failed to get favorite collection."), + Data = null + }; + } + } + + private SaveSnapshotResult TrySaveSnapshot( + CurrentUserSnapshot currentUser, + long? rowVersion, + string favoriteRoutesJson, + int routeCount, + DateTime updatedAt, + string? triggeredBy) + { + const int maxInsertRetryCount = 2; + + for (var attempt = 1; attempt <= maxInsertRetryCount; attempt++) + { + var existing = _favoriteCollectionRepository.GetFirst(x => x.UserNumber == currentUser.UserNumber); + + if (existing == null) + { + if (rowVersion.HasValue && rowVersion.Value > 0) + { + _logger.LogWarning( + "Favorite collection insert rejected because client provided stale row version {RowVersion} for user {UserNumber}.", + rowVersion.Value, + currentUser.UserNumber); + return new SaveSnapshotResult(SaveSnapshotOutcome.Conflict); + } + + var entity = new UserFavoriteCollection + { + UserNumber = currentUser.UserNumber, + LoginType = currentUser.LoginType, + Account = currentUser.Account, + FavoriteRoutesJson = favoriteRoutesJson, + RouteCount = routeCount, + UpdatedAt = updatedAt, + TriggeredBy = triggeredBy + }; + + try + { + if (_favoriteCollectionRepository.Insert(entity)) + { + return new SaveSnapshotResult(SaveSnapshotOutcome.Saved, entity.RowVersion); + } + } + catch (Exception ex) + { + _logger.LogWarning( + ex, + "Insert favorite collection snapshot failed on attempt {Attempt} for user {UserNumber}.", + attempt, + currentUser.UserNumber); + } + + continue; + } + + if (!rowVersion.HasValue || rowVersion.Value <= 0) + { + _logger.LogWarning( + "Favorite collection update rejected because row version is missing for user {UserNumber}. CurrentRowVersion={CurrentRowVersion}.", + currentUser.UserNumber, + existing.RowVersion); + return new SaveSnapshotResult(SaveSnapshotOutcome.Conflict); + } + + existing.RowVersion = rowVersion.Value; + existing.LoginType = currentUser.LoginType; + existing.Account = currentUser.Account; + existing.FavoriteRoutesJson = favoriteRoutesJson; + existing.RouteCount = routeCount; + existing.UpdatedAt = updatedAt; + existing.TriggeredBy = triggeredBy; + + if (_favoriteCollectionRepository.Update(existing)) + { + return new SaveSnapshotResult(SaveSnapshotOutcome.Saved, existing.RowVersion); + } + + _logger.LogWarning( + "Favorite collection update hit a concurrency conflict for user {UserNumber}. ExpectedRowVersion={ExpectedRowVersion}.", + currentUser.UserNumber, + rowVersion.Value); + return new SaveSnapshotResult(SaveSnapshotOutcome.Conflict); + } + + return new SaveSnapshotResult(SaveSnapshotOutcome.Failed); + } + + private bool TryValidateRequestedIdentity( + CurrentUserSnapshot currentUser, + SaveFavoriteCollectionInputDto input, + out SingleOutputDto? forbiddenResponse) + { + forbiddenResponse = null; + + var requestAccount = NormalizeText(input.Account, 128); + if (!string.IsNullOrWhiteSpace(requestAccount) && !IsCurrentAccount(currentUser, requestAccount)) + { + _logger.LogWarning( + "Favorite collection request account mismatch. UserNumber={UserNumber}, RequestAccount={RequestAccount}, ResolvedAccount={ResolvedAccount}.", + currentUser.UserNumber, + requestAccount, + currentUser.Account); + + forbiddenResponse = new SingleOutputDto + { + Code = BusinessStatusCode.Forbidden, + Message = LocalizationHelper.GetLocalizedString( + "Requested identity does not match current user.", + "请求身份与当前登录用户不一致。"), + Data = null + }; + + return false; + } + + var requestLoginType = NormalizeText(input.LoginType, 32); + if (!string.IsNullOrWhiteSpace(requestLoginType) + && !string.Equals(requestLoginType, currentUser.LoginType, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning( + "Favorite collection request login type mismatch. UserNumber={UserNumber}, RequestLoginType={RequestLoginType}, ResolvedLoginType={ResolvedLoginType}.", + currentUser.UserNumber, + requestLoginType, + currentUser.LoginType); + + forbiddenResponse = new SingleOutputDto + { + Code = BusinessStatusCode.Forbidden, + Message = LocalizationHelper.GetLocalizedString( + "Requested identity does not match current user.", + "请求身份与当前登录用户不一致。"), + Data = null + }; + + return false; + } + + return true; + } + + private CurrentUserSnapshot? ResolveCurrentUser() + { + var principal = _httpContextAccessor.HttpContext?.User; + var userNumber = principal?.FindFirst(ClaimTypes.SerialNumber)?.Value + ?? principal?.FindFirst("serialnumber")?.Value + ?? principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + if (string.IsNullOrWhiteSpace(userNumber)) + { + return null; + } + + var administrator = _administratorRepository.GetFirst(x => x.Number == userNumber && x.IsDelete != 1); + if (administrator != null) + { + return new CurrentUserSnapshot(userNumber, "admin", administrator.Account); + } + + var employee = _employeeRepository.GetFirst(x => x.EmployeeId == userNumber && x.IsDelete != 1); + if (employee != null) + { + return new CurrentUserSnapshot(userNumber, "employee", employee.EmployeeId); + } + + var fallbackAccount = NormalizeText( + principal?.FindFirst("account")?.Value ?? principal?.Identity?.Name, + 128); + var loginType = NormalizeText( + principal?.FindFirst("login_type")?.Value ?? principal?.FindFirst("logintype")?.Value, + 32) ?? "unknown"; + + return new CurrentUserSnapshot(userNumber, loginType, fallbackAccount); + } + + private static bool IsCurrentAccount(CurrentUserSnapshot currentUser, string requestAccount) + { + return string.Equals(requestAccount, currentUser.Account, StringComparison.OrdinalIgnoreCase) + || string.Equals(requestAccount, currentUser.UserNumber, StringComparison.OrdinalIgnoreCase); + } + + private static List NormalizeRoutes(IEnumerable? routes) + { + var result = new List(); + var uniqueRoutes = new HashSet(StringComparer.Ordinal); + + foreach (var route in routes ?? Enumerable.Empty()) + { + var normalizedRoute = NormalizeText(route, 2048); + if (string.IsNullOrWhiteSpace(normalizedRoute)) + { + continue; + } + + if (uniqueRoutes.Add(normalizedRoute)) + { + result.Add(normalizedRoute); + } + } + + return result; + } + + private static List DeserializeRoutes(string? favoriteRoutesJson) + { + if (string.IsNullOrWhiteSpace(favoriteRoutesJson)) + { + return new List(); + } + + try + { + return JsonSerializer.Deserialize>(favoriteRoutesJson, JsonSerializerOptions) ?? new List(); + } + catch + { + return new List(); + } + } + + private static DateTime NormalizeUpdatedAt(DateTime? updatedAt) + { + if (!updatedAt.HasValue) + { + return DateTime.UtcNow; + } + + return updatedAt.Value.Kind switch + { + DateTimeKind.Utc => updatedAt.Value, + DateTimeKind.Local => updatedAt.Value.ToUniversalTime(), + _ => DateTime.SpecifyKind(updatedAt.Value, DateTimeKind.Utc) + }; + } + + private static string? NormalizeText(string? value, int maxLength) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + var trimmed = value.Trim(); + return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength]; + } + + private sealed record CurrentUserSnapshot(string UserNumber, string LoginType, string? Account); + private sealed record SaveSnapshotResult(SaveSnapshotOutcome Outcome, long RowVersion = 0); + + private enum SaveSnapshotOutcome + { + Saved, + Conflict, + Failed + } + } +} diff --git a/EOM.TSHotelManagement.Service/Application/FavoriteCollection/IFavoriteCollectionService.cs b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/IFavoriteCollectionService.cs new file mode 100644 index 0000000000000000000000000000000000000000..cdb5e3966ef6884accee0f064555faae589b9f30 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/IFavoriteCollectionService.cs @@ -0,0 +1,23 @@ +using EOM.TSHotelManagement.Contract; + +namespace EOM.TSHotelManagement.Service +{ + /// + /// 收藏夹服务接口 + /// + public interface IFavoriteCollectionService + { + /// + /// 保存当前用户的收藏夹快照 + /// + /// 收藏夹保存请求 + /// 保存结果 + SingleOutputDto SaveFavoriteCollection(SaveFavoriteCollectionInputDto input); + + /// + /// 获取当前用户的收藏夹快照 + /// + /// 收藏夹读取结果 + SingleOutputDto GetFavoriteCollection(); + } +} diff --git a/EOM.TSHotelManagement.Service/Application/Profile/IProfileService.cs b/EOM.TSHotelManagement.Service/Application/Profile/IProfileService.cs new file mode 100644 index 0000000000000000000000000000000000000000..13c277d2f003aa5a6c038654a5f577d6c0843423 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Application/Profile/IProfileService.cs @@ -0,0 +1,15 @@ +using EOM.TSHotelManagement.Contract; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.Service +{ + public interface IProfileService + { + SingleOutputDto GetCurrentProfile(string serialNumber); + + Task> UploadAvatar(string serialNumber, UploadAvatarInputDto inputDto, IFormFile file); + + BaseResponse ChangePassword(string serialNumber, ChangePasswordInputDto inputDto); + } +} diff --git a/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs b/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs new file mode 100644 index 0000000000000000000000000000000000000000..351ea568cea94ec9fd51a90e9bce221ef3038913 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs @@ -0,0 +1,586 @@ +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Data; +using EOM.TSHotelManagement.Domain; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Mail; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.Service +{ + public class ProfileService( + GenericRepository adminRepository, + GenericRepository adminPhotoRepository, + GenericRepository adminTypeRepository, + GenericRepository employeeRepository, + GenericRepository employeePhotoRepository, + GenericRepository departmentRepository, + GenericRepository positionRepository, + DataProtectionHelper dataProtectionHelper, + LskyHelper lskyHelper, + MailHelper mailHelper, + ILogger logger) : IProfileService + { + private const string AdminLoginType = "admin"; + private const string EmployeeLoginType = "employee"; + private readonly GenericRepository _adminRepository = adminRepository; + private readonly GenericRepository _adminPhotoRepository = adminPhotoRepository; + private readonly GenericRepository _adminTypeRepository = adminTypeRepository; + private readonly GenericRepository _employeeRepository = employeeRepository; + private readonly GenericRepository _employeePhotoRepository = employeePhotoRepository; + private readonly GenericRepository _departmentRepository = departmentRepository; + private readonly GenericRepository _positionRepository = positionRepository; + private readonly DataProtectionHelper _dataProtectionHelper = dataProtectionHelper; + private readonly LskyHelper _lskyHelper = lskyHelper; + private readonly MailHelper _mailHelper = mailHelper; + private readonly ILogger _logger = logger; + + public SingleOutputDto GetCurrentProfile(string serialNumber) + { + try + { + var currentUser = ResolveCurrentUser(serialNumber); + if (currentUser == null) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.NotFound, + Message = LocalizationHelper.GetLocalizedString("Current profile not found", "未找到当前登录人资料") + }; + } + + return new SingleOutputDto + { + Message = LocalizationHelper.GetLocalizedString("ok", "成功"), + Data = BuildCurrentProfileOutput(currentUser) + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting current profile for serial number: {SerialNumber}", serialNumber); + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message) + }; + } + } + + public async Task> UploadAvatar(string serialNumber, UploadAvatarInputDto inputDto, IFormFile file) + { + try + { + var currentUser = ResolveCurrentUser(serialNumber); + if (currentUser == null) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.NotFound, + Message = LocalizationHelper.GetLocalizedString("Current profile not found", "未找到当前登录人资料") + }; + } + + if (!string.IsNullOrWhiteSpace(inputDto?.LoginType) + && !string.Equals(inputDto.LoginType, currentUser.LoginType, StringComparison.OrdinalIgnoreCase)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Login type does not match current user", "账号类型与当前登录人不匹配") + }; + } + + var uploadResult = await UploadImageAsync(file); + if (!uploadResult.Success) + { + return uploadResult; + } + + var saveResult = SaveAvatarPath(currentUser, uploadResult.Data.PhotoUrl); + if (!saveResult.Success) + { + return saveResult; + } + + return new SingleOutputDto + { + Message = LocalizationHelper.GetLocalizedString("ok", "成功"), + Data = new UploadAvatarOutputDto + { + PhotoUrl = uploadResult.Data.PhotoUrl + } + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error uploading avatar for serial number: {SerialNumber}", serialNumber); + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message) + }; + } + } + + public BaseResponse ChangePassword(string serialNumber, ChangePasswordInputDto inputDto) + { + try + { + var currentUser = ResolveCurrentUser(serialNumber); + if (currentUser == null) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.NotFound, + Message = LocalizationHelper.GetLocalizedString("Current profile not found", "未找到当前登录人资料") + }; + } + + if (!string.Equals(inputDto.NewPassword, inputDto.ConfirmPassword, StringComparison.Ordinal)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("The new password and confirmation password do not match", "新密码与确认密码不一致") + }; + } + + if (string.Equals(inputDto.OldPassword, inputDto.NewPassword, StringComparison.Ordinal)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("The new password cannot be the same as the old password", "新密码不能与旧密码相同") + }; + } + + BaseResponse updateResponse = currentUser.LoginType switch + { + AdminLoginType => ChangeAdminPassword(currentUser.Admin, inputDto), + EmployeeLoginType => ChangeEmployeePassword(currentUser.Employee, inputDto), + _ => new BaseResponse(BusinessStatusCode.BadRequest, LocalizationHelper.GetLocalizedString("Unsupported login type", "不支持的登录类型")) + }; + + if (!updateResponse.Success) + { + return new SingleOutputDto + { + Code = updateResponse.Code, + Message = updateResponse.Message + }; + } + + return new SingleOutputDto + { + Message = LocalizationHelper.GetLocalizedString("Password changed successfully", "密码修改成功"), + Data = null + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error changing password for serial number: {SerialNumber}", serialNumber); + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), + Data = null + }; + } + } + + private CurrentProfileOutputDto BuildCurrentProfileOutput(CurrentUserProfile currentUser) + { + return string.Equals(currentUser.LoginType, AdminLoginType, StringComparison.OrdinalIgnoreCase) + ? BuildAdminCurrentProfile(currentUser.Admin, currentUser.PhotoUrl) + : BuildEmployeeCurrentProfile(currentUser.Employee, currentUser.PhotoUrl); + } + + private CurrentProfileOutputDto BuildAdminCurrentProfile(Administrator admin, string photoUrl) + { + var account = FirstNonEmpty(admin.Account, admin.Number); + var displayName = FirstNonEmpty(admin.Name, account); + var typeCode = FirstNonEmpty(admin.Type, string.Empty); + var typeName = FirstNonEmpty( + _adminTypeRepository.GetFirst(a => a.TypeId == admin.Type && a.IsDelete != 1)?.TypeName, + typeCode); + + return new CurrentProfileOutputDto + { + LoginType = AdminLoginType, + UserNumber = FirstNonEmpty(admin.Number, account), + Account = account, + DisplayName = displayName, + PhotoUrl = FirstNonEmpty(photoUrl, string.Empty), + Profile = new CurrentProfileAdminDto + { + Number = FirstNonEmpty(admin.Number, account), + Account = account, + Name = displayName, + TypeName = typeName, + Type = typeCode, + IsSuperAdmin = admin.IsSuperAdmin, + PhotoUrl = FirstNonEmpty(photoUrl, string.Empty) + } + }; + } + + private CurrentProfileOutputDto BuildEmployeeCurrentProfile(Domain.Employee employee, string photoUrl) + { + var account = FirstNonEmpty(employee.EmailAddress, employee.EmployeeId); + var displayName = FirstNonEmpty(employee.Name, employee.EmployeeId); + var departmentName = FirstNonEmpty( + _departmentRepository.GetFirst(a => a.DepartmentNumber == employee.Department && a.IsDelete != 1)?.DepartmentName, + employee.Department); + var positionName = FirstNonEmpty( + _positionRepository.GetFirst(a => a.PositionNumber == employee.Position && a.IsDelete != 1)?.PositionName, + employee.Position); + + return new CurrentProfileOutputDto + { + LoginType = EmployeeLoginType, + UserNumber = FirstNonEmpty(employee.EmployeeId, account), + Account = account, + DisplayName = displayName, + PhotoUrl = FirstNonEmpty(photoUrl, string.Empty), + Profile = new CurrentProfileEmployeeDto + { + EmployeeId = FirstNonEmpty(employee.EmployeeId, account), + Name = displayName, + DepartmentName = departmentName, + PositionName = positionName, + PhoneNumber = FirstNonEmpty(dataProtectionHelper.SafeDecryptEmployeeData(employee.PhoneNumber), string.Empty), + EmailAddress = FirstNonEmpty(employee.EmailAddress, account), + Address = FirstNonEmpty(employee.Address, string.Empty), + HireDate = FormatDate(employee.HireDate), + DateOfBirth = FormatDate(employee.DateOfBirth), + PhotoUrl = FirstNonEmpty(photoUrl, string.Empty) + } + }; + } + + private BaseResponse ChangeAdminPassword(Administrator admin, ChangePasswordInputDto inputDto) + { + var currentPassword = _dataProtectionHelper.SafeDecryptAdministratorData(admin.Password); + if (!string.Equals(inputDto.OldPassword, currentPassword, StringComparison.Ordinal)) + { + return new BaseResponse( + BusinessStatusCode.BadRequest, + LocalizationHelper.GetLocalizedString("The old password is incorrect", "旧密码不正确")); + } + + admin.Password = _dataProtectionHelper.EncryptAdministratorData(inputDto.NewPassword); + var updateResult = _adminRepository.Update(admin); + if (!updateResult) + { + return BaseResponseFactory.ConcurrencyConflict(); + } + + TrySendPasswordChangeEmail(admin.Account, admin.Name, inputDto.NewPassword); + return new BaseResponse(); + } + + private BaseResponse ChangeEmployeePassword(Domain.Employee employee, ChangePasswordInputDto inputDto) + { + var currentPassword = _dataProtectionHelper.SafeDecryptEmployeeData(employee.Password); + if (!string.Equals(inputDto.OldPassword, currentPassword, StringComparison.Ordinal)) + { + return new BaseResponse( + BusinessStatusCode.BadRequest, + LocalizationHelper.GetLocalizedString("The old password is incorrect", "旧密码不正确")); + } + + employee.Password = _dataProtectionHelper.EncryptEmployeeData(inputDto.NewPassword); + employee.IsInitialize = 1; + var updateResult = _employeeRepository.Update(employee); + if (!updateResult) + { + return BaseResponseFactory.ConcurrencyConflict(); + } + + TrySendPasswordChangeEmail(employee.EmailAddress, employee.Name, inputDto.NewPassword); + return new BaseResponse(); + } + + private async Task> UploadImageAsync(IFormFile file) + { + if (!await _lskyHelper.GetEnabledState()) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Image upload service is not enabled", "图片上传服务未启用") + }; + } + + if (file == null || file.Length == 0) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("File cannot null", "文件不能为空") + }; + } + + if (file.Length > 1048576) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Image size exceeds 1MB limit", "图片大小不能超过1MB") + }; + } + + if (file.ContentType != "image/jpeg" && file.ContentType != "image/png") + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Invalid image format", "图片格式不正确") + }; + } + + using var stream = file.OpenReadStream(); + if (!TryDetectImageContentType(stream, out var detectedContentType)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Invalid image format", "图片格式不正确") + }; + } + + if (!IsAllowedDeclaredImageContentType(file.ContentType, detectedContentType)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("Invalid image format", "图片格式不正确") + }; + } + + var token = await _lskyHelper.GetImageStorageTokenAsync(); + if (string.IsNullOrWhiteSpace(token)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString("Get Token Fail", "获取Token失败") + }; + } + + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var imageUrl = await _lskyHelper.UploadImageAsync( + fileStream: stream, + fileName: file.FileName, + contentType: detectedContentType, + token: token); + + if (string.IsNullOrWhiteSpace(imageUrl)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString("Image upload failed", "图片上传失败") + }; + } + + return new SingleOutputDto + { + Data = new UploadAvatarOutputDto + { + PhotoUrl = imageUrl + } + }; + } + + private SingleOutputDto SaveAvatarPath(CurrentUserProfile currentUser, string photoUrl) + { + if (string.Equals(currentUser.LoginType, AdminLoginType, StringComparison.OrdinalIgnoreCase)) + { + var adminPhoto = _adminPhotoRepository.GetFirst(a => a.AdminNumber == currentUser.UserNumber && a.IsDelete != 1); + if (adminPhoto == null) + { + _adminPhotoRepository.Insert(new AdministratorPhoto + { + AdminNumber = currentUser.UserNumber, + PhotoPath = photoUrl + }); + + return new SingleOutputDto(); + } + + adminPhoto.PhotoPath = photoUrl; + if (!_adminPhotoRepository.Update(adminPhoto)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Conflict, + Message = LocalizationHelper.GetLocalizedString("Data has been modified by another user. Please refresh and retry.", "数据已被其他用户修改,请刷新后重试。") + }; + } + + return new SingleOutputDto(); + } + + var employeePhoto = _employeePhotoRepository.GetFirst(a => a.EmployeeId == currentUser.UserNumber && a.IsDelete != 1); + if (employeePhoto == null) + { + _employeePhotoRepository.Insert(new EmployeePhoto + { + EmployeeId = currentUser.UserNumber, + PhotoPath = photoUrl + }); + + return new SingleOutputDto(); + } + + employeePhoto.PhotoPath = photoUrl; + if (!_employeePhotoRepository.Update(employeePhoto)) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.Conflict, + Message = LocalizationHelper.GetLocalizedString("Data has been modified by another user. Please refresh and retry.", "数据已被其他用户修改,请刷新后重试。") + }; + } + + return new SingleOutputDto(); + } + + private CurrentUserProfile? ResolveCurrentUser(string serialNumber) + { + if (string.IsNullOrWhiteSpace(serialNumber)) + { + return null; + } + + var admin = _adminRepository.GetFirst(a => a.Number == serialNumber && a.IsDelete != 1); + if (admin != null) + { + var adminPhoto = _adminPhotoRepository.GetFirst(a => a.AdminNumber == admin.Number && a.IsDelete != 1); + return new CurrentUserProfile + { + LoginType = AdminLoginType, + UserNumber = admin.Number, + Account = admin.Account, + DisplayName = admin.Name, + PhotoUrl = adminPhoto?.PhotoPath ?? string.Empty, + Admin = admin + }; + } + + var employee = _employeeRepository.GetFirst(a => a.EmployeeId == serialNumber && a.IsDelete != 1); + if (employee != null) + { + var employeePhoto = _employeePhotoRepository.GetFirst(a => a.EmployeeId == employee.EmployeeId && a.IsDelete != 1); + return new CurrentUserProfile + { + LoginType = EmployeeLoginType, + UserNumber = employee.EmployeeId, + Account = string.IsNullOrWhiteSpace(employee.EmailAddress) ? employee.EmployeeId : employee.EmailAddress, + DisplayName = employee.Name, + PhotoUrl = employeePhoto?.PhotoPath ?? string.Empty, + Employee = employee + }; + } + + return null; + } + + private void TrySendPasswordChangeEmail(string? emailAddress, string? displayName, string newPassword) + { + if (string.IsNullOrWhiteSpace(emailAddress) || !MailAddress.TryCreate(emailAddress, out _)) + { + return; + } + + try + { + var template = EmailTemplate.GetUpdatePasswordTemplate(displayName ?? "User", newPassword); + _mailHelper.SendMail(new List { emailAddress }, template.Subject, template.Body, new List { emailAddress }); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Password change email send failed for account: {Account}", emailAddress); + } + } + + private static bool IsAllowedDeclaredImageContentType(string? declaredContentType, string detectedContentType) + { + if (string.IsNullOrWhiteSpace(declaredContentType)) + { + return true; + } + + return string.Equals(declaredContentType, detectedContentType, StringComparison.OrdinalIgnoreCase); + } + + private static bool TryDetectImageContentType(Stream stream, out string detectedContentType) + { + detectedContentType = string.Empty; + if (stream == null || !stream.CanRead) + { + return false; + } + + var header = new byte[8]; + var bytesRead = stream.Read(header, 0, header.Length); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var signature = header.AsSpan(0, bytesRead); + if (signature.SequenceEqual(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A })) + { + detectedContentType = "image/png"; + return true; + } + + if (signature.Length >= 3 + && signature[0] == 0xFF + && signature[1] == 0xD8 + && signature[2] == 0xFF) + { + detectedContentType = "image/jpeg"; + return true; + } + + return false; + } + + private static string FirstNonEmpty(string? primary, string? fallback) + { + if (!string.IsNullOrWhiteSpace(primary)) + { + return primary; + } + + return fallback ?? string.Empty; + } + + private static string FormatDate(DateOnly date) + { + return date == default ? string.Empty : date.ToString("yyyy-MM-dd"); + } + + private sealed class CurrentUserProfile + { + public string LoginType { get; set; } + public string UserNumber { get; set; } + public string Account { get; set; } + public string DisplayName { get; set; } + public string PhotoUrl { get; set; } + public Administrator Admin { get; set; } + public Domain.Employee Employee { get; set; } + } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs b/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs index a3763018cf880abe5dd1c5499b902cdaee90d6f1..fb0480ee28990c30f026079ff713327cd6656704 100644 --- a/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs +++ b/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs @@ -48,7 +48,7 @@ namespace EOM.TSHotelManagement.Service /// /// 员工 /// - private readonly GenericRepository employeeRepository; + private readonly GenericRepository employeeRepository; /// /// @@ -56,7 +56,7 @@ namespace EOM.TSHotelManagement.Service /// /// /// - public AssetService(GenericRepository assetRepository, GenericRepository deptRepository, GenericRepository employeeRepository) + public AssetService(GenericRepository assetRepository, GenericRepository deptRepository, GenericRepository employeeRepository) { this.assetRepository = assetRepository; this.deptRepository = deptRepository; @@ -99,15 +99,15 @@ namespace EOM.TSHotelManagement.Service //查询所有部门信息 List depts = deptRepository.GetList(); //查询所有员工信息 - List employees = employeeRepository.GetList(); + List employees = employeeRepository.GetList(); - var where = SqlFilterBuilder.BuildExpression(asset); + var where = SqlFilterBuilder.BuildExpression(asset, nameof(Asset.AcquisitionDate)); where = where.And(a => a.IsDelete == asset.IsDelete); int count = 0; List assets = new List(); - if (asset.Page != 0 && asset.PageSize != 0) + if (!asset.IgnorePaging) { assets = assetRepository.AsQueryable().Where(where.ToExpression()).OrderBy(a => a.AssetNumber) .ToPageList((int)asset.Page, (int)asset.PageSize, ref count); @@ -125,7 +125,7 @@ namespace EOM.TSHotelManagement.Service var dept = depts.SingleOrDefault(a => a.DepartmentNumber.Equals(source.DepartmentCode)); source.DepartmentName = dept == null ? "" : dept.DepartmentName; var worker = employees.SingleOrDefault(a => a.EmployeeId.Equals(source.AcquiredByEmployeeId)); - source.AcquiredByEmployeeName = worker == null ? "" : worker.EmployeeName; + source.AcquiredByName = worker == null ? "" : worker.Name; source.AssetValueFormatted = source.AssetValue == 0 ? "" : Decimal.Parse(source.AssetValue.ToString()).ToString("#,##0.00").ToString(); }); diff --git a/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs b/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs index 80cb10a259471ad7587167008847c1cfda9fb129..2962c7aac385529c70d22be7c90742d5eb6c36cf 100644 --- a/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs +++ b/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs @@ -1,4 +1,4 @@ -using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; @@ -6,6 +6,7 @@ using jvncorelib.CodeLib; using jvncorelib.EntityLib; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using System.IdentityModel.Tokens.Jwt; using System.Net.Mail; using System.Security.Claims; using System.Text.Json; @@ -148,35 +149,30 @@ namespace EOM.TSHotelManagement.Service copyCustomerAccount.Password = null; - copyCustomerAccount.UserToken = jWTHelper.GenerateJWT(new ClaimsIdentity(new Claim[] + if (usedRecoveryCode) + { + NotifyRecoveryCodeLoginByEmail(copyCustomerAccount.EmailAddress, copyCustomerAccount.Name, copyCustomerAccount.Account); + } + + var responseResult = EntityMapper.Map(copyCustomerAccount); + + responseResult.UserToken = jWTHelper.GenerateJWT(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, customerAccount.Name), new Claim(ClaimTypes.SerialNumber, customerAccount.CustomerNumber), new Claim("account", customerAccount.Account), - new Claim(ClaimTypes.UserData, JsonSerializer.Serialize(customerAccount)), - }), 10080); // 7天有效期 - - if (usedRecoveryCode) + }), 15); // 15分钟有效期,与员工和管理员保持一致 + responseResult.RefreshToken = jWTHelper.GenerateRefreshToken(new ClaimsIdentity(new Claim[] { - NotifyRecoveryCodeLoginByEmail(copyCustomerAccount.EmailAddress, copyCustomerAccount.Name, copyCustomerAccount.Account); - } + new Claim(ClaimTypes.SerialNumber, customerAccount.CustomerNumber), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + })); return new SingleOutputDto() { Code = BusinessStatusCode.Success, Message = LocalizationHelper.GetLocalizedString("Login successful", "登录成功"), - Data = new ReadCustomerAccountOutputDto - { - Account = copyCustomerAccount.Account, - EmailAddress = copyCustomerAccount.EmailAddress, - Name = copyCustomerAccount.Name, - LastLoginIp = copyCustomerAccount.LastLoginIp, - LastLoginTime = copyCustomerAccount.LastLoginTime, - Status = copyCustomerAccount.Status, - UserToken = copyCustomerAccount.UserToken, - RequiresTwoFactor = false, - UsedRecoveryCodeLogin = usedRecoveryCode - }, + Data = responseResult, }; } @@ -284,14 +280,14 @@ namespace EOM.TSHotelManagement.Service var customerResult = customerRepository.Insert(new Customer { CustomerNumber = customerNumber, - CustomerName = string.Empty, - CustomerGender = 0, + Name = string.Empty, + Gender = 0, CustomerType = 0, - CustomerPhoneNumber = string.Empty, - CustomerAddress = string.Empty, + PhoneNumber = string.Empty, + Address = string.Empty, DateOfBirth = DateOnly.MinValue, IdCardNumber = string.Empty, - PassportId = 0, + IdCardType = 0, IsDelete = 0, DataInsUsr = readCustomerAccountInputDto.Account, DataInsDate = DateTime.Now @@ -319,13 +315,12 @@ namespace EOM.TSHotelManagement.Service } // 绑定客户到客户组角色 - if (!userRoleRepository.AsQueryable().Any(ur => ur.UserNumber == customerNumber && ur.RoleNumber == customerRoleNumber && ur.IsDelete != 1)) + if (!userRoleRepository.AsQueryable().Any(ur => ur.UserNumber == customerNumber && ur.RoleNumber == customerRoleNumber)) { userRoleRepository.Insert(new UserRole { UserNumber = customerNumber, RoleNumber = customerRoleNumber, - IsDelete = 0, DataInsUsr = readCustomerAccountInputDto.Account, DataInsDate = DateTime.Now }); @@ -336,7 +331,6 @@ namespace EOM.TSHotelManagement.Service new Claim(ClaimTypes.Name, customerAccount.Name), new Claim(ClaimTypes.SerialNumber, customerNumber), new Claim("account", customerAccount.Account), - new Claim(ClaimTypes.UserData, JsonSerializer.Serialize(customerAccount)), }), 10080); // 7天有效期 scope.Complete(); diff --git a/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs b/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs index ef10f0c3b96c7d885fe4ac1cd3db9821dc8c859e..36605721f785c53883acbf363fda6fd7981912d4 100644 --- a/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs +++ b/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs @@ -22,6 +22,7 @@ * */ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; @@ -29,79 +30,13 @@ using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; using SqlSugar; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.Business.Customer { /// /// 客户信息接口实现类 /// - public class CustomerService : ICustomerService + public class CustomerService(GenericRepository custoRepository, GenericRepository roomRepository, GenericRepository spendRepository, GenericRepository passPortTypeRepository, GenericRepository custoTypeRepository, GenericRepository roleRepository, GenericRepository userRoleRepository, DataProtectionHelper dataProtector, ILogger logger) : ICustomerService { - /// - /// 客户信息 - /// - private readonly GenericRepository custoRepository; - - /// - /// 房间信息 - /// - private readonly GenericRepository roomRepository; - - /// - /// 消费情况 - /// - private readonly GenericRepository spendRepository; - - /// - /// 证件类型 - /// - private readonly GenericRepository passPortTypeRepository; - - /// - /// 客户类型 - /// - private readonly GenericRepository custoTypeRepository; - - /// - /// 角色仓储(用于客户组角色) - /// - private readonly GenericRepository roleRepository; - - /// - /// 用户-角色映射仓储(用于绑定客户至客户组) - /// - private readonly GenericRepository userRoleRepository; - - /// - /// 数据保护工具 - /// - private readonly DataProtectionHelper dataProtector; - - private readonly ILogger logger; - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public CustomerService(GenericRepository custoRepository, GenericRepository roomRepository, GenericRepository spendRepository, GenericRepository passPortTypeRepository, GenericRepository custoTypeRepository, GenericRepository roleRepository, GenericRepository userRoleRepository, DataProtectionHelper dataProtector, ILogger logger) - { - this.custoRepository = custoRepository; - this.roomRepository = roomRepository; - this.spendRepository = spendRepository; - this.passPortTypeRepository = passPortTypeRepository; - this.custoTypeRepository = custoTypeRepository; - this.roleRepository = roleRepository; - this.userRoleRepository = userRoleRepository; - this.dataProtector = dataProtector; - this.logger = logger; - } /// /// 添加客户信息 @@ -110,16 +45,16 @@ namespace EOM.TSHotelManagement.Service public BaseResponse InsertCustomerInfo(CreateCustomerInputDto custo) { string NewID = dataProtector.EncryptCustomerData(custo.IdCardNumber); - string NewTel = dataProtector.EncryptCustomerData(custo.CustomerPhoneNumber); + string NewTel = dataProtector.EncryptCustomerData(custo.PhoneNumber); custo.IdCardNumber = NewID; - custo.CustomerPhoneNumber = NewTel; + custo.PhoneNumber = NewTel; try { if (custoRepository.IsAny(a => a.CustomerNumber == custo.CustomerNumber)) { return new BaseResponse() { Message = LocalizationHelper.GetLocalizedString("customer number already exist.", "客户编号已存在"), Code = BusinessStatusCode.InternalServerError }; } - var customer = EntityMapper.Map(custo); + var customer = EntityMapper.Map(custo); var result = custoRepository.Insert(customer); if (!result) { @@ -145,13 +80,12 @@ namespace EOM.TSHotelManagement.Service } // 绑定客户到客户组角色 - if (!userRoleRepository.AsQueryable().Any(ur => ur.UserNumber == customer.CustomerNumber && ur.RoleNumber == customerRoleNumber && ur.IsDelete != 1)) + if (!userRoleRepository.AsQueryable().Any(ur => ur.UserNumber == customer.CustomerNumber && ur.RoleNumber == customerRoleNumber)) { userRoleRepository.Insert(new UserRole { UserNumber = customer.CustomerNumber, RoleNumber = customerRoleNumber, - IsDelete = 0, DataInsUsr = customer.DataInsUsr, DataInsDate = DateTime.Now }); @@ -174,16 +108,16 @@ namespace EOM.TSHotelManagement.Service public BaseResponse UpdCustomerInfo(UpdateCustomerInputDto custo) { string NewID = dataProtector.EncryptCustomerData(custo.IdCardNumber); - string NewTel = dataProtector.EncryptCustomerData(custo.CustomerPhoneNumber); + string NewTel = dataProtector.EncryptCustomerData(custo.PhoneNumber); custo.IdCardNumber = NewID; - custo.CustomerPhoneNumber = NewTel; + custo.PhoneNumber = NewTel; try { if (!custoRepository.IsAny(a => a.CustomerNumber == custo.CustomerNumber)) { return new BaseResponse() { Message = LocalizationHelper.GetLocalizedString("customer number does not exist.", "客户编号不存在"), Code = BusinessStatusCode.InternalServerError }; } - var customer = EntityMapper.Map(custo); + var customer = EntityMapper.Map(custo); var result = custoRepository.Update(customer); if (!result) { @@ -233,19 +167,21 @@ namespace EOM.TSHotelManagement.Service return BaseResponseFactory.ConcurrencyConflict(); } - var occupied = Convert.ToInt32(RoomState.Occupied); - foreach (var customer in customers) - { - var isOccupied = roomRepository.IsAny(a => a.CustomerNumber == customer.CustomerNumber && a.RoomStateId == occupied); - var haveUnSettle = spendRepository.IsAny(a => a.CustomerNumber == customer.CustomerNumber && a.SettlementStatus == ConsumptionConstant.UnSettle.Code); + var customerNumbers = customers.Select(c => c.CustomerNumber).ToList(); + var occupiedState = Convert.ToInt32(RoomState.Occupied); - if (isOccupied) - return new BaseResponse(BusinessStatusCode.InternalServerError, - string.Format(LocalizationHelper.GetLocalizedString("Customer {0} is currently occupying a room", "客户{0}当前正在占用房间"), customer.CustomerNumber)); + var occupiedCustomer = roomRepository.GetFirst(a => customerNumbers.Contains(a.CustomerNumber) && a.RoomStateId == occupiedState); + if (occupiedCustomer != null) + { + return new BaseResponse(BusinessStatusCode.InternalServerError, + string.Format(LocalizationHelper.GetLocalizedString("Customer {0} is currently occupying a room", "客户{0}当前正在占用房间"), occupiedCustomer.CustomerNumber)); + } - if (haveUnSettle) - return new BaseResponse(BusinessStatusCode.InternalServerError, - string.Format(LocalizationHelper.GetLocalizedString("Customer {0} has unsettled bills", "客户{0}有未结算的账单"), customer.CustomerNumber)); + var unsettledCustomer = spendRepository.GetFirst(a => customerNumbers.Contains(a.CustomerNumber) && a.SettlementStatus == ConsumptionConstant.UnSettle.Code); + if (unsettledCustomer != null) + { + return new BaseResponse(BusinessStatusCode.InternalServerError, + string.Format(LocalizationHelper.GetLocalizedString("Customer {0} has unsettled bills", "客户{0}有未结算的账单"), unsettledCustomer.CustomerNumber)); } // 批量软删除 @@ -253,9 +189,9 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Delete Customer Success", "客户信息删除成功")); } - catch (Exception) + catch (Exception ex) { - logger.LogError("Error deleting customer information for customer IDs {CustomerIds}", string.Join(", ", custo.DelIds.Select(x => x.Id))); + logger.LogError(ex, "Error deleting customer information for customer IDs {CustomerIds}", string.Join(", ", custo.DelIds.Select(x => x.Id))); return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Delete Customer Failed", "客户信息删除失败")); } } @@ -302,8 +238,8 @@ namespace EOM.TSHotelManagement.Service { readCustomerInputDto ??= new ReadCustomerInputDto(); - var where = SqlFilterBuilder.BuildExpression(readCustomerInputDto, nameof(Customer.DateOfBirth)); - var query = custoRepository.AsQueryable().Where(a => a.IsDelete != 1); + var where = SqlFilterBuilder.BuildExpression(readCustomerInputDto, nameof(Domain.Customer.DateOfBirth)); + var query = custoRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { @@ -313,7 +249,7 @@ namespace EOM.TSHotelManagement.Service query = query.OrderBy(a => a.CustomerNumber); var count = 0; - List custos; + List custos; if (!readCustomerInputDto.IgnorePaging) { var page = readCustomerInputDto.Page > 0 ? readCustomerInputDto.Page : 1; @@ -334,14 +270,13 @@ namespace EOM.TSHotelManagement.Service .GroupBy(a => a.CustomerType) .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.CustomerTypeName ?? ""); - var helper = new EnumHelper(); var genderMap = Enum.GetValues(typeof(GenderType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToDictionary(x => x.Id, x => x.Description ?? ""); @@ -352,60 +287,13 @@ namespace EOM.TSHotelManagement.Service var dtoArray = new ReadCustomerOutputDto[custos.Count]; System.Threading.Tasks.Parallel.For(0, custos.Count, i => { - var source = custos[i]; - dtoArray[i] = new ReadCustomerOutputDto - { - Id = source.Id, - CustomerNumber = source.CustomerNumber, - CustomerName = source.CustomerName, - CustomerGender = source.CustomerGender, - PassportId = source.PassportId, - GenderName = genderMap.TryGetValue(source.CustomerGender ?? 0, out var genderName) ? genderName : "", - CustomerPhoneNumber = dataProtector.SafeDecryptCustomerData(source.CustomerPhoneNumber), - DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), - CustomerType = source.CustomerType, - CustomerTypeName = custoTypeMap.TryGetValue(source.CustomerType, out var customerTypeName) ? customerTypeName : "", - PassportName = passPortTypeMap.TryGetValue(source.PassportId, out var passportName) ? passportName : "", - IdCardNumber = dataProtector.SafeDecryptCustomerData(source.IdCardNumber), - CustomerAddress = source.CustomerAddress ?? "", - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }; - }); + dtoArray[i] = MapToCustomerOutputDto(custos[i], genderMap, custoTypeMap, passPortTypeMap); + }); customerOutputDtos = dtoArray.ToList(); } else { - customerOutputDtos = new List(custos.Count); - custos.ForEach(source => - { - customerOutputDtos.Add(new ReadCustomerOutputDto - { - Id = source.Id, - CustomerNumber = source.CustomerNumber, - CustomerName = source.CustomerName, - CustomerGender = source.CustomerGender, - PassportId = source.PassportId, - GenderName = genderMap.TryGetValue(source.CustomerGender ?? 0, out var genderName) ? genderName : "", - CustomerPhoneNumber = dataProtector.SafeDecryptCustomerData(source.CustomerPhoneNumber), - DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), - CustomerType = source.CustomerType, - CustomerTypeName = custoTypeMap.TryGetValue(source.CustomerType, out var customerTypeName) ? customerTypeName : "", - PassportName = passPortTypeMap.TryGetValue(source.PassportId, out var passportName) ? passportName : "", - IdCardNumber = dataProtector.SafeDecryptCustomerData(source.IdCardNumber), - CustomerAddress = source.CustomerAddress ?? "", - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }); - }); + customerOutputDtos = custos.Select(source => MapToCustomerOutputDto(source, genderMap, custoTypeMap, passPortTypeMap)).ToList(); } return new ListOutputDto @@ -418,6 +306,32 @@ namespace EOM.TSHotelManagement.Service }; } + private ReadCustomerOutputDto MapToCustomerOutputDto(Domain.Customer source, Dictionary genderMap, Dictionary custoTypeMap, Dictionary passPortTypeMap) + { + return new ReadCustomerOutputDto + { + Id = source.Id, + CustomerNumber = source.CustomerNumber, + Name = source.Name, + Gender = source.Gender, + IdCardType = source.IdCardType, + GenderName = genderMap.TryGetValue(source.Gender, out var genderName) ? genderName : "", + PhoneNumber = dataProtector.SafeDecryptCustomerData(source.PhoneNumber), + DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), + CustomerType = source.CustomerType, + CustomerTypeName = custoTypeMap.TryGetValue(source.CustomerType, out var customerTypeName) ? customerTypeName : "", + PassportName = passPortTypeMap.TryGetValue(source.IdCardType, out var passportName) ? passportName : "", + IdCardNumber = dataProtector.SafeDecryptCustomerData(source.IdCardNumber), + Address = source.Address ?? "", + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + } + /// /// 查询指定客户信息 /// @@ -425,14 +339,13 @@ namespace EOM.TSHotelManagement.Service public SingleOutputDto SelectCustoByInfo(ReadCustomerInputDto custo) { //查询出所有性别类型 - var helper = new EnumHelper(); var genderMap = Enum.GetValues(typeof(GenderType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToDictionary(x => x.Id, x => x.Description ?? ""); //查询出所有证件类型 @@ -446,26 +359,26 @@ namespace EOM.TSHotelManagement.Service //查询出所有客户信息 SingleOutputDto singleOutputDto = new SingleOutputDto(); - var where = SqlFilterBuilder.BuildExpression(custo); + var where = SqlFilterBuilder.BuildExpression(custo); var customer = custoRepository.AsQueryable().Where(where.ToExpression()).Single(); - if (customer.IsNullOrEmpty()) + if (customer == null) { - return new SingleOutputDto { Code = BusinessStatusCode.InternalServerError, Message = "该用户不存在" }; + return new SingleOutputDto { Code = BusinessStatusCode.NotFound, Message = "该用户不存在" }; } + singleOutputDto.Data = EntityMapper.Map(customer); + //解密身份证号码/联系方式(失败时回退原值) - customer.IdCardNumber = dataProtector.SafeDecryptCustomerData(customer.IdCardNumber); - customer.CustomerPhoneNumber = dataProtector.SafeDecryptCustomerData(customer.CustomerPhoneNumber); + singleOutputDto.Data.IdCardNumber = dataProtector.SafeDecryptCustomerData(customer.IdCardNumber); + singleOutputDto.Data.PhoneNumber = dataProtector.SafeDecryptCustomerData(customer.PhoneNumber); //性别类型 - customer.GenderName = genderMap.TryGetValue((int)customer.CustomerGender!, out var genderName) ? genderName : ""; + singleOutputDto.Data.GenderName = genderMap.TryGetValue((int)customer.Gender!, out var genderName) ? genderName : ""; //证件类型 - customer.PassportName = passPortTypeMap.TryGetValue(customer.PassportId, out var passportName) ? passportName : ""; + singleOutputDto.Data.PassportName = passPortTypeMap.TryGetValue(customer.IdCardType, out var passportName) ? passportName : ""; //客户类型 - customer.CustomerTypeName = custoTypeMap.TryGetValue(customer.CustomerType, out var customerTypeName) ? customerTypeName : ""; - - singleOutputDto.Data = EntityMapper.Map(customer); + singleOutputDto.Data.CustomerTypeName = custoTypeMap.TryGetValue(customer.CustomerType, out var customerTypeName) ? customerTypeName : ""; return singleOutputDto; } diff --git a/EOM.TSHotelManagement.Service/Business/Customer/Permission/CustomerPermissionService.cs b/EOM.TSHotelManagement.Service/Business/Customer/Permission/CustomerPermissionService.cs index 02fa017dbe4b4a772e080dc501452a96b1ac901e..421b42a33ba8b15f30e1c1ad39cba53da6442782 100644 --- a/EOM.TSHotelManagement.Service/Business/Customer/Permission/CustomerPermissionService.cs +++ b/EOM.TSHotelManagement.Service/Business/Customer/Permission/CustomerPermissionService.cs @@ -19,6 +19,7 @@ namespace EOM.TSHotelManagement.Service private readonly GenericRepository userRoleRepository; private readonly GenericRepository rolePermissionRepository; private readonly GenericRepository permissionRepository; + private readonly GenericRepository menuRepository; private readonly GenericRepository roleRepository; public CustomerPermissionService( @@ -26,12 +27,14 @@ namespace EOM.TSHotelManagement.Service GenericRepository userRoleRepository, GenericRepository rolePermissionRepository, GenericRepository permissionRepository, + GenericRepository menuRepository, GenericRepository roleRepository) { this.customerRepository = customerRepository; this.userRoleRepository = userRoleRepository; this.rolePermissionRepository = rolePermissionRepository; this.permissionRepository = permissionRepository; + this.menuRepository = menuRepository; this.roleRepository = roleRepository; } @@ -53,15 +56,15 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("User not found", "用户不存在")); } - // 软删除当前用户下所有有效的角色绑定 + // 硬删除当前用户下所有有效的角色绑定 var existing = userRoleRepository.AsQueryable() - .Where(x => x.UserNumber == input.UserNumber && x.IsDelete != 1) - .Select(x => new UserRole { UserNumber = x.UserNumber, RoleNumber = x.RoleNumber, IsDelete = 1 }) + .Where(x => x.UserNumber == input.UserNumber) + .Select(x => new UserRole { UserNumber = x.UserNumber, RoleNumber = x.RoleNumber }) .ToList(); foreach (var ur in existing) { - userRoleRepository.SoftDelete(ur); + userRoleRepository.Delete(ur); } // 过滤、去重、忽略空白 @@ -77,8 +80,7 @@ namespace EOM.TSHotelManagement.Service var entity = new UserRole { UserNumber = input.UserNumber, - RoleNumber = role, - IsDelete = 0 + RoleNumber = role }; userRoleRepository.Insert(entity); } @@ -108,7 +110,7 @@ namespace EOM.TSHotelManagement.Service try { var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToList(); @@ -158,7 +160,7 @@ namespace EOM.TSHotelManagement.Service { // 1) 用户 -> 角色 var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToList(); @@ -183,8 +185,10 @@ namespace EOM.TSHotelManagement.Service // 2) 角色 -> 权限编码(RolePermission) var rolePermList = rolePermissionRepository.AsQueryable() - .Where(rp => rp.IsDelete != 1 && roleNumbers.Contains(rp.RoleNumber)) - .Select(rp => new { rp.RoleNumber, rp.PermissionNumber }) + .Where(rp => roleNumbers.Contains(rp.RoleNumber) + && rp.PermissionNumber != null + && rp.PermissionNumber != "") + .Select(rp => new { rp.RoleNumber, rp.PermissionNumber, rp.MenuId }) .ToList(); var permNumbers = rolePermList @@ -194,30 +198,61 @@ namespace EOM.TSHotelManagement.Service .ToList(); // 3) 权限详情(Permission) - var permInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); + var permInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); if (permNumbers.Count > 0) { var info = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && permNumbers.Contains(p.PermissionNumber)) - .Select(p => new { p.PermissionNumber, p.PermissionName, p.MenuKey }) + .Where(p => permNumbers.Contains(p.PermissionNumber)) + .Select(p => new { p.PermissionNumber, p.PermissionName }) .ToList(); foreach (var p in info) { - permInfo[p.PermissionNumber] = (p.PermissionName, p.MenuKey ?? string.Empty); + permInfo[p.PermissionNumber] = p.PermissionName; } } // 4) 组装输出 + var menuIds = rolePermList + .Where(x => x.MenuId.HasValue && x.MenuId.Value > 0) + .Select(x => x.MenuId!.Value) + .Distinct() + .ToList(); + + var menuInfo = new Dictionary(); + if (menuIds.Count > 0) + { + var menus = menuRepository.AsQueryable() + .Where(m => m.IsDelete != 1 && menuIds.Contains(m.Id)) + .Select(m => new { m.Id, m.Key, m.Title }) + .ToList(); + + foreach (var m in menus) + { + menuInfo[m.Id] = (m.Key ?? string.Empty, m.Title ?? string.Empty); + } + } + var resultItems = rolePermList.Select(x => { - permInfo.TryGetValue(x.PermissionNumber, out var meta); + permInfo.TryGetValue(x.PermissionNumber!, out var permName); + var menuId = x.MenuId; + var menuKey = string.Empty; + var menuName = string.Empty; + if (menuId.HasValue && menuInfo.TryGetValue(menuId.Value, out var menuMeta)) + { + menuKey = menuMeta.Key; + menuName = menuMeta.Name; + } + return new UserRolePermissionOutputDto { RoleNumber = x.RoleNumber, - PermissionNumber = x.PermissionNumber, - PermissionName = meta.Name, - MenuKey = meta.MenuKey + PermissionNumber = x.PermissionNumber!, + PermissionName = permName, + MenuId = menuId, + MenuKey = menuKey, + MenuName = menuName }; }).ToList(); @@ -281,15 +316,13 @@ namespace EOM.TSHotelManagement.Service roleRepository.Insert(role); } - // 软删除当前专属角色下所有有效的角色-权限绑定 + // 硬删除当前专属角色下所有有效的角色-权限绑定 var existing = rolePermissionRepository.AsQueryable() - .Where(x => x.RoleNumber == userRoleNumber && x.IsDelete != 1) - .Select(x => new RolePermission { RoleNumber = x.RoleNumber, PermissionNumber = x.PermissionNumber, IsDelete = 1 }) + .Where(x => x.RoleNumber == userRoleNumber) .ToList(); - - foreach (var rp in existing) + if (existing.Count > 0) { - rolePermissionRepository.SoftDelete(rp); + rolePermissionRepository.Delete(existing); } // 过滤、去重、忽略空白 @@ -303,7 +336,7 @@ namespace EOM.TSHotelManagement.Service { // 仅保留系统中存在的权限码 var validPerms = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && perms.Contains(p.PermissionNumber)) + .Where(p => perms.Contains(p.PermissionNumber)) .Select(p => p.PermissionNumber) .ToList(); @@ -312,22 +345,20 @@ namespace EOM.TSHotelManagement.Service var entity = new RolePermission { RoleNumber = userRoleNumber, - PermissionNumber = pnum, - IsDelete = 0 + PermissionNumber = pnum }; rolePermissionRepository.Insert(entity); } } // 确保用户与专属角色绑定存在 - var hasBinding = userRoleRepository.IsAny(ur => ur.UserNumber == input.UserNumber && ur.RoleNumber == userRoleNumber && ur.IsDelete != 1); + var hasBinding = userRoleRepository.IsAny(ur => ur.UserNumber == input.UserNumber && ur.RoleNumber == userRoleNumber); if (!hasBinding) { userRoleRepository.Insert(new UserRole { UserNumber = input.UserNumber, - RoleNumber = userRoleNumber, - IsDelete = 0 + RoleNumber = userRoleNumber }); } @@ -357,7 +388,7 @@ namespace EOM.TSHotelManagement.Service { var roleNumber = $"R-USER-{userNumber}"; var list = rolePermissionRepository.AsQueryable() - .Where(rp => rp.RoleNumber == roleNumber && rp.IsDelete != 1) + .Where(rp => rp.RoleNumber == roleNumber) .Select(rp => rp.PermissionNumber) .ToList(); @@ -381,4 +412,4 @@ namespace EOM.TSHotelManagement.Service } } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs b/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs index 09b7d43600ff470f018fc574968e52da1ceac2b2..1e21a0ed93de98cb0ab6f4fbe4f2915c3a793a48 100644 --- a/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs +++ b/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs @@ -1,169 +1,161 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - */ using EOM.TSHotelManagement.Common; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; +using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; namespace EOM.TSHotelManagement.Service { - /// - /// 水电信息接口实现类 - /// public class EnergyManagementService : IEnergyManagementService { - /// - /// 水电信息 - /// private readonly GenericRepository wtiRepository; - private readonly ILogger _logger; + private readonly GenericRepository roomRepository; + private readonly ILogger logger; - public EnergyManagementService(GenericRepository wtiRepository, ILogger logger) + public EnergyManagementService( + GenericRepository wtiRepository, + GenericRepository roomRepository, + ILogger logger) { this.wtiRepository = wtiRepository; - _logger = logger; + this.roomRepository = roomRepository; + this.logger = logger; } - /// - /// 根据条件查询水电费信息 - /// - /// Dto - /// 符合条件的水电费信息列表 public ListOutputDto SelectEnergyManagementInfo(ReadEnergyManagementInputDto readEnergyManagementInputDto) { - var where = SqlFilterBuilder.BuildExpression(readEnergyManagementInputDto); + readEnergyManagementInputDto ??= new ReadEnergyManagementInputDto(); + var filterInput = CreateEnergyFilter(readEnergyManagementInputDto); - var count = 0; - var Data = new List(); + var where = SqlFilterBuilder.BuildExpression(filterInput); + var query = wtiRepository.AsQueryable(); + var whereExpression = where.ToExpression(); + if (whereExpression != null) + { + query = query.Where(whereExpression); + } + + if (readEnergyManagementInputDto.RoomId.HasValue && readEnergyManagementInputDto.RoomId.Value > 0) + { + query = query.Where(a => a.RoomId == readEnergyManagementInputDto.RoomId.Value); + } + var count = 0; + List data; if (readEnergyManagementInputDto.IgnorePaging) { - Data = wtiRepository.AsQueryable() - .Where(where.ToExpression()).ToList(); - count = Data.Count; + data = query.ToList(); + count = data.Count; } else { - Data = wtiRepository.AsQueryable() - .Where(where.ToExpression()).ToPageList(readEnergyManagementInputDto.Page, readEnergyManagementInputDto.PageSize, ref count); + var page = readEnergyManagementInputDto.Page > 0 ? readEnergyManagementInputDto.Page : 1; + var pageSize = readEnergyManagementInputDto.PageSize > 0 ? readEnergyManagementInputDto.PageSize : 15; + data = query.ToPageList(page, pageSize, ref count); } - var readEnergies = EntityMapper.MapList(Data); + var rooms = RoomReferenceHelper.LoadRooms(roomRepository, data.Select(a => a.RoomId), data.Select(a => a.RoomNumber)); + var outputs = data.Select(a => MapEnergyToOutput(a, RoomReferenceHelper.FindRoom(rooms, a.RoomId, a.RoomNumber))).ToList(); return new ListOutputDto { Data = new PagedData { - Items = readEnergies, + Items = outputs, TotalCount = count } }; } - /// - /// 添加水电费信息 - /// - /// - /// - public BaseResponse InsertEnergyManagementInfo(CreateEnergyManagementInputDto w) + public BaseResponse InsertEnergyManagementInfo(CreateEnergyManagementInputDto input) { try { - if (wtiRepository.IsAny(a => a.InformationId == w.InformationNumber)) + if (wtiRepository.IsAny(a => a.InformationId == input.InformationNumber && a.IsDelete != 1)) { - return new BaseResponse() { Message = LocalizationHelper.GetLocalizedString("information number already exist.", "信息编号已存在"), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse(BusinessStatusCode.Conflict, "Information number already exists."); } - var result = wtiRepository.Insert(EntityMapper.Map(w)); + + var roomResult = RoomReferenceHelper.Resolve(roomRepository, input?.RoomId, input?.RoomNumber); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, input?.RoomId, input?.RoomNumber); + } + + var entity = EntityMapper.Map(input); + entity.InformationId = input.InformationNumber; + entity.RoomId = roomResult.Room.Id; + entity.RoomNumber = roomResult.Room.RoomNumber; + entity.StartDate = input.StartDate; + entity.EndDate = input.EndDate; + + wtiRepository.Insert(entity); + return new BaseResponse(BusinessStatusCode.Success, "Insert energy management success."); } catch (Exception ex) { - _logger.LogError(ex, LocalizationHelper.GetLocalizedString("Insert Energy Management Failed", "水电信息添加失败")); - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Insert Energy Management Failed", "水电信息添加失败")); + logger.LogError(ex, "Insert energy management failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Insert energy management failed."); } - - return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Insert Energy Management Success", "水电信息添加成功")); } - /// - /// 修改水电费信息 - /// - /// 包含要修改的数据,以及EnergyManagementNo作为查询条件 - /// - public BaseResponse UpdateEnergyManagementInfo(UpdateEnergyManagementInputDto w) + public BaseResponse UpdateEnergyManagementInfo(UpdateEnergyManagementInputDto input) { try { - if (!wtiRepository.IsAny(a => a.InformationId == w.InformationId)) + var entity = wtiRepository.GetFirst(a => a.InformationId == input.InformationId && a.IsDelete != 1); + if (entity == null) { - return new BaseResponse() { Message = LocalizationHelper.GetLocalizedString("information number does not exist.", "信息编号不存在"), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse(BusinessStatusCode.NotFound, "Information number does not exist."); } - var result = wtiRepository.Update(EntityMapper.Map(w)); - if (result) + var roomResult = RoomReferenceHelper.Resolve(roomRepository, input?.RoomId, input?.RoomNumber); + if (roomResult.Room == null) { - return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Update Energy Management Success", "水电费信息更新成功")); - } - else - { - return BaseResponseFactory.ConcurrencyConflict(); + return CreateRoomLookupFailure(roomResult, input?.RoomId, input?.RoomNumber); } + + entity.RoomId = roomResult.Room.Id; + entity.RoomNumber = roomResult.Room.RoomNumber; + entity.CustomerNumber = input.CustomerNumber; + entity.StartDate = input.StartDate; + entity.EndDate = input.EndDate; + entity.PowerUsage = input.PowerUsage; + entity.WaterUsage = input.WaterUsage; + entity.Recorder = input.Recorder; + entity.DataChgUsr = input.DataChgUsr; + entity.DataChgDate = input.DataChgDate; + entity.RowVersion = input.RowVersion ?? 0; + + return wtiRepository.Update(entity) + ? new BaseResponse(BusinessStatusCode.Success, "Update energy management success.") + : BaseResponseFactory.ConcurrencyConflict(); } catch (Exception ex) { - _logger.LogError(ex, LocalizationHelper.GetLocalizedString("Update Energy Management Failed", "水电费信息更新失败")); - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Update Energy Management Failed", "水电费信息更新失败")); + logger.LogError(ex, "Update energy management failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Update energy management failed."); } } - /// - /// 根据房间编号、使用时间删除水电费信息 - /// - /// public BaseResponse DeleteEnergyManagementInfo(DeleteEnergyManagementInputDto hydroelectricity) { try { if (hydroelectricity?.DelIds == null || !hydroelectricity.DelIds.Any()) { - return new BaseResponse - { - Code = BusinessStatusCode.BadRequest, - Message = LocalizationHelper.GetLocalizedString("Parameters Invalid", "参数错误") - }; + return new BaseResponse(BusinessStatusCode.BadRequest, "Parameters invalid."); } var delIds = DeleteConcurrencyHelper.GetDeleteIds(hydroelectricity); var energyManagements = wtiRepository.GetList(a => delIds.Contains(a.Id)); - if (!energyManagements.Any()) { - return new BaseResponse - { - Code = BusinessStatusCode.NotFound, - Message = LocalizationHelper.GetLocalizedString("Energy Management Information Not Found", "水电费信息未找到") - }; + return new BaseResponse(BusinessStatusCode.NotFound, "Energy management information not found."); } if (DeleteConcurrencyHelper.HasDeleteConflict(hydroelectricity, energyManagements, a => a.Id, a => a.RowVersion)) @@ -171,22 +163,71 @@ namespace EOM.TSHotelManagement.Service return BaseResponseFactory.ConcurrencyConflict(); } - var result = wtiRepository.SoftDeleteRange(energyManagements); - - if (result) - { - return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Delete Energy Management Success", "水电费信息删除成功")); - } - else - { - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Delete Energy Management Failed", "水电费信息删除失败")); - } + return wtiRepository.SoftDeleteRange(energyManagements) + ? new BaseResponse(BusinessStatusCode.Success, "Delete energy management success.") + : new BaseResponse(BusinessStatusCode.InternalServerError, "Delete energy management failed."); } catch (Exception ex) { - _logger.LogError(ex, LocalizationHelper.GetLocalizedString("Delete Energy Management Failed", "水电费信息删除失败")); - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Delete Energy Management Failed", "水电费信息删除失败")); + logger.LogError(ex, "Delete energy management failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Delete energy management failed."); } } + + private static ReadEnergyManagementOutputDto MapEnergyToOutput(EnergyManagement source, Room room) + { + return new ReadEnergyManagementOutputDto + { + Id = source.Id, + InformationId = source.InformationId, + RoomId = source.RoomId ?? room?.Id, + RoomNumber = room?.RoomNumber ?? source.RoomNumber, + RoomArea = RoomReferenceHelper.GetRoomArea(room), + RoomFloor = RoomReferenceHelper.GetRoomFloor(room), + RoomLocator = RoomReferenceHelper.GetRoomLocator(room), + CustomerNumber = source.CustomerNumber, + StartDate = source.StartDate, + EndDate = source.EndDate, + PowerUsage = source.PowerUsage, + WaterUsage = source.WaterUsage, + Recorder = source.Recorder, + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + } + + private static BaseResponse CreateRoomLookupFailure(RoomResolveResult result, int? roomId, string roomNumber) + { + if (!roomId.HasValue || roomId.Value <= 0) + { + return new BaseResponse(BusinessStatusCode.BadRequest, "RoomId is required."); + } + + if (result?.IsAmbiguous == true) + { + return new BaseResponse(BusinessStatusCode.Conflict, $"Multiple rooms match room id '{roomId.Value}'."); + } + + return new BaseResponse(BusinessStatusCode.NotFound, $"RoomId '{roomId.Value}' was not found."); + } + + private static ReadEnergyManagementInputDto CreateEnergyFilter(ReadEnergyManagementInputDto input) + { + return new ReadEnergyManagementInputDto + { + Page = input.Page, + PageSize = input.PageSize, + IgnorePaging = input.IgnorePaging, + CustomerNumber = input.CustomerNumber, + RoomId = null, + RoomNumber = null, + StartDate = input.StartDate, + EndDate = input.EndDate + }; + } } } diff --git a/EOM.TSHotelManagement.Service/Business/News/NewsService.cs b/EOM.TSHotelManagement.Service/Business/News/NewsService.cs index fa5322c0e54275b9840ace79edfcbdef4304dd1e..bd998d1fcd0ff39a09444c2dc8ac5066dd9e173d 100644 --- a/EOM.TSHotelManagement.Service/Business/News/NewsService.cs +++ b/EOM.TSHotelManagement.Service/Business/News/NewsService.cs @@ -1,22 +1,15 @@ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.Business.News { - public class NewsService : INewsService + public class NewsService(GenericRepository _newsRepository, ILogger _logger) : INewsService { - private readonly GenericRepository _newsRepository; - private readonly ILogger _logger; - - public NewsService(GenericRepository newsRepository, ILogger logger) - { - _newsRepository = newsRepository; - _logger = logger; - } /// /// 查询新闻列表 @@ -25,14 +18,13 @@ namespace EOM.TSHotelManagement.Service /// public ListOutputDto SelectNews(ReadNewsInputDto readNewsInputDto) { - var helper = new EnumHelper(); var newsTypes = Enum.GetValues(typeof(NewsType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); @@ -42,14 +34,14 @@ namespace EOM.TSHotelManagement.Service { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); - var where = SqlFilterBuilder.BuildExpression(readNewsInputDto); + var where = SqlFilterBuilder.BuildExpression(readNewsInputDto); var count = 0; - var newsList = new List(); + var newsList = new List(); if (readNewsInputDto.IgnorePaging) { @@ -61,7 +53,7 @@ namespace EOM.TSHotelManagement.Service newsList = _newsRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readNewsInputDto.Page, readNewsInputDto.PageSize, ref count); } - var mappedList = EntityMapper.MapList(newsList); + var mappedList = EntityMapper.MapList(newsList); mappedList.ForEach(source => { @@ -98,15 +90,14 @@ namespace EOM.TSHotelManagement.Service }; } - var outputDto = EntityMapper.Map(news); - var helper = new EnumHelper(); + var outputDto = EntityMapper.Map(news); var newsType = Enum.GetValues(typeof(NewsType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .SingleOrDefault(a => a.Name == outputDto.NewsType); outputDto.NewsTypeDescription = newsType?.Description ?? ""; @@ -116,7 +107,7 @@ namespace EOM.TSHotelManagement.Service { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .SingleOrDefault(a => a.Name == outputDto.NewsStatus); outputDto.NewsStatusDescription = newsStatus?.Description ?? ""; @@ -134,7 +125,7 @@ namespace EOM.TSHotelManagement.Service /// public BaseResponse AddNews(AddNewsInputDto addNewsInputDto) { - var news = new News + var news = new Domain.News { NewId = Guid.NewGuid().ToString(), NewsTitle = addNewsInputDto.NewsTitle, diff --git a/EOM.TSHotelManagement.Service/Business/PromotionContent/PromotionContentService.cs b/EOM.TSHotelManagement.Service/Business/PromotionContent/PromotionContentService.cs index 025f1ba2883d13e1b312c16f2ceb9af40c2ae69f..99ac7196e0620aee02e9c53a615eb83a5f4ffcbf 100644 --- a/EOM.TSHotelManagement.Service/Business/PromotionContent/PromotionContentService.cs +++ b/EOM.TSHotelManagement.Service/Business/PromotionContent/PromotionContentService.cs @@ -56,7 +56,7 @@ namespace EOM.TSHotelManagement.Service var count = 0; var where = SqlFilterBuilder.BuildExpression(readPromotionContentInputDto); - var query = fontsRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = fontsRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { diff --git a/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs index 888c7e0eb59d27c639973223fcece540b03c14a2..4a6d4bd4da1d33719c8ee2ac7069137395825e9e 100644 --- a/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs +++ b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs @@ -1,422 +1,308 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - */ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; using Microsoft.Extensions.Logging; -using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; using System.Transactions; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.Business.Reser { - /// - /// 预约信息接口实现类 - /// - public class ReserService : IReserService + public class ReserService( + GenericRepository reserRepository, + GenericRepository roomRepository, + DataProtectionHelper dataProtector, + ILogger logger) : IReserService { - /// - /// 预约信息 - /// - private readonly GenericRepository reserRepository; - /// - /// 房间信息 - /// - private readonly GenericRepository roomRepository; - - /// - /// 数据保护 - /// - private readonly DataProtectionHelper dataProtector; - - private readonly ILogger logger; - - public ReserService(GenericRepository reserRepository, GenericRepository roomRepository, DataProtectionHelper dataProtector, ILogger logger) - { - this.reserRepository = reserRepository; - this.roomRepository = roomRepository; - this.dataProtector = dataProtector; - this.logger = logger; - } - - /// - /// 获取所有预约信息 - /// - /// public ListOutputDto SelectReserAll(ReadReserInputDto readReserInputDto) { readReserInputDto ??= new ReadReserInputDto(); + var filterInput = CreateReservationFilter(readReserInputDto); - var helper = new EnumHelper(); var reserTypeMap = Enum.GetValues(typeof(ReserType)) .Cast() - .ToDictionary(e => e.ToString(), e => helper.GetEnumDescription(e) ?? "", StringComparer.OrdinalIgnoreCase); + .ToDictionary(a => a.ToString(), a => EnumHelper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var where = SqlFilterBuilder.BuildExpression(readReserInputDto, new Dictionary + var where = SqlFilterBuilder.BuildExpression(filterInput, new Dictionary { - { nameof(ReadReserInputDto.ReservationStartDateStart), nameof(Reser.ReservationStartDate) }, - { nameof(ReadReserInputDto.ReservationStartDateEnd), nameof(Reser.ReservationEndDate) }, + { nameof(ReadReserInputDto.ReservationStartDateStart), nameof(Domain.Reser.ReservationStartDate) }, + { nameof(ReadReserInputDto.ReservationStartDateEnd), nameof(Domain.Reser.ReservationEndDate) } }); - var query = reserRepository.AsQueryable().Where(a => a.IsDelete != 1); + + var query = reserRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { query = query.Where(whereExpression); } - var count = 0; - List data; - if (!readReserInputDto.IgnorePaging) + if (readReserInputDto.RoomId.HasValue && readReserInputDto.RoomId.Value > 0) { - var page = readReserInputDto.Page > 0 ? readReserInputDto.Page : 1; - var pageSize = readReserInputDto.PageSize > 0 ? readReserInputDto.PageSize : 15; - data = query.ToPageList(page, pageSize, ref count); - } - else - { - data = query.ToList(); - count = data.Count; + query = query.Where(a => a.RoomId == readReserInputDto.RoomId.Value); } - List mapped; - var useParallelProjection = readReserInputDto.IgnorePaging && data.Count >= 200; - if (useParallelProjection) + var count = 0; + List reservations; + if (readReserInputDto.IgnorePaging) { - var dtoArray = new ReadReserOutputDto[data.Count]; - System.Threading.Tasks.Parallel.For(0, data.Count, i => - { - var source = data[i]; - dtoArray[i] = new ReadReserOutputDto - { - Id = source.Id, - ReservationId = source.ReservationId, - CustomerName = source.CustomerName, - ReservationPhoneNumber = dataProtector.SafeDecryptReserData(source.ReservationPhoneNumber), - ReservationRoomNumber = source.ReservationRoomNumber, - ReservationChannel = source.ReservationChannel, - ReservationChannelDescription = reserTypeMap.TryGetValue(source.ReservationChannel ?? "", out var channelDescription) ? channelDescription : "", - ReservationStartDate = source.ReservationStartDate.ToDateTime(TimeOnly.MinValue), - ReservationEndDate = source.ReservationEndDate.ToDateTime(TimeOnly.MinValue), - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }; - }); - mapped = dtoArray.ToList(); + reservations = query.ToList(); + count = reservations.Count; } else { - mapped = new List(data.Count); - data.ForEach(source => - { - mapped.Add(new ReadReserOutputDto - { - Id = source.Id, - ReservationId = source.ReservationId, - CustomerName = source.CustomerName, - ReservationPhoneNumber = dataProtector.SafeDecryptReserData(source.ReservationPhoneNumber), - ReservationRoomNumber = source.ReservationRoomNumber, - ReservationChannel = source.ReservationChannel, - ReservationChannelDescription = reserTypeMap.TryGetValue(source.ReservationChannel ?? "", out var channelDescription) ? channelDescription : "", - ReservationStartDate = source.ReservationStartDate.ToDateTime(TimeOnly.MinValue), - ReservationEndDate = source.ReservationEndDate.ToDateTime(TimeOnly.MinValue), - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }); - }); + var page = readReserInputDto.Page > 0 ? readReserInputDto.Page : 1; + var pageSize = readReserInputDto.PageSize > 0 ? readReserInputDto.PageSize : 15; + reservations = query.ToPageList(page, pageSize, ref count); } + var rooms = RoomReferenceHelper.LoadRooms(roomRepository, reservations.Select(a => a.RoomId), reservations.Select(a => a.ReservationRoomNumber)); + var outputs = reservations + .Select(a => MapReservationToOutput(a, RoomReferenceHelper.FindRoom(rooms, a.RoomId, a.ReservationRoomNumber), reserTypeMap)) + .ToList(); + return new ListOutputDto { Data = new PagedData { - Items = mapped, + Items = outputs, TotalCount = count } }; } - /// - /// 根据房间编号获取预约信息 - /// - /// - /// public SingleOutputDto SelectReserInfoByRoomNo(ReadReserInputDto readReserInputDt) { - var helper = new EnumHelper(); - var reserType = Enum.GetValues(typeof(ReserType)) + var reserTypeMap = Enum.GetValues(typeof(ReserType)) .Cast() - .Select(e => new EnumDto - { - Id = (int)e, - Name = e.ToString(), - Description = helper.GetEnumDescription(e) - }) - .ToList(); + .ToDictionary(a => a.ToString(), a => EnumHelper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); - Reser res = null; - res = reserRepository.GetFirst(a => a.ReservationRoomNumber == readReserInputDt.ReservationRoomNumber && a.IsDelete != 1); - //解密联系方式 - var sourceTelStr = dataProtector.SafeDecryptReserData(res.ReservationPhoneNumber); - res.ReservationPhoneNumber = sourceTelStr; - - var outputReser = EntityMapper.Map(res); + var query = reserRepository.AsQueryable().Where(a => a.ReservationStatus == 0 && a.IsDelete != 1); + if (readReserInputDt?.RoomId > 0) + { + query = query.Where(a => a.RoomId == readReserInputDt.RoomId); + } + else + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = "RoomId is required.", + Data = new ReadReserOutputDto() + }; + } - outputReser.ReservationChannelDescription = reserType.Where(a => a.Name == outputReser.ReservationChannel).Single().Description; + var reservation = query.First(); + if (reservation == null) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.NotFound, + Message = "Reservation not found.", + Data = new ReadReserOutputDto() + }; + } - return new SingleOutputDto { Data = outputReser }; + var room = RoomReferenceHelper.Resolve(roomRepository, reservation.RoomId, reservation.ReservationRoomNumber).Room; + return new SingleOutputDto + { + Data = MapReservationToOutput(reservation, room, reserTypeMap) + }; } - /// - /// 删除预约信息 - /// - /// - /// public BaseResponse DeleteReserInfo(DeleteReserInputDto reser) { - if (reser?.DelIds == null || !reser.DelIds.Any()) { - return new BaseResponse - { - Code = BusinessStatusCode.BadRequest, - Message = LocalizationHelper.GetLocalizedString("Parameters Invalid", "参数错误") - }; + return new BaseResponse(BusinessStatusCode.BadRequest, "Parameters invalid."); } var delIds = DeleteConcurrencyHelper.GetDeleteIds(reser); - var resers = reserRepository.GetList(a => delIds.Contains(a.Id)); - - if (!resers.Any()) + var reservations = reserRepository.GetList(a => delIds.Contains(a.Id)); + if (!reservations.Any()) { - return new BaseResponse - { - Code = BusinessStatusCode.NotFound, - Message = LocalizationHelper.GetLocalizedString("Reservation Information Not Found", "预约信息未找到") - }; + return new BaseResponse(BusinessStatusCode.NotFound, "Reservation information not found."); } - if (DeleteConcurrencyHelper.HasDeleteConflict(reser, resers, a => a.Id, a => a.RowVersion)) + if (DeleteConcurrencyHelper.HasDeleteConflict(reser, reservations, a => a.Id, a => a.RowVersion)) { return BaseResponseFactory.ConcurrencyConflict(); } try { - using (TransactionScope scope = new TransactionScope()) + using var scope = new TransactionScope(); + if (!reserRepository.SoftDeleteRange(reservations)) { - var roomNumbers = resers.Select(a => a.ReservationRoomNumber).ToList(); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Delete reservation failed."); + } - var result = reserRepository.SoftDeleteRange(resers); + var rooms = RoomReferenceHelper.LoadRooms(roomRepository, reservations.Select(a => a.RoomId), reservations.Select(a => a.ReservationRoomNumber)); + if (rooms.Count > 0) + { + var remainingReservations = reserRepository.AsQueryable() + .Where(a => a.IsDelete != 1 && a.ReservationStatus == 0) + .ToList(); - if (result) + var changedRooms = new List(); + foreach (var room in rooms) { - var rooms = roomRepository.AsQueryable().Where(a => roomNumbers.Contains(a.RoomNumber)).ToList(); - rooms = rooms.Select(a => - { - a.RoomStateId = (int)RoomState.Vacant; - return a; - }).ToList(); - - var roomUpdateResult = roomRepository.UpdateRange(rooms); - if (!roomUpdateResult) + var hasOtherActiveReservation = remainingReservations.Any(a => IsSameRoom(a, room)); + if (!hasOtherActiveReservation) { - return BaseResponseFactory.ConcurrencyConflict(); + room.RoomStateId = (int)RoomState.Vacant; + changedRooms.Add(room); } - - scope.Complete(); - return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Delete Reser Success", "预约信息删除成功")); } - else + + if (changedRooms.Count > 0 && !roomRepository.UpdateRange(changedRooms)) { - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Delete Reser Failed", "预约信息删除失败")); + return BaseResponseFactory.ConcurrencyConflict(); } } + + scope.Complete(); + return new BaseResponse(BusinessStatusCode.Success, "Delete reservation success."); } catch (Exception ex) { - logger.LogError(ex, LocalizationHelper.GetLocalizedString("Delete Reser Failed", "预约信息删除失败")); - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Delete Reser Failed", "预约信息删除失败")); + logger.LogError(ex, "Delete reservation failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Delete reservation failed."); } } - /// - /// 更新预约信息(支持恢复功能) - /// - /// - /// public BaseResponse UpdateReserInfo(UpdateReserInputDto reser) { - string NewTel = dataProtector.EncryptReserData(reser.ReservationPhoneNumber); - reser.ReservationPhoneNumber = NewTel; - try { - using (TransactionScope scope = new TransactionScope()) - { - // 获取原预约(包括软删除的记录) - var originalReser = reserRepository.GetFirst(a => a.Id == reser.Id); + using var scope = new TransactionScope(); - if (originalReser == null) - { - return new BaseResponse(BusinessStatusCode.NotFound, - LocalizationHelper.GetLocalizedString("Reservation not found", "预约信息不存在")); - } - - bool isRestoring = originalReser.IsDelete == 1 && reser.IsDelete == 0; + var originalReser = reserRepository.GetFirst(a => a.Id == reser.Id); + if (originalReser == null) + { + return new BaseResponse(BusinessStatusCode.NotFound, "Reservation not found."); + } - // 如果是恢复操作 - if (isRestoring) - { - // 检查原房间的当前状态 - var room = roomRepository.GetFirst(a => a.RoomNumber == originalReser.ReservationRoomNumber); + var targetRoomResult = RoomReferenceHelper.Resolve(roomRepository, reser.RoomId ?? originalReser.RoomId, reser.ReservationRoomNumber ?? originalReser.ReservationRoomNumber); + if (targetRoomResult.Room == null) + { + return CreateRoomLookupFailure(targetRoomResult, reser.RoomId ?? originalReser.RoomId, reser.ReservationRoomNumber ?? originalReser.ReservationRoomNumber); + } - if (room == null) - { - return new BaseResponse(BusinessStatusCode.Conflict, - LocalizationHelper.GetLocalizedString("Room does not exist, cannot restore reservation", - "关联的房间不存在,无法恢复预约")); - } + var targetRoom = targetRoomResult.Room; + var startDate = DateOnly.FromDateTime(reser.ReservationStartDate); + var endDate = DateOnly.FromDateTime(reser.ReservationEndDate); - // 检查房间是否可用 - if (room.RoomStateId != (int)RoomState.Vacant) - { - return new BaseResponse(BusinessStatusCode.Conflict, - string.Format(LocalizationHelper.GetLocalizedString( - "Room {0} is currently occupied, cannot restore reservation", - "房间{0}当前已被占用,无法恢复预约"), - room.RoomNumber)); - } + var isRestoring = originalReser.IsDelete == 1 && reser.IsDelete == 0; + if (isRestoring && targetRoom.RoomStateId != (int)RoomState.Vacant) + { + return new BaseResponse(BusinessStatusCode.Conflict, "Room is not vacant."); + } - // 检查时间段冲突(如果有时间字段) - var conflictingReservation = reserRepository.GetFirst(r => - r.Id != originalReser.Id && - r.IsDelete == 0 && - r.ReservationRoomNumber == originalReser.ReservationRoomNumber && - r.ReservationStartDate < originalReser.ReservationEndDate && - r.ReservationEndDate > originalReser.ReservationStartDate); + var hasConflict = reserRepository.AsQueryable().Any(a => + a.Id != originalReser.Id && + a.IsDelete != 1 && + a.ReservationStatus == 0 && + a.RoomId == targetRoom.Id && + a.ReservationStartDate < endDate && + a.ReservationEndDate > startDate); - if (conflictingReservation != null) - { - return new BaseResponse(BusinessStatusCode.Conflict, - LocalizationHelper.GetLocalizedString( - "Room is already reserved during this period, cannot restore reservation", - "该时间段内房间已被预订,无法恢复预约")); - } + if (hasConflict) + { + return new BaseResponse(BusinessStatusCode.Conflict, "Room is already reserved during this period."); + } - // 恢复预约并更新房间状态 - var entity = EntityMapper.Map(reser); - var reserUpdateResult = reserRepository.Update(entity); - if (!reserUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + originalReser.ReservationId = reser.ReservationId; + originalReser.CustomerName = reser.CustomerName; + originalReser.ReservationPhoneNumber = dataProtector.EncryptReserData(reser.ReservationPhoneNumber); + originalReser.RoomId = targetRoom.Id; + originalReser.ReservationRoomNumber = targetRoom.RoomNumber; + originalReser.ReservationChannel = reser.ReservationChannel; + originalReser.ReservationStartDate = startDate; + originalReser.ReservationEndDate = endDate; + originalReser.IsDelete = reser.IsDelete; + originalReser.DataChgUsr = reser.DataChgUsr; + originalReser.DataChgDate = reser.DataChgDate; + originalReser.RowVersion = reser.RowVersion ?? 0; + + if (!reserRepository.Update(originalReser)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } - room.RoomStateId = (int)RoomState.Reserved; - var roomUpdateResult = roomRepository.Update(room); - if (!roomUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } - } - else + if (originalReser.IsDelete != 1 && originalReser.ReservationStatus == 0) + { + targetRoom.RoomStateId = (int)RoomState.Reserved; + if (!roomRepository.Update(targetRoom)) { - // 普通更新逻辑 - var entity = EntityMapper.Map(reser); - var reserUpdateResult = reserRepository.Update(entity); - if (!reserUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + return BaseResponseFactory.ConcurrencyConflict(); } - - scope.Complete(); - return new BaseResponse(BusinessStatusCode.Success, - LocalizationHelper.GetLocalizedString("Update Reservation Success", "预约信息更新成功")); } + + scope.Complete(); + return new BaseResponse(BusinessStatusCode.Success, "Update reservation success."); } catch (Exception ex) { - logger.LogError(ex, LocalizationHelper.GetLocalizedString("Update Customer Failed", "预约信息更新失败")); - return new BaseResponse(BusinessStatusCode.InternalServerError, - LocalizationHelper.GetLocalizedString("Update Customer Failed", "预约信息更新失败")); + logger.LogError(ex, "Update reservation failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Update reservation failed."); } } - /// - /// 添加预约信息 - /// - /// - /// - public BaseResponse InserReserInfo(CreateReserInputDto r) + public BaseResponse InserReserInfo(CreateReserInputDto input) { - string NewTel = dataProtector.EncryptReserData(r.ReservationPhoneNumber); - r.ReservationPhoneNumber = NewTel; try { - var entity = EntityMapper.Map(r); - reserRepository.Insert(entity); + var roomResult = RoomReferenceHelper.Resolve(roomRepository, input?.RoomId, input?.ReservationRoomNumber); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, input?.RoomId, input?.ReservationRoomNumber); + } + + var entity = new Domain.Reser + { + ReservationId = input.ReservationId, + CustomerName = input.CustomerName, + ReservationPhoneNumber = dataProtector.EncryptReserData(input.ReservationPhoneNumber), + RoomId = roomResult.Room.Id, + ReservationRoomNumber = roomResult.Room.RoomNumber, + ReservationChannel = input.ReservationChannel, + ReservationStartDate = DateOnly.FromDateTime(input.ReservationStartDate), + ReservationEndDate = DateOnly.FromDateTime(input.ReservationEndDate), + ReservationStatus = input.ReservationStatus, + DataInsUsr = input.DataInsUsr, + DataInsDate = input.DataInsDate + }; - var room = roomRepository.GetFirst(a => a.RoomNumber == r.ReservationRoomNumber); - room.RoomStateId = new EnumHelper().GetEnumValue(RoomState.Reserved); - var updateResult = roomRepository.Update(room); + reserRepository.Insert(entity); - if (!updateResult) + roomResult.Room.RoomStateId = EnumHelper.GetEnumValue(RoomState.Reserved); + if (!roomRepository.Update(roomResult.Room)) { - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Add Customer Failed", "预约信息添加失败")); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Insert reservation failed."); } - return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Add Customer Success", "预约信息添加成功")); + + return new BaseResponse(BusinessStatusCode.Success, "Insert reservation success."); } catch (Exception ex) { - logger.LogError(ex, LocalizationHelper.GetLocalizedString("Add Customer Failed", "预约信息添加失败")); - return new BaseResponse(BusinessStatusCode.InternalServerError, LocalizationHelper.GetLocalizedString("Add Customer Failed", "预约信息添加失败")); + logger.LogError(ex, "Insert reservation failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, "Insert reservation failed."); } } - /// - /// 查询所有预约类型 - /// - /// public ListOutputDto SelectReserTypeAll() { - var helper = new EnumHelper(); var enumList = Enum.GetValues(typeof(ReserType)) .Cast() - .Select(e => new EnumDto + .Select(a => new EnumDto { - Id = (int)e, - Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Id = (int)a, + Name = a.ToString(), + Description = EnumHelper.GetEnumDescription(a) }) .ToList(); @@ -430,5 +316,73 @@ namespace EOM.TSHotelManagement.Service }; } + private ReadReserOutputDto MapReservationToOutput(Domain.Reser source, Domain.Room room, Dictionary reserTypeMap) + { + return new ReadReserOutputDto + { + Id = source.Id, + RoomId = source.RoomId ?? room?.Id, + ReservationId = source.ReservationId, + CustomerName = source.CustomerName, + ReservationPhoneNumber = dataProtector.SafeDecryptReserData(source.ReservationPhoneNumber), + ReservationRoomNumber = room?.RoomNumber ?? source.ReservationRoomNumber, + RoomArea = RoomReferenceHelper.GetRoomArea(room), + RoomFloor = RoomReferenceHelper.GetRoomFloor(room), + RoomLocator = RoomReferenceHelper.GetRoomLocator(room), + ReservationChannel = source.ReservationChannel, + ReservationChannelDescription = reserTypeMap.TryGetValue(source.ReservationChannel ?? string.Empty, out var channelDescription) ? channelDescription : string.Empty, + ReservationStartDate = source.ReservationStartDate.ToDateTime(TimeOnly.MinValue), + ReservationEndDate = source.ReservationEndDate.ToDateTime(TimeOnly.MinValue), + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + } + + private static bool IsSameRoom(Domain.Reser reservation, Domain.Room room) + { + if (reservation == null || room == null || !reservation.RoomId.HasValue || reservation.RoomId.Value <= 0) + { + return false; + } + + return reservation.RoomId.Value == room.Id; + } + + private static BaseResponse CreateRoomLookupFailure(RoomResolveResult result, int? roomId, string roomNumber) + { + if (!roomId.HasValue || roomId.Value <= 0) + { + return new BaseResponse(BusinessStatusCode.BadRequest, "RoomId is required."); + } + + if (result?.IsAmbiguous == true) + { + return new BaseResponse(BusinessStatusCode.Conflict, $"Multiple rooms match room id '{roomId.Value}'."); + } + + return new BaseResponse(BusinessStatusCode.NotFound, $"RoomId '{roomId.Value}' was not found."); + } + + private static ReadReserInputDto CreateReservationFilter(ReadReserInputDto input) + { + return new ReadReserInputDto + { + Page = input.Page, + PageSize = input.PageSize, + IgnorePaging = input.IgnorePaging, + RoomId = null, + ReservationId = input.ReservationId, + CustomerName = input.CustomerName, + ReservationPhoneNumber = input.ReservationPhoneNumber, + ReservationRoomNumber = null, + ReservationChannel = input.ReservationChannel, + ReservationStartDateStart = input.ReservationStartDateStart, + ReservationStartDateEnd = input.ReservationStartDateEnd + }; + } } } diff --git a/EOM.TSHotelManagement.Service/Business/Room/IRoomService.cs b/EOM.TSHotelManagement.Service/Business/Room/IRoomService.cs index 8930b2aaae0c475e82529c219a3596e723caed43..9ec1546ecefdec36ee2ac646cb84581184d7ad68 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/IRoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/IRoomService.cs @@ -123,6 +123,13 @@ namespace EOM.TSHotelManagement.Service object SelectRoomByRoomPrice(ReadRoomInputDto readRoomInputDto); #endregion + /// + /// 查询房间可用计价项 + /// + /// + /// + SingleOutputDto SelectRoomPricingOptions(ReadRoomInputDto readRoomInputDto); + #region 查询脏房数量 /// /// 查询脏房数量 @@ -205,4 +212,4 @@ namespace EOM.TSHotelManagement.Service #endregion } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..e14d6b80173bc322a27d05adc91de444c1246113 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs @@ -0,0 +1,96 @@ +using EOM.TSHotelManagement.Data; +using EOM.TSHotelManagement.Domain; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace EOM.TSHotelManagement.Service +{ + internal sealed class RoomResolveResult + { + public Room Room { get; init; } + + public bool IsAmbiguous { get; init; } + } + + internal static class RoomLocatorHelper + { + public static RoomResolveResult Resolve( + GenericRepository repository, + int? roomId, + string roomNumber, + string roomArea, + int? roomFloor) + { + if (roomId.HasValue && roomId.Value > 0) + { + var room = repository.GetFirst(a => a.Id == roomId.Value); + + return new RoomResolveResult { Room = room, IsAmbiguous = false }; + } + + if (string.IsNullOrWhiteSpace(roomNumber)) + { + return new RoomResolveResult(); + } + + var normalizedNumber = roomNumber.Trim(); + var normalizedArea = NormalizeArea(roomArea); + var candidates = repository.GetList(a => a.RoomNumber == normalizedNumber); + + if (!candidates.Any()) + { + return new RoomResolveResult(); + } + + var hasArea = !string.IsNullOrWhiteSpace(normalizedArea); + var hasFloor = roomFloor.HasValue; + if (!hasArea && !hasFloor) + { + return candidates.Count == 1 + ? new RoomResolveResult { Room = candidates[0], IsAmbiguous = false } + : new RoomResolveResult { IsAmbiguous = true }; + } + + var exactMatches = candidates + .Where(a => (!hasArea || string.Equals(NormalizeArea(a.RoomArea), normalizedArea, StringComparison.OrdinalIgnoreCase)) + && (!hasFloor || a.RoomFloor == roomFloor)) + .Take(2) + .ToList(); + + if (exactMatches.Count == 1) + { + return new RoomResolveResult { Room = exactMatches[0], IsAmbiguous = false }; + } + + return new RoomResolveResult { IsAmbiguous = exactMatches.Count > 1 }; + } + + public static string NormalizeArea(string area) + { + return string.IsNullOrWhiteSpace(area) ? string.Empty : area.Trim(); + } + + public static string BuildLocator(string roomArea, int? roomFloor, string roomNumber) + { + var parts = new List(); + + if (!string.IsNullOrWhiteSpace(roomArea)) + { + parts.Add(roomArea.Trim()); + } + + if (roomFloor.HasValue) + { + parts.Add($"{roomFloor.Value}F"); + } + + if (!string.IsNullOrWhiteSpace(roomNumber)) + { + parts.Add(roomNumber.Trim()); + } + + return string.Join(" / ", parts); + } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomPricingEvaluation.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingEvaluation.cs new file mode 100644 index 0000000000000000000000000000000000000000..210b1385b1537eddb62f63829dbb0726e4e99d85 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingEvaluation.cs @@ -0,0 +1,14 @@ +namespace EOM.TSHotelManagement.Service +{ + internal sealed class RoomPricingEvaluation + { + public string SelectedPricingCode { get; init; } + public string SelectedPricingName { get; init; } + public string EffectivePricingCode { get; init; } + public string EffectivePricingName { get; init; } + public decimal EffectiveRoomRent { get; init; } + public decimal EffectiveRoomDeposit { get; init; } + public int? PricingStayHours { get; init; } + public bool IsPricingTimedOut { get; init; } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..bbb8b3dadd025d43fa4849c281e3a1df5cbd5a02 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs @@ -0,0 +1,117 @@ +using EOM.TSHotelManagement.Contract; +using System.Text.Json; + +namespace EOM.TSHotelManagement.Service +{ + internal static class RoomPricingHelper + { + public const string DefaultPricingCode = "STANDARD"; + public const string DefaultPricingName = "标准价"; + + public static List BuildPricingItems(decimal roomRent, decimal roomDeposit, string pricingItemsJson) + { + var items = new List + { + CreateDefaultPricingItem(roomRent, roomDeposit) + }; + + items.AddRange(DeserializeAdditionalPricingItems(pricingItemsJson)); + return items; + } + + public static RoomTypePricingItemDto CreateDefaultPricingItem(decimal roomRent, decimal roomDeposit) + { + return new RoomTypePricingItemDto + { + PricingCode = DefaultPricingCode, + PricingName = DefaultPricingName, + RoomRent = roomRent, + RoomDeposit = roomDeposit, + Sort = 0, + IsDefault = true + }; + } + + public static string SerializeAdditionalPricingItems(IEnumerable? pricingItems) + { + var normalized = NormalizeAdditionalPricingItems(pricingItems).ToList(); + return normalized.Count == 0 ? "[]" : JsonSerializer.Serialize(normalized); + } + + public static RoomTypePricingItemDto? ResolvePricingItem(decimal roomRent, decimal roomDeposit, string pricingItemsJson, string? pricingCode) + { + var normalizedCode = NormalizePricingCode(pricingCode); + var items = BuildPricingItems(roomRent, roomDeposit, pricingItemsJson); + if (string.IsNullOrWhiteSpace(normalizedCode)) + { + return items.FirstOrDefault(); + } + + return items.FirstOrDefault(a => string.Equals(a.PricingCode, normalizedCode, StringComparison.OrdinalIgnoreCase)); + } + + public static string NormalizePricingCode(string? pricingCode) + { + return string.IsNullOrWhiteSpace(pricingCode) + ? string.Empty + : pricingCode.Trim().ToUpperInvariant(); + } + + private static List DeserializeAdditionalPricingItems(string pricingItemsJson) + { + if (string.IsNullOrWhiteSpace(pricingItemsJson)) + { + return new List(); + } + + try + { + var items = JsonSerializer.Deserialize>(pricingItemsJson) ?? new List(); + return NormalizeAdditionalPricingItems(items).ToList(); + } + catch + { + return new List(); + } + } + + private static IEnumerable NormalizeAdditionalPricingItems(IEnumerable? pricingItems) + { + if (pricingItems == null) + { + yield break; + } + + var seenCodes = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var item in pricingItems.OrderBy(a => a?.Sort ?? 0)) + { + if (item == null) + { + continue; + } + + var code = NormalizePricingCode(item.PricingCode); + if (string.IsNullOrWhiteSpace(code) || string.Equals(code, DefaultPricingCode, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (!seenCodes.Add(code)) + { + continue; + } + + yield return new RoomTypePricingItemDto + { + PricingCode = code, + PricingName = string.IsNullOrWhiteSpace(item.PricingName) ? code : item.PricingName.Trim(), + RoomRent = item.RoomRent, + RoomDeposit = item.RoomDeposit, + StayHours = item.StayHours > 0 ? item.StayHours : null, + Sort = item.Sort, + IsDefault = false + }; + } + } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..5ee47ea2c76c63d1df0bdccca7764c099b5594ca --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs @@ -0,0 +1,64 @@ +using EOM.TSHotelManagement.Data; +using EOM.TSHotelManagement.Domain; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace EOM.TSHotelManagement.Service +{ + internal static class RoomReferenceHelper + { + public static RoomResolveResult Resolve(GenericRepository repository, int? roomId, string roomNumber) + { + return RoomLocatorHelper.Resolve(repository, roomId, null, null, null); + } + + public static List LoadRooms(GenericRepository repository, IEnumerable roomIds, IEnumerable roomNumbers) + { + var ids = roomIds? + .Where(a => a.HasValue && a.Value > 0) + .Select(a => a.Value) + .Distinct() + .ToList() ?? new List(); + + if (ids.Count == 0) + { + return new List(); + } + + return repository.AsQueryable() + .Where(a => a.IsDelete != 1 && ids.Contains(a.Id)) + .ToList(); + } + + public static Room FindRoom(IEnumerable rooms, int? roomId, string roomNumber) + { + if (!roomId.HasValue || roomId.Value <= 0) + { + return null; + } + + return rooms.FirstOrDefault(a => a.Id == roomId.Value); + } + + public static string GetRoomArea(Room room) + { + return room?.RoomArea ?? string.Empty; + } + + public static int? GetRoomFloor(Room room) + { + return room?.RoomFloor; + } + + public static string GetRoomLocator(Room room, string fallbackRoomNumber = null) + { + if (room != null) + { + return RoomLocatorHelper.BuildLocator(room.RoomArea, room.RoomFloor, room.RoomNumber); + } + + return string.Empty; + } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs index c32440a4b44173882417295604260679207949e1..996d0daf70e71fe7544562f560619ed3250f67a8 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs @@ -1,27 +1,5 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - */ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; @@ -29,63 +7,37 @@ using jvncorelib.CodeLib; using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; using System.Transactions; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.Business.Room { - /// - /// 客房信息接口实现类 - /// public class RoomService : IRoomService { - /// - /// 客房信息 - /// - private readonly GenericRepository roomRepository; - - /// - /// 消费记录 - /// + private readonly GenericRepository roomRepository; private readonly GenericRepository spendRepository; - - /// - /// 客房类型 - /// private readonly GenericRepository roomTypeRepository; - - /// - /// 能耗管理 - /// private readonly GenericRepository energyRepository; - - /// - /// 客户信息 - /// - private readonly GenericRepository custoRepository; - - /// - /// 客户类型 - /// + private readonly GenericRepository custoRepository; private readonly GenericRepository custoTypeRepository; - - /// - /// 会员等级规则 - /// private readonly GenericRepository vipLevelRuleRepository; - - /// - /// 预约信息 - /// - private readonly GenericRepository reserRepository; - - /// - /// 唯一编码 - /// + private readonly GenericRepository reserRepository; private readonly UniqueCode uniqueCode; - private readonly ILogger logger; - public RoomService(GenericRepository roomRepository, GenericRepository spendRepository, GenericRepository roomTypeRepository, GenericRepository energyRepository, GenericRepository custoRepository, GenericRepository custoTypeRepository, GenericRepository vipLevelRuleRepository, GenericRepository reserRepository, UniqueCode uniqueCode, ILogger logger) + public RoomService( + GenericRepository roomRepository, + GenericRepository spendRepository, + GenericRepository roomTypeRepository, + GenericRepository energyRepository, + GenericRepository custoRepository, + GenericRepository custoTypeRepository, + GenericRepository vipLevelRuleRepository, + GenericRepository reserRepository, + UniqueCode uniqueCode, + ILogger logger) { this.roomRepository = roomRepository; this.spendRepository = spendRepository; @@ -99,529 +51,343 @@ namespace EOM.TSHotelManagement.Service this.logger = logger; } - /// - /// 根据房间状态获取相应状态的房间信息 - /// - /// - /// - public ListOutputDto SelectRoomByRoomState(ReadRoomInputDto readRoomInputDto) - { - return BuildRoomList(readRoomInputDto); - } + public ListOutputDto SelectRoomByRoomState(ReadRoomInputDto readRoomInputDto) => BuildRoomList(readRoomInputDto); - /// - /// 根据房间状态来查询可使用的房间 - /// - /// - public ListOutputDto SelectCanUseRoomAll() + public ListOutputDto SelectCanUseRoomAll() => BuildRoomList(new ReadRoomInputDto { - var rooms = roomRepository.GetList(a => a.RoomStateId == (int)RoomState.Vacant); - var result = EntityMapper.MapList(rooms); - return new ListOutputDto - { - Data = new PagedData - { - Items = result, - TotalCount = result.Count - } - }; - } + IgnorePaging = true, + RoomStateId = (int)RoomState.Vacant + }); - /// - /// 获取所有房间信息 - /// - /// - public ListOutputDto SelectRoomAll(ReadRoomInputDto readRoomInputDto) - { - return BuildRoomList(readRoomInputDto); - } + public ListOutputDto SelectRoomAll(ReadRoomInputDto readRoomInputDto) => BuildRoomList(readRoomInputDto); - /// - /// 获取房间分区的信息 - /// - /// - public ListOutputDto SelectRoomByTypeName(ReadRoomInputDto readRoomInputDto) - { - return BuildRoomList(readRoomInputDto); - } + public ListOutputDto SelectRoomByTypeName(ReadRoomInputDto readRoomInputDto) => BuildRoomList(readRoomInputDto); - private ListOutputDto BuildRoomList(ReadRoomInputDto readRoomInputDto) + public SingleOutputDto SelectRoomByRoomNo(ReadRoomInputDto readRoomInputDto) { - readRoomInputDto ??= new ReadRoomInputDto(); - - var where = SqlFilterBuilder.BuildExpression(readRoomInputDto); - var query = roomRepository.AsQueryable().Where(a => a.IsDelete != 1); - var whereExpression = where.ToExpression(); - if (whereExpression != null) - { - query = query.Where(whereExpression); - } - - query = query.OrderBy(a => a.RoomNumber); - - var count = 0; - List rooms; - if (!readRoomInputDto.IgnorePaging) + var roomResult = ResolveRoom(readRoomInputDto); + if (roomResult.Room == null) { - var page = readRoomInputDto.Page > 0 ? readRoomInputDto.Page : 1; - var pageSize = readRoomInputDto.PageSize > 0 ? readRoomInputDto.PageSize : 15; - rooms = query.ToPageList(page, pageSize, ref count); - } - else - { - rooms = query.ToList(); - count = rooms.Count; + return CreateRoomLookupFailureOutput(roomResult, readRoomInputDto?.RoomNumber, readRoomInputDto?.RoomArea, readRoomInputDto?.RoomFloor); } - var roomTypeMap = roomTypeRepository.AsQueryable() - .Where(a => a.IsDelete != 1) - .ToList() - .GroupBy(a => a.RoomTypeId) - .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.RoomTypeName ?? ""); + NormalizeRoom(roomResult.Room); + var data = MapRoomToOutput( + roomResult.Room, + BuildRoomTypeMap(), + BuildCustomerMap(new List { roomResult.Room }), + BuildRoomStateMap()); - var customerNumbers = rooms - .Select(a => a.CustomerNumber) - .Where(a => !a.IsNullOrEmpty()) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); + return new SingleOutputDto { Data = data }; + } - var customerMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (customerNumbers.Count > 0) + public SingleOutputDto DayByRoomNo(ReadRoomInputDto roomInputDto) + { + var roomResult = ResolveRoom(roomInputDto); + if (roomResult.Room == null) { - customerMap = custoRepository.AsQueryable() - .Where(a => a.IsDelete != 1 && customerNumbers.Contains(a.CustomerNumber)) - .ToList() - .GroupBy(a => a.CustomerNumber) - .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.CustomerName ?? "", StringComparer.OrdinalIgnoreCase); + return CreateRoomLookupFailureOutput(roomResult, roomInputDto?.RoomNumber, roomInputDto?.RoomArea, roomInputDto?.RoomFloor); } - var helper = new EnumHelper(); - var roomStateMap = Enum.GetValues(typeof(RoomState)) - .Cast() - .ToDictionary(e => (int)e, e => helper.GetEnumDescription(e) ?? ""); - - List result; - var useParallelProjection = readRoomInputDto.IgnorePaging && rooms.Count >= 200; - if (useParallelProjection) + var lastCheckInTime = NormalizeStayDateTime(roomResult.Room.LastCheckInTime); + if (lastCheckInTime.HasValue) { - var dtoArray = new ReadRoomOutputDto[rooms.Count]; - System.Threading.Tasks.Parallel.For(0, rooms.Count, i => - { - var source = rooms[i]; - dtoArray[i] = new ReadRoomOutputDto - { - Id = source.Id, - RoomNumber = source.RoomNumber, - RoomTypeId = source.RoomTypeId, - RoomName = roomTypeMap.TryGetValue(source.RoomTypeId, out var roomTypeName) ? roomTypeName : "", - CustomerNumber = source.CustomerNumber ?? "", - CustomerName = customerMap.TryGetValue(source.CustomerNumber ?? "", out var customerName) ? customerName : "", - LastCheckInTime = source.LastCheckInTime.HasValue ? source.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue) : null, - LastCheckOutTime = source.LastCheckOutTime == DateOnly.MinValue ? null : source.LastCheckOutTime.ToDateTime(TimeOnly.MinValue), - RoomStateId = source.RoomStateId, - RoomState = roomStateMap.TryGetValue(source.RoomStateId, out var roomStateName) ? roomStateName : "", - RoomRent = source.RoomRent, - RoomDeposit = source.RoomDeposit, - RoomLocation = source.RoomLocation, - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }; - }); - result = dtoArray.ToList(); - } - else - { - result = new List(rooms.Count); - rooms.ForEach(source => - { - result.Add(new ReadRoomOutputDto - { - Id = source.Id, - RoomNumber = source.RoomNumber, - RoomTypeId = source.RoomTypeId, - RoomName = roomTypeMap.TryGetValue(source.RoomTypeId, out var roomTypeName) ? roomTypeName : "", - CustomerNumber = source.CustomerNumber ?? "", - CustomerName = customerMap.TryGetValue(source.CustomerNumber ?? "", out var customerName) ? customerName : "", - LastCheckInTime = source.LastCheckInTime.HasValue ? source.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue) : null, - LastCheckOutTime = source.LastCheckOutTime == DateOnly.MinValue ? null : source.LastCheckOutTime.ToDateTime(TimeOnly.MinValue), - RoomStateId = source.RoomStateId, - RoomState = roomStateMap.TryGetValue(source.RoomStateId, out var roomStateName) ? roomStateName : "", - RoomRent = source.RoomRent, - RoomDeposit = source.RoomDeposit, - RoomLocation = source.RoomLocation, - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }); - }); + var days = CalculateStayDays(lastCheckInTime.Value, DateTime.Now); + return new SingleOutputDto { Data = new ReadRoomOutputDto { StayDays = days } }; } - return new ListOutputDto - { - Data = new PagedData - { - Items = result, - TotalCount = count - } - }; + return new SingleOutputDto { Data = new ReadRoomOutputDto { StayDays = 0 } }; } - /// - /// 根据房间编号查询房间信息 - /// - /// - /// - public SingleOutputDto SelectRoomByRoomNo(ReadRoomInputDto readRoomInputDto) + public BaseResponse UpdateRoomInfo(UpdateRoomInputDto r) { - List roomStates = new List(); - var helper = new EnumHelper(); - roomStates = Enum.GetValues(typeof(RoomState)) - .Cast() - .Select(e => new EnumDto - { - Id = (int)e, - Name = e.ToString(), - Description = helper.GetEnumDescription(e) - }) - .ToList(); - Room room = new Room(); - room = roomRepository.GetFirst(a => a.IsDelete != 1 && a.RoomNumber == readRoomInputDto.RoomNumber); - if (!room.IsNullOrEmpty()) - { - var roomSate = roomStates.SingleOrDefault(a => a.Id == room.RoomStateId); - room.RoomState = roomSate.Description.IsNullOrEmpty() ? "" : roomSate.Description; - var roomType = roomTypeRepository.GetFirst(a => a.RoomTypeId == room.RoomTypeId); - room.RoomName = roomType.RoomTypeName.IsNullOrEmpty() ? "" : roomType.RoomTypeName; - } - else - { - room = new Room(); - } - - var Data = EntityMapper.Map(room); - - return new SingleOutputDto() { Data = Data }; + return UpdateRoomCore(r, true); } - /// - /// 根据房间编号查询截止到今天住了多少天 - /// - /// - /// - public SingleOutputDto DayByRoomNo(ReadRoomInputDto roomInputDto) + public BaseResponse UpdateRoomInfoWithReser(UpdateRoomInputDto r) { - var room = roomRepository.GetFirst(a => a.RoomNumber == roomInputDto.RoomNumber); - if (room?.LastCheckInTime != null) - { - var days = Math.Abs((room.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue) - DateTime.Now).Days); - return new SingleOutputDto { Data = new ReadRoomOutputDto { StayDays = days } }; - } - return new SingleOutputDto { Data = new ReadRoomOutputDto { StayDays = 0 } }; + return UpdateRoomCore(r, false); } - /// - /// 根据房间编号修改房间信息(入住) - /// - /// - /// - public BaseResponse UpdateRoomInfo(UpdateRoomInputDto r) + private BaseResponse UpdateRoomCore(UpdateRoomInputDto r, bool isFullUpdate) { try { - var room = this.roomRepository.GetFirst(a => a.RoomNumber == r.RoomNumber); - room.RoomStateId = r.RoomStateId; - room.CustomerNumber = r.CustomerNumber; - room.LastCheckInTime = r.LastCheckInTime; - room.DataChgDate = r.DataChgDate; - room.DataChgUsr = r.DataChgUsr; - room.RowVersion = r.RowVersion ?? 0; - var updateResult = roomRepository.Update(room); - if (!updateResult) + var roomResult = ResolveRoom(r); + if (roomResult.Room == null) { - return BaseResponseFactory.ConcurrencyConflict(); + return CreateRoomLookupFailure(roomResult, r?.RoomNumber, r?.RoomArea, r?.RoomFloor); } - } - catch (Exception ex) - { - logger.LogError(ex, "Error updating room info for room number {RoomNumber}", r.RoomNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; - } - return new BaseResponse(); - } - /// - /// 根据房间编号修改房间信息(预约) - /// - /// - /// - public BaseResponse UpdateRoomInfoWithReser(UpdateRoomInputDto r) - { - try - { - var room = this.roomRepository.GetFirst(a => a.RoomNumber == r.RoomNumber); + var room = roomResult.Room; room.RoomStateId = r.RoomStateId; + + if (isFullUpdate) + { + room.CustomerNumber = r.CustomerNumber; + room.LastCheckInTime = NormalizeStayDateTime(r.LastCheckInTime); + var pricingResponse = ApplyPricingSelection(room, r.PricingCode); + if (pricingResponse != null) + { + return pricingResponse; + } + } + room.DataChgDate = r.DataChgDate; room.DataChgUsr = r.DataChgUsr; room.RowVersion = r.RowVersion ?? 0; - var updateResult = roomRepository.Update(room); - if (!updateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + + return roomRepository.Update(room) ? new BaseResponse() : BaseResponseFactory.ConcurrencyConflict(); } catch (Exception ex) { - logger.LogError(ex, "Error updating room info with reservation for room number {RoomNumber}", r.RoomNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + var updateType = isFullUpdate ? "full" : "with reservation"; + logger.LogError(ex, "Error updating room info ({UpdateType}) for room number {RoomNumber}", updateType, r.RoomNumber); + return ErrorResponse(ex); } - return new BaseResponse(); } - /// - /// 查询可入住房间数量 - /// - /// - public SingleOutputDto SelectCanUseRoomAllByRoomState() + public SingleOutputDto SelectCanUseRoomAllByRoomState() => CountByState(RoomState.Vacant, count => new ReadRoomOutputDto { Vacant = count }); + + public SingleOutputDto SelectNotUseRoomAllByRoomState() => CountByState(RoomState.Occupied, count => new ReadRoomOutputDto { Occupied = count }); + + public object SelectRoomByRoomPrice(ReadRoomInputDto readRoomInputDto) { - try + var roomResult = ResolveRoom(readRoomInputDto); + if (roomResult.Room == null) { - var count = roomRepository.Count(a => a.RoomStateId == (int)RoomState.Vacant && a.IsDelete != 1); - return new SingleOutputDto - { - Data = new ReadRoomOutputDto { Vacant = count } - }; + return 0M; } - catch (Exception ex) - { - return new SingleOutputDto - { - Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), - Code = BusinessStatusCode.InternalServerError - }; - } - } - /// - /// 查询已入住房间数量 - /// - /// - public SingleOutputDto SelectNotUseRoomAllByRoomState() - { - try + if (string.IsNullOrWhiteSpace(readRoomInputDto?.PricingCode)) { - var count = roomRepository.Count(a => a.RoomStateId == (int)RoomState.Occupied && a.IsDelete != 1); - return new SingleOutputDto - { - Data = new ReadRoomOutputDto { Occupied = count } - }; + return GetEffectiveRoomRent(roomResult.Room); } - catch (Exception ex) + + var roomType = roomTypeRepository.GetFirst(a => a.RoomTypeId == roomResult.Room.RoomTypeId && a.IsDelete != 1); + if (roomType == null) { - return new SingleOutputDto - { - Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), - Code = BusinessStatusCode.InternalServerError - }; + return 0M; } - } - /// - /// 根据房间编号查询房间价格 - /// - /// - public object SelectRoomByRoomPrice(ReadRoomInputDto r) - { - return roomRepository.GetFirst(a => a.RoomNumber == r.RoomNumber).RoomRent; + var pricingItem = RoomPricingHelper.ResolvePricingItem(roomType.RoomRent, roomType.RoomDeposit, roomType.PricingItemsJson, readRoomInputDto.PricingCode); + return pricingItem?.RoomRent ?? 0M; } - /// - /// 查询脏房数量 - /// - /// - public SingleOutputDto SelectNotClearRoomAllByRoomState() + public SingleOutputDto SelectRoomPricingOptions(ReadRoomInputDto readRoomInputDto) { - try + var roomResult = ResolveRoom(readRoomInputDto); + Domain.Room room = null; + RoomType roomType = null; + + if (roomResult.Room != null) { - var count = roomRepository.Count(a => a.RoomStateId == (int)RoomState.Dirty && a.IsDelete != 1); - return new SingleOutputDto - { - Data = new ReadRoomOutputDto { Dirty = count } - }; + room = roomResult.Room; + roomType = roomTypeRepository.GetFirst(a => a.RoomTypeId == room.RoomTypeId && a.IsDelete != 1); } - catch (Exception ex) + else if (readRoomInputDto?.RoomTypeId > 0) { - return new SingleOutputDto - { - Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), - Code = BusinessStatusCode.InternalServerError - }; + roomType = roomTypeRepository.GetFirst(a => a.RoomTypeId == readRoomInputDto.RoomTypeId && a.IsDelete != 1); } - } - /// - /// 查询维修房数量 - /// - /// - public SingleOutputDto SelectFixingRoomAllByRoomState() - { - try + if (roomType == null) { - var count = roomRepository.Count(a => a.RoomStateId == (int)RoomState.Maintenance && a.IsDelete != 1); - return new SingleOutputDto + if (roomResult.IsAmbiguous) { - Data = new ReadRoomOutputDto { Maintenance = count } - }; - } - catch (Exception ex) - { - return new SingleOutputDto + return new SingleOutputDto + { + Code = BusinessStatusCode.Conflict, + Message = "Multiple rooms match the current room number.", + Data = new ReadRoomPricingOutputDto { PricingItems = new List() } + }; + } + + return new SingleOutputDto { - Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), - Code = BusinessStatusCode.InternalServerError + Code = BusinessStatusCode.NotFound, + Message = "Room type pricing not found.", + Data = new ReadRoomPricingOutputDto { PricingItems = new List() } }; } - } - /// - /// 查询预约房数量 - /// - /// - public SingleOutputDto SelectReservedRoomAllByRoomState() - { - try + var pricingItems = RoomPricingHelper.BuildPricingItems(roomType.RoomRent, roomType.RoomDeposit, roomType.PricingItemsJson); + if (room != null + && !string.IsNullOrWhiteSpace(room.RoomPricingCode) + && pricingItems.All(a => !string.Equals(a.PricingCode, room.RoomPricingCode, StringComparison.OrdinalIgnoreCase))) { - var count = roomRepository.Count(a => a.RoomStateId == (int)RoomState.Reserved && a.IsDelete != 1); - return new SingleOutputDto + pricingItems.Add(new RoomTypePricingItemDto { - Data = new ReadRoomOutputDto { Reserved = count } - }; + PricingCode = room.RoomPricingCode, + PricingName = string.IsNullOrWhiteSpace(room.RoomPricingName) ? room.RoomPricingCode : room.RoomPricingName, + RoomRent = room.AppliedRoomRent, + RoomDeposit = room.AppliedRoomDeposit, + StayHours = room.PricingStayHours, + IsDefault = false + }); } - catch (Exception ex) + + var pricingEvaluation = room == null + ? new RoomPricingEvaluation + { + SelectedPricingCode = RoomPricingHelper.DefaultPricingCode, + SelectedPricingName = RoomPricingHelper.DefaultPricingName, + EffectivePricingCode = RoomPricingHelper.DefaultPricingCode, + EffectivePricingName = RoomPricingHelper.DefaultPricingName, + EffectiveRoomRent = roomType.RoomRent, + EffectiveRoomDeposit = roomType.RoomDeposit + } + : EvaluateRoomPricing(room); + + return new SingleOutputDto { - return new SingleOutputDto + Data = new ReadRoomPricingOutputDto { - Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), - Code = BusinessStatusCode.InternalServerError - }; - } + RoomId = room?.Id, + RoomNumber = room?.RoomNumber ?? string.Empty, + RoomLocator = room == null ? string.Empty : RoomLocatorHelper.BuildLocator(room.RoomArea, room.RoomFloor, room.RoomNumber), + RoomTypeId = roomType.RoomTypeId, + RoomTypeName = roomType.RoomTypeName, + CurrentPricingCode = pricingEvaluation.SelectedPricingCode, + CurrentPricingName = pricingEvaluation.SelectedPricingName, + PricingStayHours = pricingEvaluation.PricingStayHours, + IsPricingTimedOut = pricingEvaluation.IsPricingTimedOut, + EffectivePricingCode = pricingEvaluation.EffectivePricingCode, + EffectivePricingName = pricingEvaluation.EffectivePricingName, + LastCheckInTime = NormalizeStayDateTime(room?.LastCheckInTime), + EffectiveRoomRent = pricingEvaluation.EffectiveRoomRent, + EffectiveRoomDeposit = pricingEvaluation.EffectiveRoomDeposit, + PricingItems = pricingItems.OrderBy(a => a.Sort).ThenBy(a => a.PricingCode).ToList() + } + }; } - /// - /// 根据房间编号更改房间状态 - /// - /// - /// + public SingleOutputDto SelectNotClearRoomAllByRoomState() => CountByState(RoomState.Dirty, count => new ReadRoomOutputDto { Dirty = count }); + + public SingleOutputDto SelectFixingRoomAllByRoomState() => CountByState(RoomState.Maintenance, count => new ReadRoomOutputDto { Maintenance = count }); + + public SingleOutputDto SelectReservedRoomAllByRoomState() => CountByState(RoomState.Reserved, count => new ReadRoomOutputDto { Reserved = count }); + public BaseResponse UpdateRoomStateByRoomNo(UpdateRoomInputDto updateRoomInputDto) { try { - var room = roomRepository.GetFirst(a => a.RoomNumber == updateRoomInputDto.RoomNumber); - room.RoomStateId = updateRoomInputDto.RoomStateId; - room.RowVersion = updateRoomInputDto.RowVersion ?? 0; - var updateResult = roomRepository.Update(room); - if (!updateResult) + var roomResult = ResolveRoom(updateRoomInputDto); + if (roomResult.Room == null) { - return BaseResponseFactory.ConcurrencyConflict(); + return CreateRoomLookupFailure(roomResult, updateRoomInputDto?.RoomNumber, updateRoomInputDto?.RoomArea, updateRoomInputDto?.RoomFloor); } + + roomResult.Room.RoomStateId = updateRoomInputDto.RoomStateId; + roomResult.Room.RowVersion = updateRoomInputDto.RowVersion ?? 0; + return roomRepository.Update(roomResult.Room) ? new BaseResponse() : BaseResponseFactory.ConcurrencyConflict(); } catch (Exception ex) { - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return ErrorResponse(ex); } - return new BaseResponse(); } - /// - /// 添加房间 - /// - /// - /// public BaseResponse InsertRoom(CreateRoomInputDto rn) { try { - var isExist = roomRepository.IsAny(a => a.RoomNumber == rn.RoomNumber && a.IsDelete != 1); - if (isExist) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("This room already exists.", "房间已存在。"), Code = BusinessStatusCode.InternalServerError }; + var normalizedRoomNumber = rn.RoomNumber?.Trim(); + var normalizedRoomArea = RoomLocatorHelper.NormalizeArea(rn.RoomArea); + var duplicateExists = roomRepository.AsQueryable() + .Where(a => a.IsDelete != 1 && a.RoomNumber == normalizedRoomNumber && a.RoomFloor == rn.RoomFloor) + .ToList() + .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase)); + + if (duplicateExists) + { + return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("This room already exists.", "房间已存在"), Code = BusinessStatusCode.InternalServerError }; + } - var entity = EntityMapper.Map(rn); - entity.LastCheckInTime = DateOnly.MinValue; - entity.LastCheckOutTime = DateOnly.MinValue; + var entity = EntityMapper.Map(rn); + entity.RoomNumber = normalizedRoomNumber ?? string.Empty; + entity.RoomArea = string.IsNullOrWhiteSpace(normalizedRoomArea) ? string.Empty : normalizedRoomArea; + entity.LastCheckInTime = null; + entity.LastCheckOutTime = null; + entity.AppliedRoomRent = 0M; + entity.AppliedRoomDeposit = 0M; + entity.RoomPricingCode = string.Empty; + entity.RoomPricingName = string.Empty; + entity.PricingStayHours = null; + entity.PricingStartTime = null; + NormalizeRoom(entity); roomRepository.Insert(entity); + return new BaseResponse(); } catch (Exception ex) { logger.LogError(ex, "Error inserting room with room number {RoomNumber}", rn.RoomNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return ErrorResponse(ex); } - - return new BaseResponse(); } - /// - /// 更新房间 - /// - /// - /// public BaseResponse UpdateRoom(UpdateRoomInputDto rn) { try { - var isExist = roomRepository.IsAny(a => a.RoomNumber == rn.RoomNumber); - if (!isExist) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("This room does not exist.", "房间不存在。"), Code = BusinessStatusCode.InternalServerError }; + var roomResult = ResolveRoom(rn); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, rn?.RoomNumber, rn?.RoomArea, rn?.RoomFloor); + } + + var room = roomResult.Room; + var normalizedRoomNumber = rn.RoomNumber?.Trim(); + var normalizedRoomArea = RoomLocatorHelper.NormalizeArea(rn.RoomArea); + var duplicateExists = roomRepository.AsQueryable() + .Where(a => a.IsDelete != 1 && a.Id != room.Id && a.RoomNumber == normalizedRoomNumber && a.RoomFloor == rn.RoomFloor) + .ToList() + .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase)); - var entity = EntityMapper.Map(rn); - var updateResult = roomRepository.Update(entity); - if (!updateResult) + if (duplicateExists) { - return BaseResponseFactory.ConcurrencyConflict(); + return new BaseResponse { Message = "This room already exists.", Code = BusinessStatusCode.InternalServerError }; } + + room.RoomNumber = normalizedRoomNumber; + room.RoomArea = string.IsNullOrWhiteSpace(normalizedRoomArea) ? null : normalizedRoomArea; + room.RoomFloor = rn.RoomFloor; + room.RoomTypeId = rn.RoomTypeId; + room.CustomerNumber = rn.CustomerNumber; + room.LastCheckInTime = NormalizeStayDateTime(rn.LastCheckInTime); + room.LastCheckOutTime = NormalizeStayDateTime(rn.LastCheckOutTime); + room.RoomStateId = rn.RoomStateId; + room.RoomRent = rn.RoomRent; + room.RoomDeposit = rn.RoomDeposit; + room.RoomLocation = rn.RoomLocation; + room.RowVersion = rn.RowVersion ?? 0; + room.DataChgUsr = rn.DataChgUsr; + room.DataChgDate = rn.DataChgDate; + room.IsDelete = rn.IsDelete; + NormalizeRoom(room); + + return roomRepository.Update(room) ? new BaseResponse() : BaseResponseFactory.ConcurrencyConflict(); } catch (Exception ex) { logger.LogError(ex, "Error updating room with room number {RoomNumber}", rn.RoomNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("Failed to update room.", "更新房间失败。"), Code = BusinessStatusCode.InternalServerError }; + return ErrorResponse(ex); } - - return new BaseResponse(); } - /// - /// 删除房间 - /// - /// - /// public BaseResponse DeleteRoom(DeleteRoomInputDto rn) { try { if (rn?.DelIds == null || !rn.DelIds.Any()) { - return new BaseResponse - { - Code = BusinessStatusCode.BadRequest, - Message = LocalizationHelper.GetLocalizedString("Parameters Invalid", "参数错误") - }; + return new BaseResponse { Code = BusinessStatusCode.BadRequest, Message = "Parameters invalid" }; } var delIds = DeleteConcurrencyHelper.GetDeleteIds(rn); var rooms = roomRepository.GetList(a => delIds.Contains(a.Id)); - if (!rooms.Any()) { - return new BaseResponse - { - Code = BusinessStatusCode.NotFound, - Message = LocalizationHelper.GetLocalizedString("Room Information Not Found", "房间信息未找到") - }; + return new BaseResponse { Code = BusinessStatusCode.NotFound, Message = "Room information not found" }; } if (DeleteConcurrencyHelper.HasDeleteConflict(rn, rooms, a => a.Id, a => a.RowVersion)) @@ -629,295 +395,717 @@ namespace EOM.TSHotelManagement.Service return BaseResponseFactory.ConcurrencyConflict(); } - // 如果房间存在预约信息,则不允许删除 - var roomNumbers = rooms.Select(a => a.RoomNumber).ToList(); - var hasReservation = reserRepository.IsAny(a => roomNumbers.Contains(a.ReservationRoomNumber) && a.IsDelete != 1 && a.ReservationEndDate >= DateOnly.FromDateTime(DateTime.Today)); + var roomIds = rooms.Select(a => a.Id).ToList(); + var hasReservation = reserRepository.IsAny(a => + a.IsDelete != 1 && + a.ReservationEndDate >= DateOnly.FromDateTime(DateTime.Today) && + a.RoomId.HasValue && + roomIds.Contains(a.RoomId.Value)); if (hasReservation) { - return new BaseResponse - { - Code = BusinessStatusCode.Conflict, - Message = LocalizationHelper.GetLocalizedString("Cannot delete rooms with active reservations", "无法删除存在有效预约的房间") - }; + return new BaseResponse { Code = BusinessStatusCode.Conflict, Message = "Cannot delete rooms with active reservations" }; } - var result = roomRepository.SoftDeleteRange(rooms); - - return new BaseResponse(BusinessStatusCode.Success, LocalizationHelper.GetLocalizedString("Delete Room Success", "房间信息删除成功")); + roomRepository.SoftDeleteRange(rooms); + return new BaseResponse(BusinessStatusCode.Success, "Delete room success"); } catch (Exception ex) { - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return ErrorResponse(ex); } } - /// - /// 转房操作 - /// - /// - /// public BaseResponse TransferRoom(TransferRoomDto transferRoomDto) { try { - using (TransactionScope scope = new TransactionScope()) + if (transferRoomDto == null + || !transferRoomDto.OriginalRoomId.HasValue + || transferRoomDto.OriginalRoomId.Value <= 0 + || !transferRoomDto.TargetRoomId.HasValue + || transferRoomDto.TargetRoomId.Value <= 0) { - var customer = custoRepository.GetFirst(a => a.CustomerNumber == transferRoomDto.CustomerNumber && a.IsDelete != 1); - if (customer.IsNullOrEmpty()) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The customer does not exist", "客户不存在"), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse { Message = "OriginalRoomId and TargetRoomId are required", Code = BusinessStatusCode.BadRequest }; + } - var originalSpends = spendRepository.GetList(a => a.RoomNumber == transferRoomDto.OriginalRoomNumber - && a.CustomerNumber == transferRoomDto.CustomerNumber && a.SettlementStatus == ConsumptionConstant.UnSettle.Code - && a.IsDelete == 0).ToList(); + using var scope = CreateTransactionScope(); - var vipRules = vipLevelRuleRepository.GetList(a => a.IsDelete != 1).ToList(); + var customer = custoRepository.GetFirst(a => a.CustomerNumber == transferRoomDto.CustomerNumber && a.IsDelete != 1); + if (customer.IsNullOrEmpty()) + { + return new BaseResponse { Message = "The customer does not exist", Code = BusinessStatusCode.InternalServerError }; + } - var originalRoom = roomRepository.GetFirst(a => a.RoomNumber == transferRoomDto.OriginalRoomNumber); - if (originalRoom.CustomerNumber != transferRoomDto.CustomerNumber) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The customer does not match the original room", "客户与原房间不匹配"), Code = BusinessStatusCode.InternalServerError }; + var originalRoomResult = ResolveOriginalRoom(transferRoomDto); + if (originalRoomResult.Room == null) + { + return CreateRoomLookupFailure(originalRoomResult, transferRoomDto?.OriginalRoomNumber, transferRoomDto?.OriginalRoomArea, transferRoomDto?.OriginalRoomFloor); + } - if (!originalRoom.LastCheckInTime.HasValue) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The original room lacks check-in time", "原房间缺少入住时间"), Code = BusinessStatusCode.InternalServerError }; + var targetRoomResult = ResolveTargetRoom(transferRoomDto); + if (targetRoomResult.Room == null) + { + return CreateRoomLookupFailure(targetRoomResult, transferRoomDto?.TargetRoomNumber, transferRoomDto?.TargetRoomArea, transferRoomDto?.TargetRoomFloor); + } - var targetRoom = roomRepository.GetFirst(a => a.RoomNumber == transferRoomDto.TargetRoomNumber); - if (targetRoom.IsNullOrEmpty()) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The room does not exist", "房间不存在"), Code = BusinessStatusCode.InternalServerError }; - if (targetRoom.RoomStateId != (int)RoomState.Vacant) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The room is not vacant", "房间不处于空房状态"), Code = BusinessStatusCode.InternalServerError }; + var originalRoom = originalRoomResult.Room; + var targetRoom = targetRoomResult.Room; + if (originalRoom.CustomerNumber != transferRoomDto.CustomerNumber) + { + return new BaseResponse { Message = "The customer does not match the original room", Code = BusinessStatusCode.InternalServerError }; + } - var staySpan = DateTime.Now - originalRoom.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue); - var stayDays = Math.Max((int)Math.Ceiling(staySpan.TotalDays), 1); + var now = DateTime.Now; + var originalCheckInTime = NormalizeStayDateTime(originalRoom.LastCheckInTime); + if (!originalCheckInTime.HasValue) + { + return new BaseResponse { Message = "The original room lacks check-in time", Code = BusinessStatusCode.InternalServerError }; + } - var originalSpendNumbers = originalSpends.Select(a => a.SpendNumber).ToList(); - var TotalCountSpent = originalSpends.Sum(a => a.ConsumptionAmount); + if (targetRoom.RoomStateId != (int)RoomState.Vacant) + { + return new BaseResponse { Message = "The room is not vacant", Code = BusinessStatusCode.InternalServerError }; + } - var newLevelId = vipRules - .Where(vipRule => TotalCountSpent >= vipRule.RuleValue) - .OrderByDescending(vipRule => vipRule.RuleValue) - .ThenByDescending(vipRule => vipRule.VipLevelId) - .FirstOrDefault()?.VipLevelId ?? 0; + var originalSpends = spendRepository.GetList(a => + a.RoomId.HasValue && a.RoomId.Value == originalRoom.Id + && a.CustomerNumber == transferRoomDto.CustomerNumber + && a.SettlementStatus == ConsumptionConstant.UnSettle.Code + && a.IsDelete == 0); + + var stayDays = CalculateStayDays(originalCheckInTime.Value, now); + var totalSpent = originalSpends.Sum(a => a.ConsumptionAmount); + var vipRules = vipLevelRuleRepository.GetList(a => a.IsDelete != 1); + var newLevelId = vipRules + .Where(vipRule => totalSpent >= vipRule.RuleValue) + .OrderByDescending(vipRule => vipRule.RuleValue) + .ThenByDescending(vipRule => vipRule.VipLevelId) + .FirstOrDefault()?.VipLevelId ?? 0; + + if (newLevelId != 0) + { + custoRepository.Update(a => new Domain.Customer { CustomerType = newLevelId }, a => a.CustomerNumber == transferRoomDto.CustomerNumber); + } - if (newLevelId != 0) - { - custoRepository.Update(a => new Customer - { - CustomerType = newLevelId - }, a => a.CustomerNumber == transferRoomDto.CustomerNumber); - } + var customerType = custoTypeRepository.GetFirst(a => a.CustomerType == customer.CustomerType && a.IsDelete != 1); + if (customerType.IsNullOrEmpty()) + { + return new BaseResponse { Message = "The customer type does not exist", Code = BusinessStatusCode.InternalServerError }; + } - var customerType = custoTypeRepository.GetFirst(a => a.CustomerType == customer.CustomerType); - if (customerType.IsNullOrEmpty()) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The customer type does not exist", "客户类型不存在"), Code = BusinessStatusCode.InternalServerError }; - - decimal discount = (customerType != null && customerType.Discount > 0 && customerType.Discount < 100) - ? customerType.Discount / 100M - : 1M; - decimal originalRoomBill = originalRoom.RoomRent * stayDays * discount; - - //更新目标房间状态 - targetRoom.CustomerNumber = originalRoom.CustomerNumber; - targetRoom.RoomStateId = (int)RoomState.Occupied; - targetRoom.LastCheckInTime = DateOnly.FromDateTime(DateTime.Now); - var targetRoomUpdateResult = roomRepository.Update(targetRoom); - if (!targetRoomUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + var discount = customerType.Discount > 0 && customerType.Discount < 100 ? customerType.Discount / 100M : 1M; + var originalPricingEvaluation = EvaluateRoomPricing(originalRoom); + var originalEffectiveRent = originalPricingEvaluation.EffectiveRoomRent; + var originalRoomBill = originalEffectiveRent * stayDays * discount; + + targetRoom.CustomerNumber = originalRoom.CustomerNumber; + targetRoom.RoomStateId = (int)RoomState.Occupied; + targetRoom.LastCheckInTime = now; + targetRoom.AppliedRoomRent = originalRoom.AppliedRoomRent; + targetRoom.AppliedRoomDeposit = originalRoom.AppliedRoomDeposit; + targetRoom.RoomPricingCode = originalRoom.RoomPricingCode; + targetRoom.RoomPricingName = originalRoom.RoomPricingName; + targetRoom.PricingStayHours = originalRoom.PricingStayHours; + targetRoom.PricingStartTime = NormalizeStayDateTime(originalRoom.PricingStartTime) ?? originalCheckInTime; + if (!roomRepository.Update(targetRoom)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } - //更新原房间状态 - originalRoom.CustomerNumber = string.Empty; - originalRoom.RoomStateId = (int)RoomState.Dirty; - originalRoom.LastCheckInTime = DateOnly.MinValue; - originalRoom.LastCheckOutTime = DateOnly.MinValue; - var originalRoomUpdateResult = roomRepository.Update(originalRoom); - if (!originalRoomUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + originalRoom.CustomerNumber = string.Empty; + originalRoom.RoomStateId = (int)RoomState.Dirty; + originalRoom.LastCheckInTime = null; + originalRoom.LastCheckOutTime = now; + originalRoom.AppliedRoomRent = 0M; + originalRoom.AppliedRoomDeposit = 0M; + originalRoom.RoomPricingCode = string.Empty; + originalRoom.RoomPricingName = string.Empty; + originalRoom.PricingStayHours = null; + originalRoom.PricingStartTime = null; + if (!roomRepository.Update(originalRoom)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } - //转移原房间消费记录 - if (originalSpendNumbers.Count > 0) + if (originalSpends.Count > 0) + { + var originalSpendNumbers = originalSpends.Select(a => a.SpendNumber).ToList(); + var spends = spendRepository.AsQueryable().Where(a => originalSpendNumbers.Contains(a.SpendNumber)).ToList(); + spends.ForEach(spend => { - var originalSpendList = spendRepository.AsQueryable().Where(a => originalSpendNumbers.Contains(a.SpendNumber)).ToList(); - var spends = new List(); - - foreach (var spend in originalSpendList) - { - spend.SpendNumber = spend.SpendNumber; - spend.RoomNumber = transferRoomDto.TargetRoomNumber; - spends.Add(spend); - } - - var spendTransferResult = spendRepository.UpdateRange(spends); - if (!spendTransferResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } - } - - //添加旧房间消费记录 - var originalSpend = new Spend + spend.RoomId = targetRoom.Id; + spend.RoomNumber = targetRoom.RoomNumber; + }); + if (!spendRepository.UpdateRange(spends)) { - CustomerNumber = transferRoomDto.CustomerNumber, - RoomNumber = transferRoomDto.TargetRoomNumber, - SpendNumber = uniqueCode.GetNewId("SP-"), - ProductNumber = transferRoomDto.OriginalRoomNumber, - ProductName = "居住" + transferRoomDto.OriginalRoomNumber + "共" + stayDays + "天", - ProductPrice = originalRoom.RoomRent, - ConsumptionTime = DateTime.Now, - SettlementStatus = ConsumptionConstant.UnSettle.Code, - ConsumptionQuantity = stayDays, - ConsumptionAmount = originalRoomBill, - ConsumptionType = SpendTypeConstant.Room.Code, - IsDelete = 0 - }; - spendRepository.Insert(originalSpend); + return BaseResponseFactory.ConcurrencyConflict(); + } + } - scope.Complete(); + spendRepository.Insert(new Spend + { + CustomerNumber = transferRoomDto.CustomerNumber, + RoomId = targetRoom.Id, + RoomNumber = targetRoom.RoomNumber, + SpendNumber = uniqueCode.GetNewId("SP-"), + ProductNumber = originalRoom.RoomNumber, + ProductName = $"居住 {string.Join("/", originalRoom.RoomArea, originalRoom.RoomFloor, originalRoom.RoomNumber)} 共 {stayDays} 天", + ProductPrice = originalEffectiveRent, + ConsumptionTime = now, + SettlementStatus = ConsumptionConstant.UnSettle.Code, + ConsumptionQuantity = stayDays, + ConsumptionAmount = originalRoomBill, + ConsumptionType = SpendTypeConstant.Room.Code, + IsDelete = 0 + }); - } + scope.Complete(); + return new BaseResponse(); } catch (Exception ex) { - logger.LogError(ex, "Error transferring room from {OriginalRoomNumber} to {TargetRoomNumber} for customer {CustomerNumber}", transferRoomDto.OriginalRoomNumber, transferRoomDto.TargetRoomNumber, transferRoomDto.CustomerNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + logger.LogError(ex, "Error transferring room"); + return ErrorResponse(ex); } - return new BaseResponse(); } - /// - /// 退房操作 - /// - /// - /// public BaseResponse CheckoutRoom(CheckoutRoomDto checkoutRoomDto) { try { - using (TransactionScope scope = new TransactionScope()) + if (checkoutRoomDto == null || !checkoutRoomDto.RoomId.HasValue || checkoutRoomDto.RoomId.Value <= 0) { - var customer = custoRepository.AsQueryable().Where(a => a.CustomerNumber == checkoutRoomDto.CustomerNumber && a.IsDelete != 1); - if (!customer.Any()) - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The customer does not exist", "客户不存在"), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse { Message = "RoomId is required", Code = BusinessStatusCode.BadRequest }; + } - var room = roomRepository.GetFirst(a => a.RoomNumber == checkoutRoomDto.RoomNumber); - //更新房间状态 - room.CustomerNumber = string.Empty; - room.LastCheckOutTime = DateOnly.FromDateTime(DateTime.Now); - room.RoomStateId = (int)RoomState.Dirty; - var roomUpdateResult = roomRepository.Update(room); - if (!roomUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + using var scope = CreateTransactionScope(); - //添加能源使用记录 - var energy = new EnergyManagement - { - InformationId = uniqueCode.GetNewId("EM-"), - StartDate = (DateOnly)room.LastCheckInTime, - EndDate = DateOnly.FromDateTime((DateTime)checkoutRoomDto.DataChgDate), - WaterUsage = checkoutRoomDto.WaterUsage, - PowerUsage = checkoutRoomDto.ElectricityUsage, - Recorder = checkoutRoomDto.DataChgUsr, - CustomerNumber = room.CustomerNumber, - RoomNumber = checkoutRoomDto.RoomNumber, - IsDelete = 0 - }; - energyRepository.Insert(energy); + var customer = custoRepository.AsQueryable().Where(a => a.CustomerNumber == checkoutRoomDto.CustomerNumber && a.IsDelete != 1); + if (!customer.Any()) + { + return new BaseResponse { Message = "The customer does not exist", Code = BusinessStatusCode.InternalServerError }; + } + + var roomResult = ResolveRoom(checkoutRoomDto); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, checkoutRoomDto?.RoomNumber, checkoutRoomDto?.RoomArea, checkoutRoomDto?.RoomFloor); + } + + var room = roomResult.Room; + var now = DateTime.Now; + var checkinDate = NormalizeStayDateTime(room.LastCheckInTime); + var occupiedCustomerNumber = room.CustomerNumber; + var pricingEvaluation = EvaluateRoomPricing(room); + var effectiveRoomRent = pricingEvaluation.EffectiveRoomRent; + + room.CustomerNumber = string.Empty; + room.LastCheckInTime = null; + room.LastCheckOutTime = now; + room.RoomStateId = (int)RoomState.Dirty; + room.AppliedRoomRent = 0M; + room.AppliedRoomDeposit = 0M; + room.RoomPricingCode = string.Empty; + room.RoomPricingName = string.Empty; + room.PricingStayHours = null; + room.PricingStartTime = null; + if (!roomRepository.Update(room)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } - //结算消费记录 - var spendNumbers = spendRepository.GetList(a => a.RoomNumber == checkoutRoomDto.RoomNumber - && a.CustomerNumber.Equals(checkoutRoomDto.CustomerNumber) && a.SettlementStatus == ConsumptionConstant.UnSettle.Code - && a.IsDelete == 0).ToList(); - if (spendNumbers.Count > 0) + energyRepository.Insert(new EnergyManagement + { + InformationId = uniqueCode.GetNewId("EM-"), + StartDate = checkinDate ?? now, + EndDate = now, + WaterUsage = checkoutRoomDto.WaterUsage, + PowerUsage = checkoutRoomDto.ElectricityUsage, + Recorder = "System", + CustomerNumber = occupiedCustomerNumber, + RoomId = room.Id, + RoomNumber = room.RoomNumber, + IsDelete = 0 + }); + + var unsettledSpends = spendRepository.GetList(a => + a.RoomId.HasValue && a.RoomId.Value == room.Id + && a.CustomerNumber == checkoutRoomDto.CustomerNumber + && a.SettlementStatus == ConsumptionConstant.UnSettle.Code + && a.IsDelete == 0); + + if (unsettledSpends.Count > 0) + { + unsettledSpends.ForEach(spend => spend.SettlementStatus = ConsumptionConstant.Settled.Code); + if (!spendRepository.UpdateRange(unsettledSpends)) { - var spends = new List(); - foreach (var spend in spendNumbers) - { - spend.SettlementStatus = ConsumptionConstant.Settled.Code; - spends.Add(spend); - } - - var settleSpendResult = spendRepository.UpdateRange(spends); - if (!settleSpendResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + return BaseResponseFactory.ConcurrencyConflict(); } + } - scope.Complete(); + var stayDays = CalculateStayDays(checkinDate ?? now, now); + var customerType = custoTypeRepository.GetSingle(a => a.CustomerType == customer.First().CustomerType && a.IsDelete != 1); + if (customerType.IsNullOrEmpty()) + { + return new BaseResponse { Message = "The customer type does not exist", Code = BusinessStatusCode.InternalServerError }; } + + var discount = customerType.Discount > 0 && customerType.Discount < 100 ? customerType.Discount / 100M : 1M; + var roomBill = effectiveRoomRent * stayDays * discount; + + spendRepository.Insert(new Spend + { + SpendNumber = uniqueCode.GetNewId("SP-"), + ProductName = $"居住 {string.Join("/", room.RoomArea, room.RoomFloor, room.RoomNumber)} 共 {stayDays} 天", + SettlementStatus = ConsumptionConstant.Settled.Code, + ConsumptionType = SpendTypeConstant.Room.Code, + ConsumptionQuantity = stayDays, + ConsumptionTime = now, + ProductNumber = room.RoomNumber, + ProductPrice = effectiveRoomRent, + ConsumptionAmount = roomBill, + CustomerNumber = occupiedCustomerNumber, + RoomId = room.Id, + RoomNumber = room.RoomNumber, + IsDelete = 0 + }); + + scope.Complete(); + return new BaseResponse(); } catch (Exception ex) { - logger.LogError(ex, "Error checking out room number {RoomNumber} for customer {CustomerNumber}", checkoutRoomDto.RoomNumber, checkoutRoomDto.CustomerNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + logger.LogError(ex, "Error checking out room"); + return ErrorResponse(ex); } - return new BaseResponse(); } - /// - /// 根据预约信息办理入住 - /// - /// - /// public BaseResponse CheckinRoomByReservation(CheckinRoomByReservationDto checkinRoomByReservationDto) { try { - using (TransactionScope scope = new TransactionScope()) + if (checkinRoomByReservationDto == null + || !checkinRoomByReservationDto.RoomId.HasValue + || checkinRoomByReservationDto.RoomId.Value <= 0) { - var customer = new Customer - { - CustomerNumber = checkinRoomByReservationDto.CustomerNumber, - CustomerName = checkinRoomByReservationDto.CustomerName, - CustomerGender = checkinRoomByReservationDto.CustomerGender, - CustomerPhoneNumber = checkinRoomByReservationDto.CustomerPhoneNumber, - PassportId = checkinRoomByReservationDto.PassportId, - IdCardNumber = checkinRoomByReservationDto.IdCardNumber, - CustomerAddress = checkinRoomByReservationDto.CustomerAddress, - DateOfBirth = checkinRoomByReservationDto.DateOfBirth, - CustomerType = checkinRoomByReservationDto.CustomerType, - IsDelete = 0, - DataInsUsr = checkinRoomByReservationDto.DataInsUsr, - DataInsDate = checkinRoomByReservationDto.DataInsDate - }; - var customerResult = custoRepository.Insert(customer); - if (!customerResult) - { - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("Failed to add customer.", "添加客户失败。"), Code = BusinessStatusCode.InternalServerError }; - } + return new BaseResponse { Message = "RoomId is required", Code = BusinessStatusCode.BadRequest }; + } - var room = roomRepository.GetFirst(a => a.RoomNumber == checkinRoomByReservationDto.RoomNumber && a.IsDelete != 1); - room.LastCheckInTime = DateOnly.FromDateTime(DateTime.Now); - room.CustomerNumber = customer.CustomerNumber; - room.RoomStateId = new EnumHelper().GetEnumValue(RoomState.Occupied); - var roomUpdateResult = roomRepository.Update(room); + using var scope = CreateTransactionScope(); - if (!roomUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + var customer = new Domain.Customer + { + CustomerNumber = checkinRoomByReservationDto.CustomerNumber, + Name = checkinRoomByReservationDto.CustomerName, + Gender = checkinRoomByReservationDto.CustomerGender ?? 0, + PhoneNumber = checkinRoomByReservationDto.CustomerPhoneNumber, + IdCardType = checkinRoomByReservationDto.PassportId, + IdCardNumber = checkinRoomByReservationDto.IdCardNumber, + Address = checkinRoomByReservationDto.CustomerAddress, + DateOfBirth = checkinRoomByReservationDto.DateOfBirth, + CustomerType = checkinRoomByReservationDto.CustomerType, + IsDelete = 0, + DataInsUsr = checkinRoomByReservationDto.DataInsUsr, + DataInsDate = checkinRoomByReservationDto.DataInsDate + }; - var reser = reserRepository.GetFirst(a => a.ReservationId == checkinRoomByReservationDto.ReservationId && a.IsDelete != 1); - reser.IsDelete = 1; - var reserUpdateResult = reserRepository.Update(reser); + if (!custoRepository.Insert(customer)) + { + return new BaseResponse { Message = "Failed to add customer.", Code = BusinessStatusCode.InternalServerError }; + } - if (!reserUpdateResult) - { - return BaseResponseFactory.ConcurrencyConflict(); - } + var roomResult = ResolveRoom(checkinRoomByReservationDto); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, checkinRoomByReservationDto?.RoomNumber, checkinRoomByReservationDto?.RoomArea, checkinRoomByReservationDto?.RoomFloor); + } + + var room = roomResult.Room; + room.LastCheckInTime = DateTime.Now; + room.CustomerNumber = customer.CustomerNumber; + room.RoomStateId = EnumHelper.GetEnumValue(RoomState.Occupied); + var pricingResponse = ApplyPricingSelection(room, checkinRoomByReservationDto.PricingCode); + if (pricingResponse != null) + { + return pricingResponse; + } + if (!roomRepository.Update(room)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } + + var reser = reserRepository.GetFirst(a => a.ReservationId == checkinRoomByReservationDto.ReservationId && a.IsDelete != 1); + reser.ReservationStatus = 1; + reser.IsDelete = 1; + if (!reserRepository.Update(reser)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } + + scope.Complete(); + return new BaseResponse(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error checking in room by reservation"); + return ErrorResponse(ex); + } + } + + private ListOutputDto BuildRoomList(ReadRoomInputDto readRoomInputDto) + { + readRoomInputDto ??= new ReadRoomInputDto(); + + var where = SqlFilterBuilder.BuildExpression(readRoomInputDto); + var query = roomRepository.AsQueryable(); + var whereExpression = where.ToExpression(); + if (whereExpression != null) + { + query = query.Where(whereExpression); + } + + query = query.OrderBy(BuildRoomListOrderByClause()); + + var count = 0; + List rooms; + if (!readRoomInputDto.IgnorePaging) + { + var page = readRoomInputDto.Page > 0 ? readRoomInputDto.Page : 1; + var pageSize = readRoomInputDto.PageSize > 0 ? readRoomInputDto.PageSize : 15; + rooms = query.ToPageList(page, pageSize, ref count); + } + else + { + rooms = query.ToList(); + count = rooms.Count; + } + + rooms.ForEach(NormalizeRoom); + var roomTypeMap = BuildRoomTypeMap(); + var customerMap = BuildCustomerMap(rooms); + var roomStateMap = BuildRoomStateMap(); + + List result; + var useParallelProjection = readRoomInputDto.IgnorePaging && rooms.Count >= 200; + if (useParallelProjection) + { + var dtoArray = new ReadRoomOutputDto[rooms.Count]; + System.Threading.Tasks.Parallel.For(0, rooms.Count, i => + { + dtoArray[i] = MapRoomToOutput(rooms[i], roomTypeMap, customerMap, roomStateMap); + }); + result = dtoArray.ToList(); + } + else + { + result = rooms.Select(a => MapRoomToOutput(a, roomTypeMap, customerMap, roomStateMap)).ToList(); + } - scope.Complete(); + return new ListOutputDto + { + Data = new PagedData + { + Items = result, + TotalCount = count } + }; + } + + private SingleOutputDto CountByState(RoomState state, Func builder) + { + try + { + var count = roomRepository.Count(a => a.RoomStateId == (int)state && a.IsDelete != 1); + return new SingleOutputDto { Data = builder(count) }; } catch (Exception ex) { - logger.LogError(ex, "Error checking in room number {RoomNumber} by reservation ID {ReservationId}", checkinRoomByReservationDto.RoomNumber, checkinRoomByReservationDto.ReservationId); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return new SingleOutputDto { Code = BusinessStatusCode.InternalServerError, Message = ex.Message }; } - return new BaseResponse(); + } + + private RoomResolveResult ResolveRoom(ReadRoomInputDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.Id, inputDto?.RoomNumber, inputDto?.RoomArea, inputDto?.RoomFloor); + + private RoomResolveResult ResolveRoom(UpdateRoomInputDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.Id, inputDto?.RoomNumber, inputDto?.RoomArea, inputDto?.RoomFloor); + + private RoomResolveResult ResolveRoom(CheckoutRoomDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.RoomId, null, null, null); + + private RoomResolveResult ResolveRoom(CheckinRoomByReservationDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.RoomId, null, null, null); + + private RoomResolveResult ResolveOriginalRoom(TransferRoomDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.OriginalRoomId, null, null, null); + + private RoomResolveResult ResolveTargetRoom(TransferRoomDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.TargetRoomId, null, null, null); + + private static void NormalizeRoom(Domain.Room room) + { + if (room == null) + { + return; + } + + room.RoomNumber = room.RoomNumber?.Trim(); + room.RoomArea = string.IsNullOrWhiteSpace(room.RoomArea) ? null : RoomLocatorHelper.NormalizeArea(room.RoomArea); + room.RoomLocator = RoomLocatorHelper.BuildLocator(room.RoomArea, room.RoomFloor, room.RoomNumber); + } + + private static BaseResponse CreateRoomLookupFailure(RoomResolveResult result, string roomNumber, string roomArea, int? roomFloor) + { + var locator = RoomLocatorHelper.BuildLocator(roomArea, roomFloor, roomNumber); + if (result?.IsAmbiguous == true) + { + return new BaseResponse { Code = BusinessStatusCode.Conflict, Message = $"Multiple rooms match '{locator}'. Please specify room area or floor." }; + } + + if (string.IsNullOrWhiteSpace(locator)) + { + return new BaseResponse { Code = BusinessStatusCode.NotFound, Message = "RoomId was not found." }; + } + + return new BaseResponse { Code = BusinessStatusCode.NotFound, Message = $"Room '{locator}' was not found." }; + } + + private static SingleOutputDto CreateRoomLookupFailureOutput(RoomResolveResult result, string roomNumber, string roomArea, int? roomFloor) + { + var response = CreateRoomLookupFailure(result, roomNumber, roomArea, roomFloor); + return new SingleOutputDto { Code = response.Code, Message = response.Message, Data = new ReadRoomOutputDto() }; + } + + private string BuildRoomListOrderByClause() + { + var entityInfo = roomRepository.Context.EntityMaintenance.GetEntityInfo(); + var orderedColumns = new[] + { + nameof(Domain.Room.RoomArea), + nameof(Domain.Room.RoomFloor), + nameof(Domain.Room.RoomNumber) + }; + + return string.Join(", ", orderedColumns.Select(propertyName => + { + var columnInfo = entityInfo.Columns.FirstOrDefault(a => a.PropertyName == propertyName); + var columnName = columnInfo?.DbColumnName ?? propertyName; + return $"{columnName} asc"; + })); + } + + private Dictionary BuildRoomTypeMap() + { + return roomTypeRepository.AsQueryable() + .Where(a => a.IsDelete != 1) + .ToList() + .GroupBy(a => a.RoomTypeId) + .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.RoomTypeName ?? ""); + } + + private Dictionary BuildCustomerMap(List rooms) + { + var customerNumbers = rooms + .Select(a => a.CustomerNumber) + .Where(a => !a.IsNullOrEmpty()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (customerNumbers.Count == 0) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return custoRepository.AsQueryable() + .Where(a => a.IsDelete != 1 && customerNumbers.Contains(a.CustomerNumber)) + .ToList() + .GroupBy(a => a.CustomerNumber) + .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.Name ?? "", StringComparer.OrdinalIgnoreCase); + } + + private static Dictionary BuildRoomStateMap() + { + return Enum.GetValues(typeof(RoomState)).Cast().ToDictionary(e => (int)e, e => EnumHelper.GetEnumDescription(e) ?? ""); + } + + private static ReadRoomOutputDto MapRoomToOutput(Domain.Room source, Dictionary roomTypeMap, Dictionary customerMap, Dictionary roomStateMap) + { + var pricingEvaluation = EvaluateRoomPricing(source); + return new ReadRoomOutputDto + { + Id = source.Id, + RoomNumber = source.RoomNumber, + RoomArea = source.RoomArea ?? string.Empty, + RoomFloor = source.RoomFloor, + RoomLocator = RoomLocatorHelper.BuildLocator(source.RoomArea, source.RoomFloor, source.RoomNumber), + RoomTypeId = source.RoomTypeId, + RoomName = roomTypeMap.TryGetValue(source.RoomTypeId, out var roomTypeName) ? roomTypeName : "", + CustomerNumber = source.CustomerNumber ?? "", + CustomerName = customerMap.TryGetValue(source.CustomerNumber ?? "", out var customerName) ? customerName : "", + LastCheckInTime = NormalizeStayDateTime(source.LastCheckInTime), + LastCheckOutTime = NormalizeStayDateTime(source.LastCheckOutTime), + RoomStateId = source.RoomStateId, + RoomState = roomStateMap.TryGetValue(source.RoomStateId, out var roomStateName) ? roomStateName : "", + RoomRent = pricingEvaluation.EffectiveRoomRent, + RoomDeposit = pricingEvaluation.EffectiveRoomDeposit, + StandardRoomRent = source.RoomRent, + StandardRoomDeposit = source.RoomDeposit, + AppliedRoomRent = source.AppliedRoomRent, + AppliedRoomDeposit = source.AppliedRoomDeposit, + EffectiveRoomRent = pricingEvaluation.EffectiveRoomRent, + EffectiveRoomDeposit = pricingEvaluation.EffectiveRoomDeposit, + EffectivePricingCode = pricingEvaluation.EffectivePricingCode, + EffectivePricingName = pricingEvaluation.EffectivePricingName, + PricingCode = pricingEvaluation.SelectedPricingCode, + PricingName = pricingEvaluation.SelectedPricingName, + PricingStayHours = pricingEvaluation.PricingStayHours, + IsPricingTimedOut = pricingEvaluation.IsPricingTimedOut, + RoomLocation = source.RoomLocation, + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + } + + private static BaseResponse ErrorResponse(Exception ex) + { + return new BaseResponse { Message = ex.Message, Code = BusinessStatusCode.InternalServerError }; + } + + private BaseResponse ApplyPricingSelection(Domain.Room room, string pricingCode) + { + if (room == null || string.IsNullOrWhiteSpace(pricingCode)) + { + return null; + } + + var roomType = roomTypeRepository.GetFirst(a => a.RoomTypeId == room.RoomTypeId && a.IsDelete != 1); + if (roomType == null) + { + return new BaseResponse { Message = "Room type pricing not found", Code = BusinessStatusCode.NotFound }; + } + + var pricingItem = RoomPricingHelper.ResolvePricingItem(roomType.RoomRent, roomType.RoomDeposit, roomType.PricingItemsJson, pricingCode); + if (pricingItem == null) + { + return new BaseResponse { Message = "Pricing code not found", Code = BusinessStatusCode.BadRequest }; + } + + if (pricingItem.IsDefault) + { + room.AppliedRoomRent = 0M; + room.AppliedRoomDeposit = 0M; + room.RoomPricingCode = string.Empty; + room.RoomPricingName = string.Empty; + room.PricingStayHours = null; + room.PricingStartTime = null; + return null; + } + + room.AppliedRoomRent = pricingItem.RoomRent; + room.AppliedRoomDeposit = pricingItem.RoomDeposit; + room.RoomPricingCode = pricingItem.PricingCode; + room.RoomPricingName = pricingItem.PricingName; + room.PricingStayHours = pricingItem.StayHours > 0 ? pricingItem.StayHours : null; + room.PricingStartTime = room.PricingStayHours.HasValue + ? NormalizeStayDateTime(room.LastCheckInTime) ?? DateTime.Now + : null; + return null; + } + + private static decimal GetEffectiveRoomRent(Domain.Room room) + { + return EvaluateRoomPricing(room).EffectiveRoomRent; + } + + private static decimal GetEffectiveRoomDeposit(Domain.Room room) + { + return EvaluateRoomPricing(room).EffectiveRoomDeposit; + } + + private static RoomPricingEvaluation EvaluateRoomPricing(Domain.Room room) + { + if (room == null) + { + return new RoomPricingEvaluation + { + SelectedPricingCode = RoomPricingHelper.DefaultPricingCode, + SelectedPricingName = RoomPricingHelper.DefaultPricingName, + EffectivePricingCode = RoomPricingHelper.DefaultPricingCode, + EffectivePricingName = RoomPricingHelper.DefaultPricingName, + EffectiveRoomRent = 0M, + EffectiveRoomDeposit = 0M + }; + } + + var selectedPricingCode = string.IsNullOrWhiteSpace(room.RoomPricingCode) ? RoomPricingHelper.DefaultPricingCode : room.RoomPricingCode; + var selectedPricingName = string.IsNullOrWhiteSpace(room.RoomPricingName) ? RoomPricingHelper.DefaultPricingName : room.RoomPricingName; + var effectivePricingCode = RoomPricingHelper.DefaultPricingCode; + var effectivePricingName = RoomPricingHelper.DefaultPricingName; + var effectiveRoomRent = room.RoomRent; + var effectiveRoomDeposit = room.RoomDeposit; + var isPricingTimedOut = false; + + if (room.AppliedRoomRent > 0 || room.AppliedRoomDeposit > 0 || !string.IsNullOrWhiteSpace(room.RoomPricingCode)) + { + isPricingTimedOut = IsPricingTimedOut(room); + if (!isPricingTimedOut) + { + effectivePricingCode = selectedPricingCode; + effectivePricingName = selectedPricingName; + effectiveRoomRent = room.AppliedRoomRent > 0 ? room.AppliedRoomRent : room.RoomRent; + effectiveRoomDeposit = room.AppliedRoomDeposit > 0 ? room.AppliedRoomDeposit : room.RoomDeposit; + } + } + + return new RoomPricingEvaluation + { + SelectedPricingCode = selectedPricingCode, + SelectedPricingName = selectedPricingName, + EffectivePricingCode = effectivePricingCode, + EffectivePricingName = effectivePricingName, + EffectiveRoomRent = effectiveRoomRent, + EffectiveRoomDeposit = effectiveRoomDeposit, + PricingStayHours = room.PricingStayHours > 0 ? room.PricingStayHours : null, + IsPricingTimedOut = isPricingTimedOut + }; + } + + private static bool IsPricingTimedOut(Domain.Room room) + { + if (room?.PricingStayHours is not > 0) + { + return false; + } + + var pricingStartTime = NormalizeStayDateTime(room.PricingStartTime) + ?? NormalizeStayDateTime(room.LastCheckInTime); + if (!pricingStartTime.HasValue) + { + return false; + } + + var now = DateTime.Now; + return now > pricingStartTime.Value.AddHours(room.PricingStayHours.Value); + } + + private static TransactionScope CreateTransactionScope() + { + return new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted, + Timeout = TimeSpan.FromSeconds(30) + }); + } + + private static int CalculateStayDays(DateTime checkInTime, DateTime referenceTime) + { + var staySpan = referenceTime - checkInTime; + return Math.Max((int)Math.Ceiling(staySpan.TotalDays), 1); + } + + private static DateTime? NormalizeStayDateTime(DateTime? value) + { + return value.HasValue && value.Value > DateTime.MinValue ? value : null; } } } diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs index ebfadc02d561d37aa2b48284f76c78baa580dc97..b3d2d7b52aa6253e81fb48e85b7edfa1b073212c 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs @@ -62,7 +62,7 @@ namespace EOM.TSHotelManagement.Service readRoomTypeInputDto ??= new ReadRoomTypeInputDto(); var where = SqlFilterBuilder.BuildExpression(readRoomTypeInputDto); - var query = roomTypeRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = roomTypeRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { @@ -99,12 +99,13 @@ namespace EOM.TSHotelManagement.Service RoomTypeName = source.RoomTypeName, RoomRent = source.RoomRent, RoomDeposit = source.RoomDeposit, + PricingItems = RoomPricingHelper.BuildPricingItems(source.RoomRent, source.RoomDeposit, source.PricingItemsJson), DataInsUsr = source.DataInsUsr, DataInsDate = source.DataInsDate, DataChgUsr = source.DataChgUsr, DataChgDate = source.DataChgDate, RowVersion = source.RowVersion, - IsDelete = source.IsDelete + IsDelete = source.IsDelete, }; }); mapped = dtoArray.ToList(); @@ -121,6 +122,7 @@ namespace EOM.TSHotelManagement.Service RoomTypeName = source.RoomTypeName, RoomRent = source.RoomRent, RoomDeposit = source.RoomDeposit, + PricingItems = RoomPricingHelper.BuildPricingItems(source.RoomRent, source.RoomDeposit, source.PricingItemsJson), DataInsUsr = source.DataInsUsr, DataInsDate = source.DataInsDate, DataChgUsr = source.DataChgUsr, @@ -150,13 +152,19 @@ namespace EOM.TSHotelManagement.Service /// public SingleOutputDto SelectRoomTypeByRoomNo(ReadRoomTypeInputDto readRoomTypeInputDto) { - RoomType roomtype = new RoomType(); - Room room = new Room(); - room = roomRepository.GetFirst(a => a.RoomNumber == readRoomTypeInputDto.RoomNumber && a.IsDelete != 1); - roomtype.RoomTypeName = roomTypeRepository.GetFirst(a => a.RoomTypeId == room.RoomTypeId).RoomTypeName; - - var source = EntityMapper.Map(roomtype); + var roomResult = RoomLocatorHelper.Resolve(roomRepository, readRoomTypeInputDto?.Id, readRoomTypeInputDto?.RoomNumber, readRoomTypeInputDto?.RoomArea, readRoomTypeInputDto?.RoomFloor); + if (roomResult.Room == null) + { + return new SingleOutputDto + { + Code = roomResult.IsAmbiguous ? BusinessStatusCode.Conflict : BusinessStatusCode.NotFound, + Message = roomResult.IsAmbiguous ? "Multiple rooms match the current room number." : "Room not found.", + Data = new ReadRoomTypeOutputDto() + }; + } + var roomType = roomTypeRepository.GetFirst(a => a.RoomTypeId == roomResult.Room.RoomTypeId && a.IsDelete != 1) ?? new RoomType(); + var source = MapRoomTypeToOutput(roomType); return new SingleOutputDto { Data = source }; } #endregion @@ -173,7 +181,17 @@ namespace EOM.TSHotelManagement.Service var existRoomType = roomTypeRepository.IsAny(a => a.RoomTypeId == roomType.RoomTypeId); if (existRoomType) return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("This room type already exists.", "房间类型已存在。"), Code = BusinessStatusCode.InternalServerError }; - roomTypeRepository.Insert(EntityMapper.Map(roomType)); + roomTypeRepository.Insert(new RoomType + { + RoomTypeId = roomType.RoomTypeId, + RoomTypeName = roomType.RoomTypeName, + RoomRent = roomType.RoomRent, + RoomDeposit = roomType.RoomDeposit, + PricingItemsJson = RoomPricingHelper.SerializeAdditionalPricingItems(roomType.PricingItems), + IsDelete = roomType.IsDelete ?? 0, + DataInsUsr = roomType.DataInsUsr, + DataInsDate = roomType.DataInsDate + }); } catch (Exception ex) { @@ -192,18 +210,29 @@ namespace EOM.TSHotelManagement.Service { try { - var result = roomTypeRepository.Update(new RoomType + var targetRoomType = roomTypeRepository.GetFirst(a => a.Id == (roomType.Id ?? 0)); + if (targetRoomType == null) { - RoomTypeId = roomType.RoomTypeId, - Id = roomType.Id ?? 0, - RoomTypeName = roomType.RoomTypeName, - RoomRent = roomType.RoomRent, - RoomDeposit = roomType.RoomDeposit, - IsDelete = roomType.IsDelete, - DataChgUsr = roomType.DataChgUsr, - DataChgDate = roomType.DataChgDate, - RowVersion = roomType.RowVersion ?? 0 - }); + return new BaseResponse + { + Code = BusinessStatusCode.NotFound, + Message = LocalizationHelper.GetLocalizedString("Room Type Information Not Found", "房间类型信息未找到") + }; + } + + targetRoomType.RoomTypeId = roomType.RoomTypeId; + targetRoomType.RoomTypeName = roomType.RoomTypeName; + targetRoomType.RoomRent = roomType.RoomRent; + targetRoomType.RoomDeposit = roomType.RoomDeposit; + targetRoomType.PricingItemsJson = roomType.PricingItems == null + ? targetRoomType.PricingItemsJson + : RoomPricingHelper.SerializeAdditionalPricingItems(roomType.PricingItems); + targetRoomType.IsDelete = roomType.IsDelete; + targetRoomType.DataChgUsr = roomType.DataChgUsr; + targetRoomType.DataChgDate = roomType.DataChgDate; + targetRoomType.RowVersion = roomType.RowVersion ?? 0; + + var result = roomTypeRepository.Update(targetRoomType); if (!result) { return BaseResponseFactory.ConcurrencyConflict(); @@ -255,7 +284,7 @@ namespace EOM.TSHotelManagement.Service // 检查是否有房间关联到这些房间类型 var roomTypeIds = roomTypes.Select(rt => rt.RoomTypeId).ToList(); var associatedRooms = roomRepository.IsAny(r => roomTypeIds.Contains(r.RoomTypeId) && r.IsDelete != 1); - if (!associatedRooms) + if (associatedRooms) { return new BaseResponse { @@ -274,5 +303,32 @@ namespace EOM.TSHotelManagement.Service } return new BaseResponse(); } + + private static ReadRoomTypeOutputDto MapRoomTypeToOutput(RoomType source) + { + if (source == null) + { + return new ReadRoomTypeOutputDto + { + PricingItems = new List() + }; + } + + return new ReadRoomTypeOutputDto + { + Id = source.Id, + RoomTypeId = source.RoomTypeId, + RoomTypeName = source.RoomTypeName, + RoomRent = source.RoomRent, + RoomDeposit = source.RoomDeposit, + PricingItems = RoomPricingHelper.BuildPricingItems(source.RoomRent, source.RoomDeposit, source.PricingItemsJson), + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + } } } diff --git a/EOM.TSHotelManagement.Service/Business/Spend/ISpendService.cs b/EOM.TSHotelManagement.Service/Business/Spend/ISpendService.cs index bd9d152066a23999347526f525d71bb39bef3b2b..e94eb7232c1d3013475ac42be9394adbbd873ad9 100644 --- a/EOM.TSHotelManagement.Service/Business/Spend/ISpendService.cs +++ b/EOM.TSHotelManagement.Service/Business/Spend/ISpendService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -68,9 +68,9 @@ namespace EOM.TSHotelManagement.Service /// /// 撤回客户消费信息 /// - /// + /// /// - BaseResponse UndoCustomerSpend(UpdateSpendInputDto updateSpendInputDto); + BaseResponse UndoCustomerSpend(UndoCustomerSpendInputDto undoCustomerSpendInputDto); /// /// 添加客户消费信息 @@ -86,4 +86,4 @@ namespace EOM.TSHotelManagement.Service /// BaseResponse UpdSpendInfo(UpdateSpendInputDto updateSpendInputDto); } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs index 1fb1e461ea618bd5f4d50b8197efcc113570d41d..a64e4e62cd2b23b32a8031274210046fc9571d9d 100644 --- a/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs +++ b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs @@ -1,355 +1,165 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - */ using EOM.TSHotelManagement.Common; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; using jvncorelib.CodeLib; -using jvncorelib.EntityLib; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; using System.Transactions; namespace EOM.TSHotelManagement.Service { - /// - /// 商品消费接口实现类 - /// public class SpendService : ISpendService { - /// - /// 商品消费 - /// private readonly GenericRepository spendRepository; - - /// - /// 商品 - /// private readonly GenericRepository sellThingRepository; - - /// - /// 房间 - /// - private readonly GenericRepository roomRepository; - - /// - /// 客户 - /// - private readonly GenericRepository customerRepository; - - /// - /// 客户类型 - /// - private readonly GenericRepository custoTypeRepository; - - /// - /// 操作日志 - /// - - private readonly GenericRepository operationLogRepository; - - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger logger; - public SpendService(GenericRepository spendRepository, GenericRepository sellThingRepository, GenericRepository roomRepository, GenericRepository customerRepository, GenericRepository custoTypeRepository, GenericRepository operationLogRepository, IHttpContextAccessor httpContextAccessor, ILogger logger) + public SpendService( + GenericRepository spendRepository, + GenericRepository sellThingRepository, + GenericRepository roomRepository, + GenericRepository customerRepository, + GenericRepository custoTypeRepository, + ILogger logger) { this.spendRepository = spendRepository; this.sellThingRepository = sellThingRepository; this.roomRepository = roomRepository; this.customerRepository = customerRepository; this.custoTypeRepository = custoTypeRepository; - this.operationLogRepository = operationLogRepository; - _httpContextAccessor = httpContextAccessor; this.logger = logger; } - #region 根据客户编号查询历史消费信息 - /// - /// 根据客户编号查询历史消费信息 - /// - /// - /// - public ListOutputDto SeletHistorySpendInfoAll(ReadSpendInputDto readSpendInputDto) - { - readSpendInputDto ??= new ReadSpendInputDto(); + public ListOutputDto SeletHistorySpendInfoAll(ReadSpendInputDto readSpendInputDto) => BuildSpendList(readSpendInputDto); - var where = SqlFilterBuilder.BuildExpression(readSpendInputDto); - var query = spendRepository.AsQueryable().Where(a => a.IsDelete != 1); - var whereExpression = where.ToExpression(); - if (whereExpression != null) - { - query = query.Where(whereExpression); - } + public ListOutputDto SelectSpendByRoomNo(ReadSpendInputDto readSpendInputDto) => BuildSpendList(readSpendInputDto); - var count = 0; - List spends; - if (!readSpendInputDto.IgnorePaging) - { - var page = readSpendInputDto.Page > 0 ? readSpendInputDto.Page : 1; - var pageSize = readSpendInputDto.PageSize > 0 ? readSpendInputDto.PageSize : 15; - spends = query.ToPageList(page, pageSize, ref count); - } - else - { - spends = query.ToList(); - count = spends.Count; - } - var result = EntityMapper.MapList(spends); - var useParallelProjection = readSpendInputDto.IgnorePaging && result.Count >= 200; - if (useParallelProjection) - { - System.Threading.Tasks.Parallel.For(0, result.Count, i => FillSpendDerivedFields(result[i])); - } - else - { - result.ForEach(FillSpendDerivedFields); - } + public ListOutputDto SelectSpendInfoAll(ReadSpendInputDto readSpendInputDto) => BuildSpendList(readSpendInputDto, nameof(Spend.ConsumptionTime)); - return new ListOutputDto - { - Data = new PagedData - { - Items = result, - TotalCount = count - } - }; - } - #endregion - - #region 根据房间编号查询消费信息 - /// - /// 根据房间编号查询消费信息 - /// - /// - /// - public ListOutputDto SelectSpendByRoomNo(ReadSpendInputDto readSpendInputDto) + public SingleOutputDto SumConsumptionAmount(ReadSpendInputDto readSpendInputDto) { readSpendInputDto ??= new ReadSpendInputDto(); - var where = SqlFilterBuilder.BuildExpression(readSpendInputDto); - var query = spendRepository.AsQueryable().Where(a => a.IsDelete != 1); - var whereExpression = where.ToExpression(); - if (whereExpression != null) - { - query = query.Where(whereExpression); - } + var query = spendRepository.AsQueryable() + .Where(a => a.IsDelete != 1 && a.CustomerNumber == readSpendInputDto.CustomerNumber && a.SettlementStatus == ConsumptionConstant.UnSettle.Code); - var count = 0; - List spends; - if (!readSpendInputDto.IgnorePaging) + if (readSpendInputDto.RoomId.HasValue && readSpendInputDto.RoomId.Value > 0) { - var page = readSpendInputDto.Page > 0 ? readSpendInputDto.Page : 1; - var pageSize = readSpendInputDto.PageSize > 0 ? readSpendInputDto.PageSize : 15; - spends = query.ToPageList(page, pageSize, ref count); - } - else - { - spends = query.ToList(); - count = spends.Count; - } - var result = EntityMapper.MapList(spends); - var useParallelProjection = readSpendInputDto.IgnorePaging && result.Count >= 200; - if (useParallelProjection) - { - System.Threading.Tasks.Parallel.For(0, result.Count, i => FillSpendDerivedFields(result[i])); - } - else - { - result.ForEach(FillSpendDerivedFields); + query = query.Where(a => a.RoomId == readSpendInputDto.RoomId); } - return new ListOutputDto + var totalCountAmount = query.ToList().Sum(a => a.ConsumptionAmount); + return new SingleOutputDto { - Data = new PagedData + Data = new ReadSpendInputDto { - Items = result, - TotalCount = count + RoomId = readSpendInputDto.RoomId, + RoomNumber = readSpendInputDto.RoomNumber, + CustomerNumber = readSpendInputDto.CustomerNumber, + ConsumptionAmount = totalCountAmount } }; } - #endregion - - #region 查询消费的所有信息 - /// - /// 查询消费的所有信息 - /// - /// - public ListOutputDto SelectSpendInfoAll(ReadSpendInputDto readSpendInputDto) - { - readSpendInputDto ??= new ReadSpendInputDto(); - var where = SqlFilterBuilder.BuildExpression(readSpendInputDto, nameof(Spend.ConsumptionTime)); - var query = spendRepository.AsQueryable().Where(a => a.IsDelete != 1); - var whereExpression = where.ToExpression(); - if (whereExpression != null) - { - query = query.Where(whereExpression); - } - - var count = 0; - List spends; - if (!readSpendInputDto.IgnorePaging) - { - var page = readSpendInputDto.Page > 0 ? readSpendInputDto.Page : 1; - var pageSize = readSpendInputDto.PageSize > 0 ? readSpendInputDto.PageSize : 15; - spends = query.ToPageList(page, pageSize, ref count); - } - else - { - spends = query.ToList(); - count = spends.Count; - } - var result = EntityMapper.MapList(spends); - var useParallelProjection = readSpendInputDto.IgnorePaging && result.Count >= 200; - if (useParallelProjection) - { - System.Threading.Tasks.Parallel.For(0, result.Count, i => FillSpendDerivedFields(result[i])); - } - else - { - result.ForEach(FillSpendDerivedFields); - } + public BaseResponse UndoCustomerSpend(UndoCustomerSpendInputDto undoCustomerSpendInputDto) + { + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); - return new ListOutputDto + try { - Data = new PagedData + var existingSpend = spendRepository.GetFirst(a => a.Id == undoCustomerSpendInputDto.Id && a.IsDelete != 1); + if (existingSpend == null) { - Items = result, - TotalCount = count + return new BaseResponse(BusinessStatusCode.NotFound, "Spend record not found."); } - }; - } - #endregion - - private static void FillSpendDerivedFields(ReadSpendOutputDto item) - { - item.SettlementStatusDescription = item.SettlementStatus.IsNullOrEmpty() ? "" - : item.SettlementStatus.Equals(ConsumptionConstant.Settled.Code) ? "已结算" : "未结算"; - item.ProductPriceFormatted = item.ProductPrice.ToString("#,##0.00"); - item.ConsumptionAmountFormatted = item.ConsumptionAmount.ToString("#,##0.00"); - - item.ConsumptionTypeDescription = item.ConsumptionType == SpendTypeConstant.Product.Code ? SpendTypeConstant.Product.Description - : item.ConsumptionType == SpendTypeConstant.Room.Code ? SpendTypeConstant.Room.Description - : SpendTypeConstant.Other.Description; - } + if (existingSpend.ConsumptionType != SpendTypeConstant.Product.Code) + { + return new BaseResponse(BusinessStatusCode.BadRequest, "Only product spends can be canceled."); + } - #region 根据房间编号、入住时间到当前时间查询消费总金额 - /// - /// 根据房间编号、入住时间到当前时间查询消费总金额 - /// - /// - /// - public SingleOutputDto SumConsumptionAmount(ReadSpendInputDto readSpendInputDto) - { - var TotalCountAmount = spendRepository.GetList(a => a.RoomNumber == readSpendInputDto.RoomNumber && a.CustomerNumber == readSpendInputDto.CustomerNumber && a.SettlementStatus == ConsumptionConstant.UnSettle.Code).Sum(a => a.ConsumptionAmount); - return new SingleOutputDto { Data = new ReadSpendInputDto { ConsumptionAmount = TotalCountAmount } }; - } - #endregion - - /// - /// 撤回客户消费信息 - /// - /// - /// - public BaseResponse UndoCustomerSpend(UpdateSpendInputDto updateSpendInputDto) - { - try - { - var existingSpend = spendRepository.GetFirst(a => a.SpendNumber == updateSpendInputDto.SpendNumber && a.IsDelete != 1); - if (existingSpend == null) + if (!string.IsNullOrWhiteSpace(existingSpend.ProductNumber) && existingSpend.ConsumptionQuantity > 0) { - return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Spend record not found", "消费记录不存在")); + var product = sellThingRepository.GetFirst(a => a.ProductNumber == existingSpend.ProductNumber && a.IsDelete != 1); + if (product == null) + { + return new BaseResponse(BusinessStatusCode.NotFound, "Product not found."); + } + + product.Stock += existingSpend.ConsumptionQuantity; + if (!sellThingRepository.Update(product)) + { + return BaseResponseFactory.ConcurrencyConflict(); + } } + existingSpend.IsDelete = 1; - existingSpend.RowVersion = updateSpendInputDto.RowVersion ?? 0; - var updateResult = spendRepository.Update(existingSpend); - if (!updateResult) + existingSpend.RowVersion = undoCustomerSpendInputDto.RowVersion ?? 0; + if (!spendRepository.Update(existingSpend)) { return BaseResponseFactory.ConcurrencyConflict(); } + + scope.Complete(); + return new BaseResponse(); } catch (Exception ex) { - logger.LogError(ex, "撤回客户消费信息失败"); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + logger.LogError(ex, "Undo customer spend failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, ex.Message); } - return new BaseResponse(); } - /// - /// 添加客户消费信息 - /// - /// - /// public BaseResponse AddCustomerSpend(AddCustomerSpendInputDto addCustomerSpendInputDto) { - if (addCustomerSpendInputDto?.Quantity <= 0 || addCustomerSpendInputDto.Price <= 0) + if (addCustomerSpendInputDto?.ConsumptionQuantity <= 0 || addCustomerSpendInputDto.ProductPrice <= 0) { - return new BaseResponse() { Message = "商品数量和价格必须大于零", Code = BusinessStatusCode.BadRequest }; + return new BaseResponse(BusinessStatusCode.BadRequest, "Product quantity and price must be greater than zero."); } using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); try { - var room = roomRepository.AsQueryable().Single(a => a.RoomNumber == addCustomerSpendInputDto.RoomNumber); - if (room == null) + var roomResult = RoomReferenceHelper.Resolve(roomRepository, addCustomerSpendInputDto?.RoomId, addCustomerSpendInputDto?.RoomNumber); + if (roomResult.Room == null) { - return new BaseResponse() { Message = $"房间 '{addCustomerSpendInputDto.RoomNumber}' 不存在", Code = BusinessStatusCode.BadRequest }; + return CreateRoomLookupFailure(roomResult, addCustomerSpendInputDto?.RoomId, addCustomerSpendInputDto?.RoomNumber); } - var customer = customerRepository.AsQueryable().Single(a => a.CustomerNumber == room.CustomerNumber); + var room = roomResult.Room; + var customer = customerRepository.GetFirst(a => a.CustomerNumber == room.CustomerNumber && a.IsDelete != 1); if (customer == null) { - return new BaseResponse() { Message = $"客户 '{room.CustomerNumber}' 不存在", Code = BusinessStatusCode.BadRequest }; + return new BaseResponse(BusinessStatusCode.BadRequest, $"Customer '{room.CustomerNumber}' was not found."); } - var customerType = custoTypeRepository.AsQueryable().Single(a => a.CustomerType == customer.CustomerType); - decimal discount = (customerType != null && customerType.Discount > 0 && customerType.Discount < 100) - ? customerType.Discount / 100M - : 1M; - - decimal realAmount = addCustomerSpendInputDto.Price * addCustomerSpendInputDto.Quantity * discount; + var customerType = custoTypeRepository.GetFirst(a => a.CustomerType == customer.CustomerType && a.IsDelete != 1); + var discount = customerType != null && customerType.Discount > 0 && customerType.Discount < 100 + ? customerType.Discount / 100M + : 1M; - var existingSpend = spendRepository.AsQueryable().Single(a => a.RoomNumber == addCustomerSpendInputDto.RoomNumber && a.ProductNumber == addCustomerSpendInputDto.ProductNumber && a.IsDelete != 1 && a.SettlementStatus == ConsumptionConstant.UnSettle.Code); + var realAmount = addCustomerSpendInputDto.ProductPrice * addCustomerSpendInputDto.ConsumptionQuantity * discount; + var existingSpend = spendRepository.GetFirst(a => + a.IsDelete != 1 && + a.SettlementStatus == ConsumptionConstant.UnSettle.Code && + a.ProductNumber == addCustomerSpendInputDto.ProductNumber && + a.RoomId == room.Id); if (existingSpend != null) { + existingSpend.RoomId = room.Id; + existingSpend.RoomNumber = room.RoomNumber; existingSpend.ConsumptionType = SpendTypeConstant.Product.Code; - existingSpend.ConsumptionQuantity += addCustomerSpendInputDto.Quantity; + existingSpend.ConsumptionQuantity += addCustomerSpendInputDto.ConsumptionQuantity; existingSpend.ConsumptionAmount += realAmount; - existingSpend.DataChgDate = DateTime.Now; - existingSpend.DataChgUsr = addCustomerSpendInputDto.WorkerNo; - - var result = spendRepository.Update(existingSpend); - if (!result) + if (!spendRepository.Update(existingSpend)) { return BaseResponseFactory.ConcurrencyConflict(); } @@ -359,74 +169,47 @@ namespace EOM.TSHotelManagement.Service var newSpend = new Spend { SpendNumber = new UniqueCode().GetNewId("SP-"), - RoomNumber = addCustomerSpendInputDto.RoomNumber, + RoomId = room.Id, + RoomNumber = room.RoomNumber, ProductNumber = addCustomerSpendInputDto.ProductNumber, ProductName = addCustomerSpendInputDto.ProductName, - ConsumptionQuantity = addCustomerSpendInputDto.Quantity, + ConsumptionQuantity = addCustomerSpendInputDto.ConsumptionQuantity, CustomerNumber = room.CustomerNumber, - ProductPrice = addCustomerSpendInputDto.Price, + ProductPrice = addCustomerSpendInputDto.ProductPrice, ConsumptionAmount = realAmount, ConsumptionTime = DateTime.Now, ConsumptionType = SpendTypeConstant.Product.Code, - SettlementStatus = ConsumptionConstant.UnSettle.Code, - DataInsUsr = addCustomerSpendInputDto.WorkerNo, - DataInsDate = DateTime.Now + SettlementStatus = ConsumptionConstant.UnSettle.Code }; - var result = spendRepository.Insert(newSpend); - if (!result) + if (!spendRepository.Insert(newSpend)) { - return new BaseResponse() { Message = "添加消费记录失败", Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse(BusinessStatusCode.InternalServerError, "Failed to add spend record."); } } - var product = sellThingRepository.AsQueryable().Single(a => a.ProductNumber == addCustomerSpendInputDto.ProductNumber); - product.Stock = product.Stock - addCustomerSpendInputDto.Quantity; - var updateResult = sellThingRepository.Update(product); - if (!updateResult) + var product = sellThingRepository.GetFirst(a => a.ProductNumber == addCustomerSpendInputDto.ProductNumber && a.IsDelete != 1); + if (product == null) { - return BaseResponseFactory.ConcurrencyConflict(); + return new BaseResponse(BusinessStatusCode.BadRequest, $"Product '{addCustomerSpendInputDto.ProductNumber}' was not found."); } - var logContent = $"{addCustomerSpendInputDto.WorkerNo} 添加了消费记录: " + - $"房间 {addCustomerSpendInputDto.RoomNumber}, " + - $"商品 {addCustomerSpendInputDto.ProductName}, " + - $"数量 {addCustomerSpendInputDto.Quantity}, " + - $"金额 {realAmount.ToString("#,##0.00")}"; - - var context = _httpContextAccessor.HttpContext; - - var log = new OperationLog + product.Stock -= addCustomerSpendInputDto.ConsumptionQuantity; + if (!sellThingRepository.Update(product)) { - OperationId = new UniqueCode().GetNewId("OP-"), - OperationTime = Convert.ToDateTime(DateTime.Now), - LogContent = logContent, - LoginIpAddress = context.Connection.RemoteIpAddress?.ToString() ?? string.Empty, - OperationAccount = addCustomerSpendInputDto.WorkerNo, - LogLevel = (int)Common.LogLevel.Warning, - SoftwareVersion = addCustomerSpendInputDto.SoftwareVersion, - IsDelete = 0, - DataInsUsr = addCustomerSpendInputDto.WorkerNo, - DataInsDate = Convert.ToDateTime(DateTime.Now) - }; - operationLogRepository.Insert(log); + return BaseResponseFactory.ConcurrencyConflict(); + } scope.Complete(); - return new BaseResponse(); } catch (Exception ex) { - logger.LogError(ex, "添加客户消费信息失败"); - return new BaseResponse() { Message = $"添加消费记录失败,请稍后重试。{ex.Message}", Code = BusinessStatusCode.InternalServerError }; + logger.LogError(ex, "Add customer spend failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, $"Failed to add spend record. {ex.Message}"); } } - /// - /// 更新消费信息 - /// - /// - /// public BaseResponse UpdSpendInfo(UpdateSpendInputDto spend) { try @@ -434,28 +217,180 @@ namespace EOM.TSHotelManagement.Service var dbSpend = spendRepository.GetFirst(a => a.SpendNumber == spend.SpendNumber && a.IsDelete != 1); if (dbSpend == null) { - return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Spend record not found", "消费记录不存在")); + return new BaseResponse(BusinessStatusCode.NotFound, "Spend record not found."); + } + + Room room = null; + if (spend.RoomId.HasValue && spend.RoomId.Value > 0) + { + var roomResult = RoomReferenceHelper.Resolve(roomRepository, spend.RoomId, spend.RoomNumber); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, spend.RoomId, spend.RoomNumber); + } + + room = roomResult.Room; } + else if (!string.IsNullOrWhiteSpace(spend.RoomNumber)) + { + return new BaseResponse(BusinessStatusCode.BadRequest, "RoomId is required."); + } + dbSpend.SettlementStatus = spend.SettlementStatus; - dbSpend.RoomNumber = spend.RoomNumber; + dbSpend.RoomId = room?.Id ?? dbSpend.RoomId; + dbSpend.RoomNumber = room?.RoomNumber ?? dbSpend.RoomNumber; dbSpend.CustomerNumber = spend.CustomerNumber; dbSpend.ProductName = spend.ProductName; dbSpend.ConsumptionQuantity = spend.ConsumptionQuantity; + dbSpend.ProductPrice = spend.ProductPrice; dbSpend.ConsumptionAmount = spend.ConsumptionAmount; + dbSpend.ConsumptionTime = spend.ConsumptionTime; + dbSpend.ConsumptionType = spend.ConsumptionType; dbSpend.RowVersion = spend.RowVersion ?? 0; - var updateResult = spendRepository.Update(dbSpend); - if (!updateResult) + + return spendRepository.Update(dbSpend) ? new BaseResponse() : BaseResponseFactory.ConcurrencyConflict(); + } + catch (Exception ex) + { + logger.LogError(ex, "Update spend failed"); + return new BaseResponse(BusinessStatusCode.InternalServerError, ex.Message); + } + } + + private ListOutputDto BuildSpendList(ReadSpendInputDto readSpendInputDto, string dateFieldName = null) + { + readSpendInputDto ??= new ReadSpendInputDto(); + var filterInput = CreateSpendFilter(readSpendInputDto); + + var where = string.IsNullOrWhiteSpace(dateFieldName) + ? SqlFilterBuilder.BuildExpression(filterInput) + : SqlFilterBuilder.BuildExpression(filterInput, dateFieldName); + + var query = spendRepository.AsQueryable(); + var whereExpression = where.ToExpression(); + if (whereExpression != null) + { + query = query.Where(whereExpression); + } + + if (readSpendInputDto.RoomId.HasValue && readSpendInputDto.RoomId.Value > 0) + { + query = query.Where(a => a.RoomId == readSpendInputDto.RoomId.Value); + } + + var count = 0; + List spends; + if (readSpendInputDto.IgnorePaging) + { + spends = query.ToList(); + count = spends.Count; + } + else + { + var page = readSpendInputDto.Page > 0 ? readSpendInputDto.Page : 1; + var pageSize = readSpendInputDto.PageSize > 0 ? readSpendInputDto.PageSize : 15; + spends = query.ToPageList(page, pageSize, ref count); + } + + var rooms = RoomReferenceHelper.LoadRooms(roomRepository, spends.Select(a => a.RoomId), spends.Select(a => a.RoomNumber)); + var result = spends.Select(a => MapSpendToOutput(a, RoomReferenceHelper.FindRoom(rooms, a.RoomId, a.RoomNumber))).ToList(); + + result.ForEach(r => + { + r.SettlementStatusDescription = ConsumptionConstant.GetDescriptionByCode(r.SettlementStatus) ?? r.SettlementStatus; + r.ConsumptionTypeDescription = SpendTypeConstant.GetDescriptionByCode(r.ConsumptionType) ?? r.ConsumptionType; + }); + + return new ListOutputDto + { + Data = new PagedData { - return BaseResponseFactory.ConcurrencyConflict(); + Items = result, + TotalCount = count } + }; + } + + private static ReadSpendOutputDto MapSpendToOutput(Spend source, Room room) + { + var output = new ReadSpendOutputDto + { + Id = source.Id, + RoomId = source.RoomId ?? room?.Id, + SpendNumber = source.SpendNumber, + RoomNumber = room?.RoomNumber ?? source.RoomNumber, + RoomArea = RoomReferenceHelper.GetRoomArea(room), + RoomFloor = RoomReferenceHelper.GetRoomFloor(room), + RoomLocator = RoomReferenceHelper.GetRoomLocator(room), + CustomerNumber = source.CustomerNumber, + ProductNumber = source.ProductNumber, + ProductName = source.ProductName, + ConsumptionQuantity = source.ConsumptionQuantity, + ProductPrice = source.ProductPrice, + ConsumptionAmount = source.ConsumptionAmount, + ConsumptionTime = source.ConsumptionTime, + SettlementStatus = source.SettlementStatus, + ConsumptionType = source.ConsumptionType, + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + + FillSpendDerivedFields(output); + return output; + } + + private static void FillSpendDerivedFields(ReadSpendOutputDto item) + { + item.SettlementStatusDescription = string.IsNullOrWhiteSpace(item.SettlementStatus) + ? string.Empty + : item.SettlementStatus.Equals(ConsumptionConstant.Settled.Code, StringComparison.OrdinalIgnoreCase) ? "Settled" : "Unsettled"; + + item.ProductPriceFormatted = item.ProductPrice.ToString("#,##0.00"); + item.ConsumptionAmountFormatted = item.ConsumptionAmount.ToString("#,##0.00"); + item.ConsumptionTypeDescription = item.ConsumptionType == SpendTypeConstant.Product.Code + ? SpendTypeConstant.Product.Description + : item.ConsumptionType == SpendTypeConstant.Room.Code + ? SpendTypeConstant.Room.Description + : SpendTypeConstant.Other.Description; + } + + private static BaseResponse CreateRoomLookupFailure(RoomResolveResult result, int? roomId, string roomNumber) + { + if (!roomId.HasValue || roomId.Value <= 0) + { + return new BaseResponse(BusinessStatusCode.BadRequest, "RoomId is required."); } - catch (Exception ex) + + if (result?.IsAmbiguous == true) { - logger.LogError(ex, "更新消费信息失败"); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse(BusinessStatusCode.Conflict, $"Multiple rooms match room id '{roomId.Value}'."); } - return new BaseResponse(); + + return new BaseResponse(BusinessStatusCode.NotFound, $"RoomId '{roomId.Value}' was not found."); } + private static ReadSpendInputDto CreateSpendFilter(ReadSpendInputDto input) + { + return new ReadSpendInputDto + { + Page = input.Page, + PageSize = input.PageSize, + IgnorePaging = input.IgnorePaging, + SpendNumber = input.SpendNumber, + RoomId = null, + RoomNumber = null, + CustomerNumber = input.CustomerNumber, + ProductName = input.ProductName, + ConsumptionQuantity = input.ConsumptionQuantity, + ProductPrice = input.ProductPrice, + ConsumptionAmount = input.ConsumptionAmount, + SettlementStatus = input.SettlementStatus, + DateRangeDto = input.DateRangeDto + }; + } } } diff --git a/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs b/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs index 1517faa73d973d00bf2b3a26e92956db06e679b6..e7af58e7004fbc4b13a9c3355a39e157867bfc45 100644 --- a/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs +++ b/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs @@ -1,97 +1,15 @@ -using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; using jvncorelib.EntityLib; using System.Text; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.Dashboard { - public class DashboardService : IDashboardService + public class DashboardService(GenericRepository roomRepository, GenericRepository roomTypeRepository, GenericRepository reserRepository, GenericRepository customerRepository, GenericRepository custoTypeRepository, GenericRepository spendRepository, GenericRepository sellThingRepository, GenericRepository employeeRepository, GenericRepository departmentRepository, GenericRepository employeeCheckRepository, DataProtectionHelper dataProtector) : IDashboardService { - /// - /// 房间 - /// - private readonly GenericRepository roomRepository; - - /// - /// 房间 - /// - private readonly GenericRepository roomTypeRepository; - - /// - /// 预约 - /// - private readonly GenericRepository reserRepository; - - /// - /// 客户 - /// - private readonly GenericRepository customerRepository; - - /// - /// 客户类型 - /// - private readonly GenericRepository custoTypeRepository; - - /// - /// 消费信息 - /// - private readonly GenericRepository spendRepository; - - /// - /// 商品 - /// - private readonly GenericRepository sellThingRepository; - - /// - /// 员工 - /// - private readonly GenericRepository employeeRepository; - - /// - /// 部门 - /// - private readonly GenericRepository departmentRepository; - - /// - /// 考勤打卡 - /// - private readonly GenericRepository employeeCheckRepository; - - /// - /// 数据保护 - /// - private readonly DataProtectionHelper dataProtector; - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public DashboardService(GenericRepository roomRepository, GenericRepository roomTypeRepository, GenericRepository reserRepository, GenericRepository customerRepository, GenericRepository custoTypeRepository, GenericRepository spendRepository, GenericRepository sellThingRepository, GenericRepository employeeRepository, GenericRepository departmentRepository, GenericRepository employeeCheckRepository, DataProtectionHelper dataProtector) - { - this.roomRepository = roomRepository; - this.roomTypeRepository = roomTypeRepository; - this.reserRepository = reserRepository; - this.customerRepository = customerRepository; - this.custoTypeRepository = custoTypeRepository; - this.spendRepository = spendRepository; - this.sellThingRepository = sellThingRepository; - this.employeeRepository = employeeRepository; - this.departmentRepository = departmentRepository; - this.employeeCheckRepository = employeeCheckRepository; - this.dataProtector = dataProtector; - } /// /// 获取房间统计信息 @@ -103,20 +21,19 @@ namespace EOM.TSHotelManagement.Service try { - var helper = new EnumHelper(); var roomStates = Enum.GetValues(typeof(RoomState)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); var roomTypes = roomTypeRepository.AsQueryable().Where(a => a.IsDelete != 1).ToList(); var resers = reserRepository.AsQueryable() - .Where(a => a.IsDelete != 1).ToList(); + .Where(a => a.ReservationStatus == 0).ToList(); var roomStateData = roomRepository.AsQueryable().Where(a => roomStates.Select(b => b.Id).ToList().Contains(a.RoomStateId)).ToList(); @@ -145,7 +62,7 @@ namespace EOM.TSHotelManagement.Service .Where(a => a.RoomStateId == (int)RoomState.Reserved) .Select(a => { - var reservation = resers.SingleOrDefault(b => b.ReservationRoomNumber == a.RoomNumber); + var reservation = resers.SingleOrDefault(b => b.RoomId.HasValue && b.RoomId.Value == a.Id); var roomType = roomTypes.SingleOrDefault(b => b.RoomTypeId == a.RoomTypeId); return new TempReservationAlert { @@ -207,8 +124,8 @@ namespace EOM.TSHotelManagement.Service businessStatisticsOutputDto.GenderRatio = new TempGenderRatio { - Male = customers.Count(a => a.CustomerGender == (int)GenderType.Male), - Female = customers.Count(a => a.CustomerGender == (int)GenderType.Female) + Male = customers.Count(a => a.Gender == (int)GenderType.Male), + Female = customers.Count(a => a.Gender == (int)GenderType.Female) }; var memberTypeDict = customerTypes.ToDictionary(rt => rt.CustomerType, rt => rt.CustomerTypeName); diff --git a/EOM.TSHotelManagement.Service/EOM.TSHotelManagement.Service.csproj b/EOM.TSHotelManagement.Service/EOM.TSHotelManagement.Service.csproj index be64ba7f1203062da366e1a9b60b73ac61d5f349..0a7c44b825306868ed2f9c9343d595ac1638fd30 100644 --- a/EOM.TSHotelManagement.Service/EOM.TSHotelManagement.Service.csproj +++ b/EOM.TSHotelManagement.Service/EOM.TSHotelManagement.Service.csproj @@ -26,7 +26,7 @@ - + diff --git a/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs b/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs index 82e0d9a79be2bfb01830964f61d206713ffab6cc..4d697110d48155c3d1f8e8c6fb8ba8322f81d5ea 100644 --- a/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs +++ b/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -25,6 +25,8 @@ using EOM.TSHotelManagement.Common; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; +using jvncorelib.CodeLib; +using Microsoft.Extensions.Logging; namespace EOM.TSHotelManagement.Service { @@ -39,12 +41,14 @@ namespace EOM.TSHotelManagement.Service private readonly GenericRepository workerCheckRepository; /// - /// + /// 唯一编码 /// - /// - public EmployeeCheckService(GenericRepository workerCheckRepository) + private readonly UniqueCode uniqueCode; + + public EmployeeCheckService(GenericRepository workerCheckRepository, UniqueCode uniqueCode) { this.workerCheckRepository = workerCheckRepository; + this.uniqueCode = uniqueCode; } /// @@ -60,7 +64,7 @@ namespace EOM.TSHotelManagement.Service var count = 0; - if (wid.Page != 0 && wid.PageSize != 0) + if (!wid.IgnorePaging) { workerChecks = workerCheckRepository.AsQueryable().Where(where.ToExpression()) .OrderByDescending(a => a.CheckTime) @@ -74,12 +78,13 @@ namespace EOM.TSHotelManagement.Service count = workerChecks.Count; } - workerChecks.ForEach(source => + var source = EntityMapper.MapList(workerChecks); + + source.ForEach(source => { source.CheckStatusDescription = source.CheckStatus == 0 ? "早班" : "晚班"; + source.CheckMethodDescription = source.CheckMethod == CheckTypeConstant.Web.Code ? CheckTypeConstant.Web.Description : CheckTypeConstant.Client.Description; }); - var source = EntityMapper.MapList(workerChecks); - return new ListOutputDto { Data = new PagedData @@ -172,18 +177,85 @@ namespace EOM.TSHotelManagement.Service { try { - var entity = EntityMapper.Map(workerCheck); + if (workerCheck == null || string.IsNullOrWhiteSpace(workerCheck.EmployeeId)) + { + return new BaseResponse + { + Message = LocalizationHelper.GetLocalizedString( + "Employee ID is required.", + "员工工号为必填字段" + ), + Code = BusinessStatusCode.InternalServerError + }; + } + + var employeeId = workerCheck.EmployeeId.Trim(); + var now = DateTime.Now; + var isMorningShift = now.Hour < 12; + + var shiftStart = isMorningShift + ? now.Date + : now.Date.AddHours(12); + + var shiftEnd = isMorningShift + ? now.Date.AddHours(12) + : now.Date.AddDays(1); + + var shiftStatus = isMorningShift ? 0 : 1; + var shiftName = isMorningShift ? "早班" : "晚班"; + + var alreadyChecked = workerCheckRepository.IsAny(x => + x.EmployeeId == employeeId && + x.CheckStatus == shiftStatus && + x.CheckTime >= shiftStart && + x.CheckTime < shiftEnd + ); + + if (alreadyChecked) + { + return new BaseResponse + { + Message = LocalizationHelper.GetLocalizedString( + $"{shiftName} already checked in.", + $"{shiftName}已打卡" + ), + Code = BusinessStatusCode.InternalServerError + }; + } + + var entity = new EmployeeCheck + { + CheckNumber = uniqueCode.GetNewId("CK-"), + EmployeeId = employeeId, + CheckTime = now, + CheckMethod = CheckTypeConstant.Web.Code, + CheckStatus = shiftStatus + }; + var result = workerCheckRepository.Insert(entity); if (!result) { - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("insert employee check failed.", "员工打卡添加失败"), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse + { + Message = LocalizationHelper.GetLocalizedString( + "insert employee check failed.", + "员工打卡添加失败" + ), + Code = BusinessStatusCode.InternalServerError + }; } + + return new BaseResponse(); } catch (Exception ex) { - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse + { + Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), + Code = BusinessStatusCode.InternalServerError + }; } - return new BaseResponse(); } + } } diff --git a/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs b/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs index 205f18924d32a39375da08fdc0a64a19a2d9800e..2177cbf97922e1d5d080dd72a4180c8e2a116e35 100644 --- a/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs +++ b/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs @@ -22,16 +22,18 @@ * */ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; using SqlSugar; +using System.IdentityModel.Tokens.Jwt; using System.Net.Mail; using System.Security.Claims; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.Employee { /// /// 员工信息接口实现类 @@ -49,64 +51,8 @@ namespace EOM.TSHotelManagement.Service /// /// /// - public class EmployeeService(GenericRepository workerRepository, GenericRepository photoRepository, GenericRepository educationRepository, GenericRepository nationRepository, GenericRepository deptRepository, GenericRepository positionRepository, GenericRepository passportTypeRepository, JWTHelper jWTHelper, MailHelper mailHelper, DataProtectionHelper dataProtectionHelper, ITwoFactorAuthService twoFactorAuthService, ILogger logger) : IEmployeeService + public class EmployeeService(GenericRepository workerRepository, GenericRepository photoRepository, GenericRepository educationRepository, GenericRepository nationRepository, GenericRepository deptRepository, GenericRepository positionRepository, GenericRepository passportTypeRepository, JWTHelper jWTHelper, MailHelper mailHelper, DataProtectionHelper dataProtector, ITwoFactorAuthService twoFactorAuthService, ILogger logger) : IEmployeeService { - /// - /// 员工信息 - /// - private readonly GenericRepository workerRepository = workerRepository; - - /// - /// 员工照片 - /// - private readonly GenericRepository photoRepository = photoRepository; - - /// - /// 学历类型 - /// - private readonly GenericRepository educationRepository = educationRepository; - - /// - /// 民族类型 - /// - private readonly GenericRepository nationRepository = nationRepository; - - /// - /// 部门 - /// - private readonly GenericRepository deptRepository = deptRepository; - - /// - /// 职务 - /// - private readonly GenericRepository positionRepository = positionRepository; - - /// - /// 证件 - /// - private readonly GenericRepository passportTypeRepository = passportTypeRepository; - - /// - /// 数据保护 - /// - private readonly DataProtectionHelper dataProtector = dataProtectionHelper; - - /// - /// JWT加密 - /// - private readonly JWTHelper jWTHelper = jWTHelper; - - /// - /// 邮件助手 - /// - private readonly MailHelper mailHelper = mailHelper; - - /// - /// 2FA服务 - /// - private readonly ITwoFactorAuthService twoFactorAuthService = twoFactorAuthService; - - private readonly ILogger logger = logger; /// /// 修改员工信息 @@ -135,7 +81,7 @@ namespace EOM.TSHotelManagement.Service var password = workerRepository.GetFirst(a => a.EmployeeId == updateEmployeeInputDto.EmployeeId).Password; updateEmployeeInputDto.Password = password; - workerRepository.Update(EntityMapper.Map(updateEmployeeInputDto)); + workerRepository.Update(EntityMapper.Map(updateEmployeeInputDto)); } catch (Exception ex) { @@ -155,7 +101,7 @@ namespace EOM.TSHotelManagement.Service { try { - workerRepository.Update(a => new Employee() + workerRepository.Update(a => new Domain.Employee() { IsEnable = updateEmployeeInputDto.IsEnable, }, a => a.EmployeeId == updateEmployeeInputDto.EmployeeId); @@ -194,7 +140,7 @@ namespace EOM.TSHotelManagement.Service var newPassword = new RandomStringGenerator().GenerateSecurePassword(); sourcePwdStr = dataProtector.EncryptEmployeeData(newPassword); - var emailTemplate = EmailTemplate.GetNewRegistrationTemplate(createEmployeeInputDto.EmployeeName, newPassword); + var emailTemplate = EmailTemplate.GetNewRegistrationTemplate(createEmployeeInputDto.Name, newPassword); var result = mailHelper.SendMail(new List { createEmployeeInputDto.EmailAddress }, emailTemplate.Subject, emailTemplate.Body, new List { createEmployeeInputDto.EmailAddress }); if (!result) { @@ -205,7 +151,7 @@ namespace EOM.TSHotelManagement.Service createEmployeeInputDto.IdCardNumber = sourceIdStr; createEmployeeInputDto.Password = sourcePwdStr; - workerRepository.Insert(EntityMapper.Map(createEmployeeInputDto)); + workerRepository.Insert(EntityMapper.Map(createEmployeeInputDto)); } catch (Exception ex) @@ -224,8 +170,8 @@ namespace EOM.TSHotelManagement.Service { readEmployeeInputDto ??= new ReadEmployeeInputDto(); - var where = SqlFilterBuilder.BuildExpression(readEmployeeInputDto, nameof(Employee.HireDate)); - var query = workerRepository.AsQueryable().Where(a => a.IsDelete != 1); + var where = SqlFilterBuilder.BuildExpression(readEmployeeInputDto, nameof(Domain.Employee.HireDate)); + var query = workerRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { @@ -235,7 +181,7 @@ namespace EOM.TSHotelManagement.Service query = query.OrderBy(a => a.EmployeeId); var count = 0; - List employees; + List employees; if (!readEmployeeInputDto.IgnorePaging) { var page = readEmployeeInputDto.Page > 0 ? readEmployeeInputDto.Page : 1; @@ -264,13 +210,12 @@ namespace EOM.TSHotelManagement.Service .GroupBy(a => a.PassportId) .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.PassportName ?? ""); - var helper = new EnumHelper(); var genderMap = Enum.GetValues(typeof(GenderType)) .Cast() - .ToDictionary(e => (int)e, e => helper.GetEnumDescription(e) ?? ""); + .ToDictionary(e => (int)e, e => EnumHelper.GetEnumDescription(e) ?? ""); var politicalAffiliationMap = Enum.GetValues(typeof(PoliticalAffiliation)) .Cast() - .ToDictionary(e => e.ToString(), e => helper.GetEnumDescription(e) ?? "", StringComparer.OrdinalIgnoreCase); + .ToDictionary(e => e.ToString(), e => EnumHelper.GetEnumDescription(e) ?? "", StringComparer.OrdinalIgnoreCase); List data; var useParallelProjection = readEmployeeInputDto.IgnorePaging && employees.Count >= 200; @@ -279,88 +224,13 @@ namespace EOM.TSHotelManagement.Service var dtoArray = new ReadEmployeeOutputDto[employees.Count]; System.Threading.Tasks.Parallel.For(0, employees.Count, i => { - var source = employees[i]; - dtoArray[i] = new ReadEmployeeOutputDto - { - Id = source.Id, - EmployeeId = source.EmployeeId, - EmployeeName = source.EmployeeName, - Gender = source.Gender, - GenderName = genderMap.TryGetValue(source.Gender, out var genderName) ? genderName : "", - DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), - Ethnicity = source.Ethnicity, - EthnicityName = nations.TryGetValue(source.Ethnicity, out var ethnicityName) ? ethnicityName : "", - PhoneNumber = dataProtector.SafeDecryptEmployeeData(source.PhoneNumber), - Department = source.Department, - DepartmentName = departments.TryGetValue(source.Department, out var departmentName) ? departmentName : "", - Address = source.Address ?? "", - Position = source.Position, - PositionName = positions.TryGetValue(source.Position, out var positionName) ? positionName : "", - IdCardType = source.IdCardType, - IdCardTypeName = passports.TryGetValue(source.IdCardType, out var passportName) ? passportName : "", - IdCardNumber = dataProtector.SafeDecryptEmployeeData(source.IdCardNumber), - HireDate = source.HireDate.ToDateTime(TimeOnly.MinValue), - PoliticalAffiliation = source.PoliticalAffiliation, - PoliticalAffiliationName = politicalAffiliationMap.TryGetValue(source.PoliticalAffiliation ?? "", out var politicalAffiliationName) ? politicalAffiliationName : "", - EducationLevel = source.EducationLevel, - EducationLevelName = educations.TryGetValue(source.EducationLevel, out var educationName) ? educationName : "", - IsEnable = source.IsEnable, - IsInitialize = source.IsInitialize, - Password = source.Password, - EmailAddress = source.EmailAddress, - PhotoUrl = string.Empty, - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }; + dtoArray[i] = MapToEmployeeOutputDto(employees[i], genderMap, nations, departments, positions, passports, politicalAffiliationMap, educations); }); data = dtoArray.ToList(); } else { - data = new List(employees.Count); - employees.ForEach(source => - { - data.Add(new ReadEmployeeOutputDto - { - Id = source.Id, - EmployeeId = source.EmployeeId, - EmployeeName = source.EmployeeName, - Gender = source.Gender, - GenderName = genderMap.TryGetValue(source.Gender, out var genderName) ? genderName : "", - DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), - Ethnicity = source.Ethnicity, - EthnicityName = nations.TryGetValue(source.Ethnicity, out var ethnicityName) ? ethnicityName : "", - PhoneNumber = dataProtector.SafeDecryptEmployeeData(source.PhoneNumber), - Department = source.Department, - DepartmentName = departments.TryGetValue(source.Department, out var departmentName) ? departmentName : "", - Address = source.Address ?? "", - Position = source.Position, - PositionName = positions.TryGetValue(source.Position, out var positionName) ? positionName : "", - IdCardType = source.IdCardType, - IdCardTypeName = passports.TryGetValue(source.IdCardType, out var passportName) ? passportName : "", - IdCardNumber = dataProtector.SafeDecryptEmployeeData(source.IdCardNumber), - HireDate = source.HireDate.ToDateTime(TimeOnly.MinValue), - PoliticalAffiliation = source.PoliticalAffiliation, - PoliticalAffiliationName = politicalAffiliationMap.TryGetValue(source.PoliticalAffiliation ?? "", out var politicalAffiliationName) ? politicalAffiliationName : "", - EducationLevel = source.EducationLevel, - EducationLevelName = educations.TryGetValue(source.EducationLevel, out var educationName) ? educationName : "", - IsEnable = source.IsEnable, - IsInitialize = source.IsInitialize, - Password = source.Password, - EmailAddress = source.EmailAddress, - PhotoUrl = string.Empty, - DataInsUsr = source.DataInsUsr, - DataInsDate = source.DataInsDate, - DataChgUsr = source.DataChgUsr, - DataChgDate = source.DataChgDate, - RowVersion = source.RowVersion, - IsDelete = source.IsDelete - }); - }); + data = employees.Select(source => MapToEmployeeOutputDto(source, genderMap, nations, departments, positions, passports, politicalAffiliationMap, educations)).ToList(); } return new ListOutputDto @@ -373,6 +243,46 @@ namespace EOM.TSHotelManagement.Service }; } + private ReadEmployeeOutputDto MapToEmployeeOutputDto(Domain.Employee source, Dictionary genderMap, Dictionary nations, Dictionary departments, Dictionary positions, Dictionary passports, Dictionary politicalAffiliationMap, Dictionary educations) + { + return new ReadEmployeeOutputDto + { + Id = source.Id, + EmployeeId = source.EmployeeId, + Name = source.Name, + Gender = source.Gender, + GenderName = genderMap.TryGetValue(source.Gender, out var genderName) ? genderName : "", + DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), + Ethnicity = source.Ethnicity, + EthnicityName = nations.TryGetValue(source.Ethnicity, out var ethnicityName) ? ethnicityName : "", + PhoneNumber = dataProtector.SafeDecryptEmployeeData(source.PhoneNumber), + Department = source.Department, + DepartmentName = departments.TryGetValue(source.Department, out var departmentName) ? departmentName : "", + Address = source.Address ?? "", + Position = source.Position, + PositionName = positions.TryGetValue(source.Position, out var positionName) ? positionName : "", + IdCardType = source.IdCardType, + IdCardTypeName = passports.TryGetValue(source.IdCardType, out var passportName) ? passportName : "", + IdCardNumber = dataProtector.SafeDecryptEmployeeData(source.IdCardNumber), + HireDate = source.HireDate.ToDateTime(TimeOnly.MinValue), + PoliticalAffiliation = source.PoliticalAffiliation, + PoliticalAffiliationName = politicalAffiliationMap.TryGetValue(source.PoliticalAffiliation ?? "", out var politicalAffiliationName) ? politicalAffiliationName : "", + EducationLevel = source.EducationLevel, + EducationLevelName = educations.TryGetValue(source.EducationLevel, out var educationName) ? educationName : "", + IsEnable = source.IsEnable, + IsInitialize = source.IsInitialize, + Password = source.Password, + EmailAddress = source.EmailAddress, + PhotoUrl = string.Empty, + DataInsUsr = source.DataInsUsr, + DataInsDate = source.DataInsDate, + DataChgUsr = source.DataChgUsr, + DataChgDate = source.DataChgDate, + RowVersion = source.RowVersion, + IsDelete = source.IsDelete + }; + } + /// /// 根据登录名称查询员工信息 /// @@ -380,45 +290,44 @@ namespace EOM.TSHotelManagement.Service /// public SingleOutputDto SelectEmployeeInfoByEmployeeId(ReadEmployeeInputDto readEmployeeInputDto) { - Employee w = new Employee(); - var helper = new EnumHelper(); var genders = Enum.GetValues(typeof(GenderType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); - w = workerRepository.GetFirst(a => a.EmployeeId == readEmployeeInputDto.EmployeeId); + var w = workerRepository.GetFirst(a => a.EmployeeId == readEmployeeInputDto.EmployeeId); + + var source = EntityMapper.Map(w); + //解密身份证号码 var sourceStr = w.IdCardNumber.IsNullOrEmpty() ? "" : dataProtector.SafeDecryptEmployeeData(w.IdCardNumber); - w.IdCardNumber = sourceStr; + source.IdCardNumber = sourceStr; //解密联系方式 var sourceTelStr = w.PhoneNumber.IsNullOrEmpty() ? "" : dataProtector.SafeDecryptEmployeeData(w.PhoneNumber); - w.PhoneNumber = sourceTelStr; + source.PhoneNumber = sourceTelStr; //性别类型 - var sexType = genders.SingleOrDefault(a => a.Id == w.Gender); - w.GenderName = sexType.Description.IsNullOrEmpty() ? "" : sexType.Description; + var genderType = genders.SingleOrDefault(a => a.Id == w.Gender); + source.GenderName = genderType.Description.IsNullOrEmpty() ? "" : genderType.Description; //教育程度 var eduction = educationRepository.GetFirst(a => a.EducationNumber == w.EducationLevel); - w.EducationLevelName = eduction.EducationName.IsNullOrEmpty() ? "" : eduction.EducationName; + source.EducationLevelName = eduction.EducationName.IsNullOrEmpty() ? "" : eduction.EducationName; //民族类型 var nation = nationRepository.GetFirst(a => a.NationNumber == w.Ethnicity); - w.EthnicityName = nation.NationName.IsNullOrEmpty() ? "" : nation.NationName; + source.EthnicityName = nation.NationName.IsNullOrEmpty() ? "" : nation.NationName; //部门 var dept = deptRepository.GetFirst(a => a.DepartmentNumber == w.Department); - w.DepartmentName = dept.DepartmentName.IsNullOrEmpty() ? "" : dept.DepartmentName; + source.DepartmentName = dept.DepartmentName.IsNullOrEmpty() ? "" : dept.DepartmentName; //职位 var position = positionRepository.GetFirst(a => a.PositionNumber == w.Position); - w.PositionName = position.PositionName.IsNullOrEmpty() ? "" : position.PositionName; + source.PositionName = position.PositionName.IsNullOrEmpty() ? "" : position.PositionName; var passport = passportTypeRepository.GetFirst(a => a.PassportId == w.IdCardType); - w.IdCardTypeName = passport.IsNullOrEmpty() ? "" : passport.PassportName; + source.IdCardTypeName = passport.IsNullOrEmpty() ? "" : passport.PassportName; //面貌 - w.PoliticalAffiliationName = new EnumHelper().GetDescriptionByName(w.PoliticalAffiliation); - - var source = EntityMapper.Map(w); + source.PoliticalAffiliationName = EnumHelper.GetDescriptionByName(w.PoliticalAffiliation); var employeePhoto = photoRepository.GetFirst(a => a.EmployeeId.Equals(source.EmployeeId)); if (employeePhoto != null && !string.IsNullOrEmpty(employeePhoto.PhotoPath)) @@ -434,15 +343,14 @@ namespace EOM.TSHotelManagement.Service /// public SingleOutputDto EmployeeLogin(EmployeeLoginDto employeeLoginDto) { - Employee w = new Employee(); - var helper = new EnumHelper(); + Domain.Employee w = new Domain.Employee(); var genders = Enum.GetValues(typeof(GenderType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); w = workerRepository.GetFirst(a => a.EmployeeId == employeeLoginDto.EmployeeId || a.EmailAddress == employeeLoginDto.EmailAddress); @@ -470,7 +378,7 @@ namespace EOM.TSHotelManagement.Service Data = new ReadEmployeeOutputDto { EmployeeId = w.EmployeeId, - EmployeeName = w.EmployeeName, + Name = w.Name, RequiresTwoFactor = true } }; @@ -486,7 +394,7 @@ namespace EOM.TSHotelManagement.Service Data = new ReadEmployeeOutputDto { EmployeeId = w.EmployeeId, - EmployeeName = w.EmployeeName, + Name = w.Name, RequiresTwoFactor = true } }; @@ -494,33 +402,40 @@ namespace EOM.TSHotelManagement.Service } w.Password = ""; + + var output = EntityMapper.Map(w); + //性别类型 - var sexType = genders.SingleOrDefault(a => a.Id == w.Gender); - w.GenderName = sexType.Description.IsNullOrEmpty() ? "" : sexType.Description; + var genderType = genders.SingleOrDefault(a => a.Id == w.Gender); + output.GenderName = genderType.Description.IsNullOrEmpty() ? "" : genderType.Description; //教育程度 var eduction = educationRepository.GetFirst(a => a.EducationNumber == w.EducationLevel); - w.EducationLevelName = eduction.EducationName.IsNullOrEmpty() ? "" : eduction.EducationName; + output.EducationLevelName = eduction.EducationName.IsNullOrEmpty() ? "" : eduction.EducationName; //民族类型 var nation = nationRepository.GetFirst(a => a.NationNumber == w.Ethnicity); - w.EthnicityName = nation.NationName.IsNullOrEmpty() ? "" : nation.NationName; + output.EthnicityName = nation.NationName.IsNullOrEmpty() ? "" : nation.NationName; //部门 var dept = deptRepository.GetFirst(a => a.DepartmentNumber == w.Department); - w.DepartmentName = dept.DepartmentName.IsNullOrEmpty() ? "" : dept.DepartmentName; + output.DepartmentName = dept.DepartmentName.IsNullOrEmpty() ? "" : dept.DepartmentName; //职位 var position = positionRepository.GetFirst(a => a.PositionNumber == w.Position); - w.PositionName = position.PositionName.IsNullOrEmpty() ? "" : position.PositionName; + output.PositionName = position.PositionName.IsNullOrEmpty() ? "" : position.PositionName; - w.UserToken = jWTHelper.GenerateJWT(new ClaimsIdentity(new Claim[] + output.UserToken = jWTHelper.GenerateJWT(new ClaimsIdentity(new Claim[] { - new Claim(ClaimTypes.Name, w.EmployeeName), + new Claim(ClaimTypes.Name, w.Name), new Claim(ClaimTypes.SerialNumber, w.EmployeeId) })); - var output = EntityMapper.Map(w); + output.RefreshToken = jWTHelper.GenerateRefreshToken(new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.SerialNumber, w.EmployeeId), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + })); output.RequiresTwoFactor = false; output.UsedRecoveryCodeLogin = usedRecoveryCode; if (usedRecoveryCode) { - NotifyRecoveryCodeLoginByEmail(w.EmailAddress, w.EmployeeName, w.EmployeeId); + NotifyRecoveryCodeLoginByEmail(w.EmailAddress, w.Name, w.EmployeeId); } return new SingleOutputDto { Data = output }; } @@ -621,7 +536,7 @@ namespace EOM.TSHotelManagement.Service if (!employee.EmailAddress.IsNullOrEmpty()) { - var mailTemplate = EmailTemplate.GetUpdatePasswordTemplate(employee.EmployeeName, newPwd); + var mailTemplate = EmailTemplate.GetUpdatePasswordTemplate(employee.Name, newPwd); mailHelper.SendMail(new List { employee.EmailAddress }, mailTemplate.Subject, mailTemplate.Body, new List { employee.EmailAddress }); } @@ -664,7 +579,7 @@ namespace EOM.TSHotelManagement.Service }; } - var mailTemplate = EmailTemplate.GetResetPasswordTemplate(employee.EmployeeName, newPwd); + var mailTemplate = EmailTemplate.GetResetPasswordTemplate(employee.Name, newPwd); var result = mailHelper.SendMail(new List { emailAddress }, mailTemplate.Subject, mailTemplate.Body, diff --git a/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs b/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs index de93a8e636030ef69bebf78d696f0c0bda16e1e8..15edd025bed9941dc3d86e6a5783c4e1046e9eeb 100644 --- a/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs +++ b/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -65,6 +65,33 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse(); } + /// + /// 根据工号更新员工履历 + /// + /// + /// + public BaseResponse UpdateHistoryByEmployeeId(UpdateEmployeeHistoryInputDto workerHistory) + { + try + { + var existingHistory = workerHistoryRepository.GetSingle(a => a.Id == workerHistory.Id); + if (existingHistory == null) + { + return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("Cannot found Employee History", "找不到员工履历"), Code = BusinessStatusCode.NotFound }; + } + existingHistory.StartDate = workerHistory.StartDate; + existingHistory.EndDate = workerHistory.EndDate; + existingHistory.Company = workerHistory.Company; + existingHistory.Position = workerHistory.Position; + workerHistoryRepository.Update(existingHistory); + } + catch (Exception ex) + { + return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + } + return new BaseResponse(); + } + /// /// 根据工号查询履历信息 /// diff --git a/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs b/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs index bbcc6f50b8ad1ef55b9b15ee594c6df1e9b3f3b2..6f3fced5e4679dcde729ef24824d3d591762607e 100644 --- a/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs +++ b/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -37,6 +37,13 @@ namespace EOM.TSHotelManagement.Service /// BaseResponse AddHistoryByEmployeeId(CreateEmployeeHistoryInputDto workerHistory); + /// + /// 根据工号更新员工履历 + /// + /// + /// + BaseResponse UpdateHistoryByEmployeeId(UpdateEmployeeHistoryInputDto workerHistory); + /// /// 根据工号查询履历信息 /// @@ -44,4 +51,4 @@ namespace EOM.TSHotelManagement.Service /// ListOutputDto SelectHistoryByEmployeeId(ReadEmployeeHistoryInputDto wid); } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs b/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs index c59a2a8be30667ff0604f2ee1cc8dec3bec44e3f..987f951e6070a566a310091b6a49c6492c7b4a7d 100644 --- a/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs +++ b/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs @@ -15,23 +15,26 @@ namespace EOM.TSHotelManagement.Service /// public class EmployeePermissionService : IEmployeePermissionService { - private readonly GenericRepository employeeRepository; + private readonly GenericRepository employeeRepository; private readonly GenericRepository userRoleRepository; private readonly GenericRepository rolePermissionRepository; private readonly GenericRepository permissionRepository; + private readonly GenericRepository menuRepository; private readonly GenericRepository roleRepository; public EmployeePermissionService( - GenericRepository employeeRepository, + GenericRepository employeeRepository, GenericRepository userRoleRepository, GenericRepository rolePermissionRepository, GenericRepository permissionRepository, + GenericRepository menuRepository, GenericRepository roleRepository) { this.employeeRepository = employeeRepository; this.userRoleRepository = userRoleRepository; this.rolePermissionRepository = rolePermissionRepository; this.permissionRepository = permissionRepository; + this.menuRepository = menuRepository; this.roleRepository = roleRepository; } @@ -55,8 +58,8 @@ namespace EOM.TSHotelManagement.Service // 软删除当前用户下所有有效的角色绑定 var existing = userRoleRepository.AsQueryable() - .Where(x => x.UserNumber == input.UserNumber && x.IsDelete != 1) - .Select(x => new UserRole { UserNumber = x.UserNumber, RoleNumber = x.RoleNumber, IsDelete = 1 }) + .Where(x => x.UserNumber == input.UserNumber) + .Select(x => new UserRole { UserNumber = x.UserNumber, RoleNumber = x.RoleNumber }) .ToList(); foreach (var ur in existing) @@ -77,8 +80,7 @@ namespace EOM.TSHotelManagement.Service var entity = new UserRole { UserNumber = input.UserNumber, - RoleNumber = role, - IsDelete = 0 + RoleNumber = role }; userRoleRepository.Insert(entity); } @@ -108,7 +110,7 @@ namespace EOM.TSHotelManagement.Service try { var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToList(); @@ -158,7 +160,7 @@ namespace EOM.TSHotelManagement.Service { // 1) 用户 -> 角色 var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToList(); @@ -183,8 +185,10 @@ namespace EOM.TSHotelManagement.Service // 2) 角色 -> 权限编码(RolePermission) var rolePermList = rolePermissionRepository.AsQueryable() - .Where(rp => rp.IsDelete != 1 && roleNumbers.Contains(rp.RoleNumber)) - .Select(rp => new { rp.RoleNumber, rp.PermissionNumber }) + .Where(rp => roleNumbers.Contains(rp.RoleNumber) + && rp.PermissionNumber != null + && rp.PermissionNumber != "") + .Select(rp => new { rp.RoleNumber, rp.PermissionNumber, rp.MenuId }) .ToList(); var permNumbers = rolePermList @@ -194,30 +198,61 @@ namespace EOM.TSHotelManagement.Service .ToList(); // 3) 权限详情(Permission) - var permInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); + var permInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); if (permNumbers.Count > 0) { var info = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && permNumbers.Contains(p.PermissionNumber)) - .Select(p => new { p.PermissionNumber, p.PermissionName, p.MenuKey }) + .Where(p => permNumbers.Contains(p.PermissionNumber)) + .Select(p => new { p.PermissionNumber, p.PermissionName }) .ToList(); foreach (var p in info) { - permInfo[p.PermissionNumber] = (p.PermissionName, p.MenuKey ?? string.Empty); + permInfo[p.PermissionNumber] = p.PermissionName; } } // 4) 组装输出 + var menuIds = rolePermList + .Where(x => x.MenuId.HasValue && x.MenuId.Value > 0) + .Select(x => x.MenuId!.Value) + .Distinct() + .ToList(); + + var menuInfo = new Dictionary(); + if (menuIds.Count > 0) + { + var menus = menuRepository.AsQueryable() + .Where(m => m.IsDelete != 1 && menuIds.Contains(m.Id)) + .Select(m => new { m.Id, m.Key, m.Title }) + .ToList(); + + foreach (var m in menus) + { + menuInfo[m.Id] = (m.Key ?? string.Empty, m.Title ?? string.Empty); + } + } + var resultItems = rolePermList.Select(x => { - permInfo.TryGetValue(x.PermissionNumber, out var meta); + permInfo.TryGetValue(x.PermissionNumber!, out var permName); + var menuId = x.MenuId; + var menuKey = string.Empty; + var menuName = string.Empty; + if (menuId.HasValue && menuInfo.TryGetValue(menuId.Value, out var menuMeta)) + { + menuKey = menuMeta.Key; + menuName = menuMeta.Name; + } + return new UserRolePermissionOutputDto { RoleNumber = x.RoleNumber, - PermissionNumber = x.PermissionNumber, - PermissionName = meta.Name, - MenuKey = meta.MenuKey + PermissionNumber = x.PermissionNumber!, + PermissionName = permName, + MenuId = menuId, + MenuKey = menuKey, + MenuName = menuName }; }).ToList(); @@ -281,15 +316,13 @@ namespace EOM.TSHotelManagement.Service roleRepository.Insert(role); } - // 软删除当前专属角色下所有有效的角色-权限绑定 + // 硬删除当前专属角色下所有有效的角色-权限绑定 var existing = rolePermissionRepository.AsQueryable() - .Where(x => x.RoleNumber == userRoleNumber && x.IsDelete != 1) - .Select(x => new RolePermission { RoleNumber = x.RoleNumber, PermissionNumber = x.PermissionNumber, IsDelete = 1 }) + .Where(x => x.RoleNumber == userRoleNumber) .ToList(); - - foreach (var rp in existing) + if (existing.Count > 0) { - rolePermissionRepository.SoftDelete(rp); + rolePermissionRepository.Delete(existing); } // 过滤、去重、忽略空白 @@ -303,7 +336,7 @@ namespace EOM.TSHotelManagement.Service { // 仅保留系统中存在的权限码 var validPerms = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && perms.Contains(p.PermissionNumber)) + .Where(p => perms.Contains(p.PermissionNumber)) .Select(p => p.PermissionNumber) .ToList(); @@ -312,22 +345,20 @@ namespace EOM.TSHotelManagement.Service var entity = new RolePermission { RoleNumber = userRoleNumber, - PermissionNumber = pnum, - IsDelete = 0 + PermissionNumber = pnum }; rolePermissionRepository.Insert(entity); } } // 确保用户与专属角色绑定存在 - var hasBinding = userRoleRepository.IsAny(ur => ur.UserNumber == input.UserNumber && ur.RoleNumber == userRoleNumber && ur.IsDelete != 1); + var hasBinding = userRoleRepository.IsAny(ur => ur.UserNumber == input.UserNumber && ur.RoleNumber == userRoleNumber); if (!hasBinding) { userRoleRepository.Insert(new UserRole { UserNumber = input.UserNumber, - RoleNumber = userRoleNumber, - IsDelete = 0 + RoleNumber = userRoleNumber }); } @@ -357,7 +388,7 @@ namespace EOM.TSHotelManagement.Service { var roleNumber = $"R-USER-{userNumber}"; var list = rolePermissionRepository.AsQueryable() - .Where(rp => rp.RoleNumber == roleNumber && rp.IsDelete != 1) + .Where(rp => rp.RoleNumber == roleNumber) .Select(rp => rp.PermissionNumber) .ToList(); @@ -381,4 +412,4 @@ namespace EOM.TSHotelManagement.Service } } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Employee/RewardPunishment/RewardPunishmentService.cs b/EOM.TSHotelManagement.Service/Employee/RewardPunishment/RewardPunishmentService.cs index 28546682e753ee71482bb32573e6f0b141fe70d3..079de15f5d3eec28e9bb3d7d35da47c97959c09a 100644 --- a/EOM.TSHotelManagement.Service/Employee/RewardPunishment/RewardPunishmentService.cs +++ b/EOM.TSHotelManagement.Service/Employee/RewardPunishment/RewardPunishmentService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -102,7 +102,7 @@ namespace EOM.TSHotelManagement.Service var count = 0; - if (wn.Page != 0 && wn.PageSize != 0) + if (!wn.IgnorePaging) { gb = rewardPunishmentRepository.AsQueryable().Where(where.ToExpression()) .OrderByDescending(a => a.RewardPunishmentTime) diff --git a/EOM.TSHotelManagement.Service/Security/TwoFactorAuthService.cs b/EOM.TSHotelManagement.Service/Security/TwoFactorAuthService.cs index 72b8645c979eea4bc4e798350f1164ef30eaa4e8..eaca844c5d982f561be5f4d75750adc35f823df9 100644 --- a/EOM.TSHotelManagement.Service/Security/TwoFactorAuthService.cs +++ b/EOM.TSHotelManagement.Service/Security/TwoFactorAuthService.cs @@ -13,7 +13,7 @@ namespace EOM.TSHotelManagement.Service { private readonly GenericRepository _twoFactorRepository; private readonly GenericRepository _recoveryCodeRepository; - private readonly GenericRepository _employeeRepository; + private readonly GenericRepository _employeeRepository; private readonly GenericRepository _administratorRepository; private readonly GenericRepository _customerAccountRepository; private readonly DataProtectionHelper _dataProtectionHelper; @@ -26,7 +26,7 @@ namespace EOM.TSHotelManagement.Service public TwoFactorAuthService( GenericRepository twoFactorRepository, GenericRepository recoveryCodeRepository, - GenericRepository employeeRepository, + GenericRepository employeeRepository, GenericRepository administratorRepository, GenericRepository customerAccountRepository, DataProtectionHelper dataProtectionHelper, diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs index 6ad64760ef5fa594d9687a9862abc6c1a36853be..e9f9e44e4249a5ea8fe8a833aad5ccfb1016dffe 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs @@ -28,6 +28,7 @@ using EOM.TSHotelManagement.Domain; using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; using SqlSugar; +using System.IdentityModel.Tokens.Jwt; using System.Net.Mail; using System.Security.Claims; @@ -63,6 +64,11 @@ namespace EOM.TSHotelManagement.Service /// private readonly GenericRepository permissionRepository; + /// + /// 菜单 + /// + private readonly GenericRepository menuRepository; + /// /// 角色 /// @@ -90,13 +96,14 @@ namespace EOM.TSHotelManagement.Service private readonly ILogger logger; - public AdminService(GenericRepository adminRepository, GenericRepository adminTypeRepository, GenericRepository userRoleRepository, GenericRepository rolePermissionRepository, GenericRepository permissionRepository, GenericRepository roleRepository, DataProtectionHelper dataProtector, JWTHelper jWTHelper, ITwoFactorAuthService twoFactorAuthService, MailHelper mailHelper, ILogger logger) + public AdminService(GenericRepository adminRepository, GenericRepository adminTypeRepository, GenericRepository userRoleRepository, GenericRepository rolePermissionRepository, GenericRepository permissionRepository, GenericRepository menuRepository, GenericRepository roleRepository, DataProtectionHelper dataProtector, JWTHelper jWTHelper, ITwoFactorAuthService twoFactorAuthService, MailHelper mailHelper, ILogger logger) { this.adminRepository = adminRepository; this.adminTypeRepository = adminTypeRepository; this.userRoleRepository = userRoleRepository; this.rolePermissionRepository = rolePermissionRepository; this.permissionRepository = permissionRepository; + this.menuRepository = menuRepository; this.roleRepository = roleRepository; this.dataProtector = dataProtector; this.jWTHelper = jWTHelper; @@ -191,14 +198,20 @@ namespace EOM.TSHotelManagement.Service } existingAdmin.Password = string.Empty; - existingAdmin.UserToken = jWTHelper.GenerateJWT(new ClaimsIdentity(new Claim[] + + var source = EntityMapper.Map(existingAdmin); + source.UserToken = jWTHelper.GenerateJWT(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, existingAdmin.Name), new Claim(ClaimTypes.SerialNumber, existingAdmin.Number), new Claim("is_super_admin", existingAdmin.IsSuperAdmin.ToString()) })); + source.RefreshToken = jWTHelper.GenerateRefreshToken(new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.SerialNumber, existingAdmin.Number), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + })); - var source = EntityMapper.Map(existingAdmin); source.RequiresTwoFactor = false; source.UsedRecoveryCodeLogin = usedRecoveryCode; if (usedRecoveryCode) @@ -270,7 +283,7 @@ namespace EOM.TSHotelManagement.Service readAdministratorInputDto ??= new ReadAdministratorInputDto(); var where = SqlFilterBuilder.BuildExpression(readAdministratorInputDto); - var query = adminRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = adminRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { @@ -374,7 +387,7 @@ namespace EOM.TSHotelManagement.Service { if (createAdministratorInputDto.IsSuperAdmin == (int)AdminRole.SuperAdmin) { - var haveSuperAdmin = adminRepository.IsAny(a => a.IsSuperAdmin == 1 && a.IsDelete != 1); + var haveSuperAdmin = adminRepository.IsAny(a => a.IsSuperAdmin == 1); if (haveSuperAdmin) { return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("Super Administrator already exists", "超级管理员已存在"), Code = BusinessStatusCode.InternalServerError }; @@ -490,7 +503,7 @@ namespace EOM.TSHotelManagement.Service readAdministratorTypeInputDto ??= new ReadAdministratorTypeInputDto(); var where = SqlFilterBuilder.BuildExpression(readAdministratorTypeInputDto); - var query = adminTypeRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = adminTypeRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { @@ -639,7 +652,7 @@ namespace EOM.TSHotelManagement.Service } // cannot be delete if have administrators - var haveAdmin = adminRepository.IsAny(a => administratorTypes.Select(a => a.TypeId).Contains(a.Type) && a.IsDelete != 1); + var haveAdmin = adminRepository.IsAny(a => administratorTypes.Select(a => a.TypeId).Contains(a.Type)); if (haveAdmin) { return new BaseResponse @@ -675,21 +688,21 @@ namespace EOM.TSHotelManagement.Service try { // 校验用户是否存在且未删除 - var userExists = adminRepository.IsAny(a => a.Number == input.UserNumber && a.IsDelete != 1); + var userExists = adminRepository.IsAny(a => a.Number == input.UserNumber); if (!userExists) { return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("User not found", "用户不存在")); } - // 软删除当前用户下所有有效的角色绑定 + // 硬删除当前用户下所有有效的角色绑定 var existing = userRoleRepository.AsQueryable() - .Where(x => x.UserNumber == input.UserNumber && x.IsDelete != 1) - .Select(x => new UserRole { UserNumber = x.UserNumber, RoleNumber = x.RoleNumber, IsDelete = 1 }) + .Where(x => x.UserNumber == input.UserNumber) + .Select(x => new UserRole { UserNumber = x.UserNumber, RoleNumber = x.RoleNumber }) .ToList(); foreach (var ur in existing) { - userRoleRepository.SoftDelete(ur); + userRoleRepository.Delete(ur); } // 过滤、去重、忽略空白 @@ -705,8 +718,7 @@ namespace EOM.TSHotelManagement.Service var entity = new UserRole { UserNumber = input.UserNumber, - RoleNumber = role, - IsDelete = 0 + RoleNumber = role }; userRoleRepository.Insert(entity); } @@ -740,7 +752,7 @@ namespace EOM.TSHotelManagement.Service try { var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToList(); @@ -793,7 +805,7 @@ namespace EOM.TSHotelManagement.Service { // 1) 用户 -> 角色 var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) + .Where(ur => ur.UserNumber == userNumber) .Select(ur => ur.RoleNumber) .ToList(); @@ -816,10 +828,12 @@ namespace EOM.TSHotelManagement.Service }; } - // 2) 角色 -> 权限编码(RolePermission) + // 2) 角色 -> 权限映射(RolePermission) var rolePermList = rolePermissionRepository.AsQueryable() - .Where(rp => rp.IsDelete != 1 && roleNumbers.Contains(rp.RoleNumber)) - .Select(rp => new { rp.RoleNumber, rp.PermissionNumber }) + .Where(rp => roleNumbers.Contains(rp.RoleNumber) + && rp.PermissionNumber != null + && rp.PermissionNumber != "") + .Select(rp => new { rp.RoleNumber, rp.PermissionNumber, rp.MenuId }) .ToList(); var permNumbers = rolePermList @@ -829,30 +843,61 @@ namespace EOM.TSHotelManagement.Service .ToList(); // 3) 权限详情(Permission) - var permInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); + var permInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); if (permNumbers.Count > 0) { var info = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && permNumbers.Contains(p.PermissionNumber)) - .Select(p => new { p.PermissionNumber, p.PermissionName, p.MenuKey }) + .Where(p => permNumbers.Contains(p.PermissionNumber)) + .Select(p => new { p.PermissionNumber, p.PermissionName }) .ToList(); foreach (var p in info) { - permInfo[p.PermissionNumber] = (p.PermissionName, p.MenuKey ?? string.Empty); + permInfo[p.PermissionNumber] = p.PermissionName; } } // 4) 组装输出 + var menuIds = rolePermList + .Where(x => x.MenuId.HasValue && x.MenuId.Value > 0) + .Select(x => x.MenuId!.Value) + .Distinct() + .ToList(); + + var menuInfo = new Dictionary(); + if (menuIds.Count > 0) + { + var menus = menuRepository.AsQueryable() + .Where(m => m.IsDelete != 1 && menuIds.Contains(m.Id)) + .Select(m => new { m.Id, m.Key, m.Title }) + .ToList(); + + foreach (var m in menus) + { + menuInfo[m.Id] = (m.Key ?? string.Empty, m.Title ?? string.Empty); + } + } + var resultItems = rolePermList.Select(x => { - permInfo.TryGetValue(x.PermissionNumber, out var meta); + permInfo.TryGetValue(x.PermissionNumber!, out var permName); + var menuId = x.MenuId; + var menuKey = string.Empty; + var menuName = string.Empty; + if (menuId.HasValue && menuInfo.TryGetValue(menuId.Value, out var menuMeta)) + { + menuKey = menuMeta.Key; + menuName = menuMeta.Name; + } + return new UserRolePermissionOutputDto { RoleNumber = x.RoleNumber, - PermissionNumber = x.PermissionNumber, - PermissionName = meta.Name, - MenuKey = meta.MenuKey + PermissionNumber = x.PermissionNumber!, + PermissionName = permName, + MenuId = menuId, + MenuKey = menuKey, + MenuName = menuName }; }).ToList(); @@ -896,7 +941,7 @@ namespace EOM.TSHotelManagement.Service try { // 校验用户是否存在且未删除 - var userExists = adminRepository.IsAny(a => a.Number == input.UserNumber && a.IsDelete != 1); + var userExists = adminRepository.IsAny(a => a.Number == input.UserNumber); if (!userExists) { return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("User not found", "用户不存在")); @@ -905,7 +950,7 @@ namespace EOM.TSHotelManagement.Service var userRoleNumber = $"R-USER-{input.UserNumber}"; // 确保专属角色存在 - var existsRole = roleRepository.IsAny(r => r.RoleNumber == userRoleNumber && r.IsDelete != 1); + var existsRole = roleRepository.IsAny(r => r.RoleNumber == userRoleNumber); if (!existsRole) { var role = new Role @@ -918,15 +963,13 @@ namespace EOM.TSHotelManagement.Service roleRepository.Insert(role); } - // 软删除当前专属角色下所有有效的角色-权限绑定 + // 硬删除当前专属角色下所有有效的角色-权限绑定 var existing = rolePermissionRepository.AsQueryable() - .Where(x => x.RoleNumber == userRoleNumber && x.IsDelete != 1) - .Select(x => new RolePermission { RoleNumber = x.RoleNumber, PermissionNumber = x.PermissionNumber, IsDelete = 1 }) + .Where(x => x.RoleNumber == userRoleNumber) .ToList(); - - foreach (var rp in existing) + if (existing.Count > 0) { - rolePermissionRepository.SoftDelete(rp); + rolePermissionRepository.Delete(existing); } // 过滤、去重、忽略空白 @@ -940,7 +983,7 @@ namespace EOM.TSHotelManagement.Service { // 仅保留系统中存在的权限码 var validPerms = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && perms.Contains(p.PermissionNumber)) + .Where(p => perms.Contains(p.PermissionNumber)) .Select(p => p.PermissionNumber) .ToList(); @@ -949,22 +992,20 @@ namespace EOM.TSHotelManagement.Service var entity = new RolePermission { RoleNumber = userRoleNumber, - PermissionNumber = pnum, - IsDelete = 0 + PermissionNumber = pnum }; rolePermissionRepository.Insert(entity); } } // 确保用户与专属角色绑定存在 - var hasBinding = userRoleRepository.IsAny(ur => ur.UserNumber == input.UserNumber && ur.RoleNumber == userRoleNumber && ur.IsDelete != 1); + var hasBinding = userRoleRepository.IsAny(ur => ur.UserNumber == input.UserNumber && ur.RoleNumber == userRoleNumber); if (!hasBinding) { userRoleRepository.Insert(new UserRole { UserNumber = input.UserNumber, - RoleNumber = userRoleNumber, - IsDelete = 0 + RoleNumber = userRoleNumber }); } @@ -996,7 +1037,7 @@ namespace EOM.TSHotelManagement.Service { var roleNumber = $"R-USER-{userNumber}"; var list = rolePermissionRepository.AsQueryable() - .Where(rp => rp.RoleNumber == roleNumber && rp.IsDelete != 1) + .Where(rp => rp.RoleNumber == roleNumber) .Select(rp => rp.PermissionNumber) .ToList(); diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs index 23dd1d28e2022abc9d4896a1f01fc90cd95c8eb2..29cd03292a941061972a2a2f26c0faeef551bd92 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs @@ -22,6 +22,7 @@ * */ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; @@ -29,88 +30,13 @@ using jvncorelib.EntityLib; using Microsoft.Extensions.Logging; using SqlSugar; -namespace EOM.TSHotelManagement.Service +namespace EOM.TSHotelManagement.Service.SystemManagement { /// /// 基础信息接口实现类 /// - public class BaseService : IBaseService + public class BaseService(GenericRepository workerRepository, GenericRepository educationRepository, GenericRepository nationRepository, GenericRepository deptRepository, GenericRepository positionRepository, GenericRepository passPortTypeRepository, GenericRepository custoTypeRepository, GenericRepository goodbadTypeRepository, GenericRepository appointmentNoticeTypeRepository, GenericRepository customerRepository, GenericRepository appointmentNoticeRepository, ILogger logger) : IBaseService { - /// - /// 员工信息 - /// - private readonly GenericRepository workerRepository; - - /// - /// 学历类型 - /// - private readonly GenericRepository educationRepository; - - /// - /// 民族类型 - /// - private readonly GenericRepository nationRepository; - - /// - /// 部门 - /// - private readonly GenericRepository deptRepository; - - /// - /// 职务 - /// - private readonly GenericRepository positionRepository; - - /// - /// 证件类型 - /// - private readonly GenericRepository passPortTypeRepository; - - /// - /// 客户类型 - /// - private readonly GenericRepository custoTypeRepository; - - /// - /// 奖惩类型 - /// - private readonly GenericRepository goodbadTypeRepository; - - /// - /// 基础URL - /// - private readonly GenericRepository baseRepository; - - /// - /// 公告类型 - /// - private readonly GenericRepository appointmentNoticeTypeRepository; - - private readonly GenericRepository employeeRepository; - - private readonly GenericRepository customerRepository; - - private readonly GenericRepository appointmentNoticeRepository; - - private readonly ILogger logger; - - public BaseService(GenericRepository workerRepository, GenericRepository educationRepository, GenericRepository nationRepository, GenericRepository deptRepository, GenericRepository positionRepository, GenericRepository passPortTypeRepository, GenericRepository custoTypeRepository, GenericRepository goodbadTypeRepository, GenericRepository baseRepository, GenericRepository appointmentNoticeTypeRepository, GenericRepository employeeRepository, GenericRepository customerRepository, GenericRepository appointmentNoticeRepository, ILogger logger) - { - this.workerRepository = workerRepository; - this.educationRepository = educationRepository; - this.nationRepository = nationRepository; - this.deptRepository = deptRepository; - this.positionRepository = positionRepository; - this.passPortTypeRepository = passPortTypeRepository; - this.custoTypeRepository = custoTypeRepository; - this.goodbadTypeRepository = goodbadTypeRepository; - this.baseRepository = baseRepository; - this.appointmentNoticeTypeRepository = appointmentNoticeTypeRepository; - this.employeeRepository = employeeRepository; - this.customerRepository = customerRepository; - this.appointmentNoticeRepository = appointmentNoticeRepository; - this.logger = logger; - } #region 预约类型模块 @@ -120,14 +46,13 @@ namespace EOM.TSHotelManagement.Service /// public ListOutputDto SelectReserTypeAll() { - var helper = new EnumHelper(); var enumList = Enum.GetValues(typeof(ReserType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); @@ -151,14 +76,13 @@ namespace EOM.TSHotelManagement.Service /// public ListOutputDto SelectGenderTypeAll() { - var helper = new EnumHelper(); var enumList = Enum.GetValues(typeof(GenderType)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); @@ -182,14 +106,13 @@ namespace EOM.TSHotelManagement.Service /// public ListOutputDto SelectWorkerFeatureAll() { - var helper = new EnumHelper(); var enumList = Enum.GetValues(typeof(PoliticalAffiliation)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); @@ -212,14 +135,13 @@ namespace EOM.TSHotelManagement.Service /// public ListOutputDto SelectRoomStateAll() { - var helper = new EnumHelper(); var enumList = Enum.GetValues(typeof(RoomState)) .Cast() .Select(e => new EnumDto { Id = (int)e, Name = e.ToString(), - Description = helper.GetEnumDescription(e) + Description = EnumHelper.GetEnumDescription(e) }) .ToList(); @@ -719,17 +641,29 @@ namespace EOM.TSHotelManagement.Service } var parentDepartmentNumbers = depts.Where(a => !a.ParentDepartmentNumber.IsNullOrEmpty()).Select(a => a.ParentDepartmentNumber).ToList(); - var parentDepartments = deptRepository.GetList(a => parentDepartmentNumbers.Contains(a.DepartmentNumber)); + var parentDepartments = deptRepository.GetList(a => parentDepartmentNumbers.Contains(a.DepartmentNumber)).ToDictionary(d => d.DepartmentNumber); var departmentLeaderNumbers = depts.Where(a => !a.DepartmentLeader.IsNullOrEmpty()).Select(a => a.DepartmentLeader).ToList(); - var departmentLeaders = workerRepository.GetList(a => departmentLeaderNumbers.Contains(a.EmployeeId)); + var departmentLeaders = workerRepository.GetList(a => departmentLeaderNumbers.Contains(a.EmployeeId)).ToDictionary(e => e.EmployeeId); depts.ForEach(source => { - var parentDepartment = parentDepartments.SingleOrDefault(a => a.DepartmentNumber.Equals(source.ParentDepartmentNumber)); - source.ParentDepartmentName = parentDepartment.IsNullOrEmpty() ? "" : parentDepartment.DepartmentName; + if (!string.IsNullOrWhiteSpace(source.ParentDepartmentNumber) && parentDepartments.TryGetValue(source.ParentDepartmentNumber, out var parentDepartment)) + { + source.ParentDepartmentName = parentDepartment.DepartmentName; + } + else + { + source.ParentDepartmentName = ""; + } - var departmentLeader = departmentLeaders.SingleOrDefault(a => a.EmployeeId.Equals(source.DepartmentLeader)); - source.LeaderName = departmentLeader.IsNullOrEmpty() ? "" : departmentLeader.EmployeeName; + if (!string.IsNullOrWhiteSpace(source.DepartmentLeader) && departmentLeaders.TryGetValue(source.DepartmentLeader, out var departmentLeader)) + { + source.LeaderName = departmentLeader.Name; + } + else + { + source.LeaderName = ""; + } }); var result = EntityMapper.MapList(depts); return new ListOutputDto @@ -1138,7 +1072,7 @@ namespace EOM.TSHotelManagement.Service // 当前证件类型下是否有客户 var passportTypeNumbers = passPortTypes.Select(a => a.PassportId).ToList(); - var customerCount = customerRepository.AsQueryable().Count(a => passportTypeNumbers.Contains(a.PassportId)); + var customerCount = customerRepository.AsQueryable().Count(a => passportTypeNumbers.Contains(a.IdCardType)); if (customerCount > 0) { return new BaseResponse diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Menu/MenuService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Menu/MenuService.cs index e774e304d08abf16358bd2aeeb9efad266eb8d0d..1c4b0225e2ee736906c28d73d2b9ccf0f7be6541 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Menu/MenuService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Menu/MenuService.cs @@ -68,13 +68,12 @@ namespace EOM.TSHotelManagement.Service /// public ListOutputDto BuildMenuAll(BaseInputDto baseInputDto) { - // 1) 读取所有未删除的菜单 + // 1) 读取所有未删除菜单 List allMenus = menuRepository.GetList(a => a.IsDelete != 1).OrderBy(a => a.Id).ToList(); - // 默认:空用户/无权限 -> 返回空树 + // 默认:无权限返回空树 List filteredMenus = new(); - // 前端按钮权限:按菜单Key聚合的“用户拥有的权限编码”集合 - Dictionary> menuPermMap = null; + Dictionary> menuPermMap = new(); try { @@ -89,12 +88,10 @@ namespace EOM.TSHotelManagement.Service } catch { - // token 无效则按无权限处理 userNumber = string.Empty; } } - // 超级管理员放行所有菜单 var isSuperAdmin = false; if (!string.IsNullOrWhiteSpace(userNumber)) { @@ -105,112 +102,139 @@ namespace EOM.TSHotelManagement.Service if (isSuperAdmin) { filteredMenus = allMenus; - - // 超管:加载所有与菜单绑定的权限编码 - var allPermPairs = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && p.MenuKey != null) - .Select(p => new { p.MenuKey, p.PermissionNumber }) + var allPermNumbers = permissionRepository.AsQueryable() + .Select(p => p.PermissionNumber) + .ToList() + .Where(p => !p.IsNullOrEmpty()) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - menuPermMap = allPermPairs - .GroupBy(x => x.MenuKey!) - .ToDictionary(g => g.Key!, g => g.Select(x => x.PermissionNumber).Distinct().ToList()); + menuPermMap = BuildDefaultMenuPermissionMap(filteredMenus, allPermNumbers); + return BuildMenuTree(filteredMenus, menuPermMap); } - else if (!string.IsNullOrWhiteSpace(userNumber)) + + if (string.IsNullOrWhiteSpace(userNumber)) { - // 2) 用户 -> 角色 - var roleNumbers = userRoleRepository.AsQueryable() - .Where(ur => ur.UserNumber == userNumber && ur.IsDelete != 1) - .Select(ur => ur.RoleNumber) - .ToList(); + return BuildMenuTree(filteredMenus, menuPermMap); + } - if (roleNumbers != null && roleNumbers.Count > 0) - { - // 3) 角色 -> 权限 - var permNumbers = rolePermissionRepository.AsQueryable() - .Where(rp => rp.IsDelete != 1 && roleNumbers.Contains(rp.RoleNumber)) - .Select(rp => rp.PermissionNumber) - .ToList(); - - if (permNumbers != null && permNumbers.Count > 0) - { - // 4) 权限 -> 绑定的菜单Key(Permission.MenuKey) - var permQuery = permissionRepository.AsQueryable() - .Where(p => p.IsDelete != 1 && p.MenuKey != null && permNumbers.Contains(p.PermissionNumber)); - - var allowedKeys = new HashSet( - permQuery - .Select(p => p.MenuKey) - .ToList() - .Where(k => !string.IsNullOrWhiteSpace(k))! - ); - - // 同时按菜单Key聚合当前用户拥有的权限编码,供前端按钮级控制 - var userPermPairs = permQuery - .Select(p => new { p.MenuKey, p.PermissionNumber }) - .ToList(); - - menuPermMap = userPermPairs - .GroupBy(x => x.MenuKey!) - .ToDictionary(g => g.Key!, g => g.Select(x => x.PermissionNumber).Distinct().ToList()); - - // 5) 仅保留有权限的菜单,并补齐其父级(保证树连通) - var menuById = allMenus.ToDictionary(m => m.Id); - var menuByKey = allMenus.Where(m => !string.IsNullOrWhiteSpace(m.Key)) - .ToDictionary(m => m.Key!); - var allowedMenuIds = new HashSet(); - - foreach (var key in allowedKeys) - { - if (menuByKey.TryGetValue(key, out var menu)) - { - var current = menu; - while (current != null && !allowedMenuIds.Contains(current.Id)) - { - allowedMenuIds.Add(current.Id); - if (current.Parent.HasValue && menuById.TryGetValue(current.Parent.Value, out var parentMenu)) - { - current = parentMenu; - } - else - { - current = null; - } - } - } - } - - filteredMenus = allMenus.Where(m => allowedMenuIds.Contains(m.Id)).ToList(); - } - else - { - filteredMenus = new List(); - } - } - else - { - filteredMenus = new List(); - } + // 2) 用户 -> 角色 + var roleNumbers = userRoleRepository.AsQueryable() + .Where(ur => ur.UserNumber == userNumber) + .Select(ur => ur.RoleNumber) + .ToList(); + + if (roleNumbers == null || roleNumbers.Count == 0) + { + return BuildMenuTree(filteredMenus, menuPermMap); + } + + // 3) 角色 -> 授权(菜单与权限独立) + var roleGrants = rolePermissionRepository.AsQueryable() + .Where(rp => roleNumbers.Contains(rp.RoleNumber)) + .Select(rp => new { rp.MenuId, rp.PermissionNumber }) + .ToList(); + + var grantedPermNumbers = roleGrants + .Where(x => !x.PermissionNumber.IsNullOrEmpty()) + .Select(x => x.PermissionNumber!) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var directMenuIds = roleGrants + .Where(x => x.MenuId.HasValue && x.MenuId.Value > 0) + .Select(x => x.MenuId!.Value) + .Distinct() + .ToHashSet(); + + if (directMenuIds.Count == 0) + { + return BuildMenuTree(filteredMenus, menuPermMap); + } + + // 4) 保留已授权菜单并补齐其父级,保证树连通 + var menuById = allMenus.ToDictionary(m => m.Id); + var allowedMenuIds = BuildAllowedMenuIdsWithAncestors(menuById, directMenuIds); + filteredMenus = allMenus.Where(m => allowedMenuIds.Contains(m.Id)).ToList(); + + // 5) 构建按菜单聚合的权限映射 + var menuSpecificPerms = roleGrants + .Where(x => x.MenuId.HasValue && x.MenuId.Value > 0 && !x.PermissionNumber.IsNullOrEmpty()) + .GroupBy(x => x.MenuId!.Value) + .ToDictionary( + g => g.Key, + g => g.Select(x => x.PermissionNumber!) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList()); + + if (menuSpecificPerms.Count > 0) + { + menuPermMap = filteredMenus + .Where(m => !string.IsNullOrWhiteSpace(m.Key)) + .ToDictionary( + m => m.Key!, + m => menuSpecificPerms.TryGetValue(m.Id, out var perms) + ? perms + : new List()); } else { - filteredMenus = new List(); + // 独立授权模式:将已授权权限作为可见菜单的全局权限集 + menuPermMap = BuildDefaultMenuPermissionMap(filteredMenus, grantedPermNumbers); } } catch { - // 异常情况下,回退为空树,避免泄露菜单 filteredMenus = new List(); + menuPermMap = new Dictionary>(); } - // 6) 构建(过滤后的)菜单树(附带每个菜单Key下的用户权限编码集合) return BuildMenuTree(filteredMenus, menuPermMap); } - /// - /// 查询所有菜单信息 - /// - /// + private static HashSet BuildAllowedMenuIdsWithAncestors( + Dictionary menuById, + HashSet directMenuIds) + { + var allowedMenuIds = new HashSet(); + + foreach (var menuId in directMenuIds) + { + if (!menuById.TryGetValue(menuId, out var current)) + { + continue; + } + + while (current != null && allowedMenuIds.Add(current.Id)) + { + if (current.Parent.HasValue && menuById.TryGetValue(current.Parent.Value, out var parentMenu)) + { + current = parentMenu; + } + else + { + current = null; + } + } + } + + return allowedMenuIds; + } + + private static Dictionary> BuildDefaultMenuPermissionMap( + List menus, + List permissionNumbers) + { + var normalizedPerms = (permissionNumbers ?? new List()) + .Where(p => !p.IsNullOrEmpty()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + return menus + .Where(m => !string.IsNullOrWhiteSpace(m.Key)) + .ToDictionary(m => m.Key!, _ => normalizedPerms.ToList()); + } + public ListOutputDto SelectMenuAll(ReadMenuInputDto readMenuInputDto) { var where = SqlFilterBuilder.BuildExpression(readMenuInputDto ?? new ReadMenuInputDto()); @@ -411,4 +435,3 @@ namespace EOM.TSHotelManagement.Service } } } - diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Notice/NoticeService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Notice/NoticeService.cs index 4d11897848a943434e8cc8f21faea5844618c0ab..260897d5563c38ad1e337681f7f57dfa7c6c4b2f 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Notice/NoticeService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Notice/NoticeService.cs @@ -56,7 +56,17 @@ namespace EOM.TSHotelManagement.Service var ntc = new List(); var where = SqlFilterBuilder.BuildExpression(readAppointmentNoticeInputDto); var count = 0; - ntc = noticeRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readAppointmentNoticeInputDto.Page, readAppointmentNoticeInputDto.PageSize, ref count); + + if (!readAppointmentNoticeInputDto.IgnorePaging) + { + ntc = noticeRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readAppointmentNoticeInputDto.Page, readAppointmentNoticeInputDto.PageSize, ref count); + } + else + { + ntc = noticeRepository.AsQueryable().Where(where.ToExpression()).ToList(); + count = ntc.Count; + } + ntc.ForEach(source => { switch (source.NoticeType) diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Quartz/IQuartzAppService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Quartz/IQuartzAppService.cs new file mode 100644 index 0000000000000000000000000000000000000000..7e20009d14baf304b04231900a1e539f761cd98b --- /dev/null +++ b/EOM.TSHotelManagement.Service/SystemManagement/Quartz/IQuartzAppService.cs @@ -0,0 +1,10 @@ +using EOM.TSHotelManagement.Contract; +using System.Threading.Tasks; + +namespace EOM.TSHotelManagement.Service +{ + public interface IQuartzAppService + { + Task> SelectQuartzJobList(); + } +} diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Quartz/QuartzAppService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Quartz/QuartzAppService.cs new file mode 100644 index 0000000000000000000000000000000000000000..9a33109145769143193576f2cf6c89b8392c1fa9 --- /dev/null +++ b/EOM.TSHotelManagement.Service/SystemManagement/Quartz/QuartzAppService.cs @@ -0,0 +1,100 @@ +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Contract; +using Quartz; +using Quartz.Impl.Matchers; + +namespace EOM.TSHotelManagement.Service +{ + public class QuartzAppService : IQuartzAppService + { + private readonly ISchedulerFactory _schedulerFactory; + + public QuartzAppService(ISchedulerFactory schedulerFactory) + { + _schedulerFactory = schedulerFactory; + } + + public async Task> SelectQuartzJobList() + { + try + { + var scheduler = await _schedulerFactory.GetScheduler(); + var jobKeys = await scheduler.GetJobKeys(GroupMatcher.AnyGroup()); + var rows = new List(); + + foreach (var jobKey in jobKeys.OrderBy(a => a.Group).ThenBy(a => a.Name)) + { + var jobDetail = await scheduler.GetJobDetail(jobKey); + if (jobDetail == null) + { + continue; + } + + var triggers = await scheduler.GetTriggersOfJob(jobKey); + if (triggers == null || triggers.Count == 0) + { + rows.Add(new ReadQuartzJobOutputDto + { + JobName = jobKey.Name, + JobGroup = jobKey.Group, + JobDescription = jobDetail.Description, + IsDurable = jobDetail.Durable, + RequestsRecovery = jobDetail.RequestsRecovery + }); + continue; + } + + foreach (var trigger in triggers.OrderBy(a => a.Key.Group).ThenBy(a => a.Key.Name)) + { + var state = await scheduler.GetTriggerState(trigger.Key); + var cronTrigger = trigger as ICronTrigger; + var simpleTrigger = trigger as ISimpleTrigger; + + rows.Add(new ReadQuartzJobOutputDto + { + JobName = jobKey.Name, + JobGroup = jobKey.Group, + JobDescription = jobDetail.Description, + IsDurable = jobDetail.Durable, + RequestsRecovery = jobDetail.RequestsRecovery, + TriggerName = trigger.Key.Name, + TriggerGroup = trigger.Key.Group, + TriggerType = trigger.GetType().Name, + TriggerState = state.ToString(), + CronExpression = cronTrigger?.CronExpressionString, + TimeZoneId = cronTrigger?.TimeZone?.Id, + RepeatCount = simpleTrigger?.RepeatCount, + RepeatIntervalMs = simpleTrigger?.RepeatInterval.TotalMilliseconds, + StartTimeUtc = trigger.StartTimeUtc.UtcDateTime, + EndTimeUtc = trigger.EndTimeUtc?.UtcDateTime, + NextFireTimeUtc = trigger.GetNextFireTimeUtc()?.UtcDateTime, + PreviousFireTimeUtc = trigger.GetPreviousFireTimeUtc()?.UtcDateTime + }); + } + } + + return new ListOutputDto + { + Data = new PagedData + { + Items = rows, + TotalCount = rows.Count + } + }; + } + catch (Exception ex) + { + return new ListOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), + Data = new PagedData + { + Items = new List(), + TotalCount = 0 + } + }; + } + } + } +} diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Role/IRoleAppService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Role/IRoleAppService.cs index cc5218dd088f9ba93285a1764d26ec1f6bb23861..49eb25b0a00227f3f61b4b9fc1460d9c486f5f8c 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Role/IRoleAppService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Role/IRoleAppService.cs @@ -47,6 +47,13 @@ namespace EOM.TSHotelManagement.Service /// 权限编码集合 ListOutputDto ReadRolePermissions(string roleNumber); + /// + /// 读取指定角色已授予的菜单与权限(菜单和权限独立) + /// + /// 角色编码 + /// 角色授权明细 + SingleOutputDto ReadRoleGrants(string roleNumber); + /// /// 读取隶属于指定角色的管理员用户编码集合 /// diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Role/RoleAppService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Role/RoleAppService.cs index 390b67c33b0d122b5dc2e8ed6d9f3b52354cb7f5..3863db75684da777858cb96595509d899516b58c 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Role/RoleAppService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Role/RoleAppService.cs @@ -30,13 +30,31 @@ namespace EOM.TSHotelManagement.Service /// private readonly GenericRepository userRoleRepository; + /// + /// 权限 仓储 + /// + private readonly GenericRepository permissionRepository; + + /// + /// 菜单 仓储 + /// + private readonly GenericRepository menuRepository; + private readonly ILogger logger; - public RoleAppService(GenericRepository roleRepository, GenericRepository rolePermissionRepository, GenericRepository userRoleRepository, ILogger logger) + public RoleAppService( + GenericRepository roleRepository, + GenericRepository rolePermissionRepository, + GenericRepository userRoleRepository, + GenericRepository permissionRepository, + GenericRepository menuRepository, + ILogger logger) { this.roleRepository = roleRepository; this.rolePermissionRepository = rolePermissionRepository; this.userRoleRepository = userRoleRepository; + this.permissionRepository = permissionRepository; + this.menuRepository = menuRepository; this.logger = logger; } @@ -78,8 +96,8 @@ namespace EOM.TSHotelManagement.Service // 如果角色组存在关联的权限映射或用户绑定,则不允许删除 var roleNumbers = roles.Select(r => r.RoleNumber).ToList(); - var hasRolePermissions = rolePermissionRepository.IsAny(rp => roleNumbers.Contains(rp.RoleNumber) && rp.IsDelete != 1); - var hasUserRoles = userRoleRepository.IsAny(ur => roleNumbers.Contains(ur.RoleNumber) && ur.IsDelete != 1); + var hasRolePermissions = rolePermissionRepository.IsAny(rp => roleNumbers.Contains(rp.RoleNumber)); + var hasUserRoles = userRoleRepository.IsAny(ur => roleNumbers.Contains(ur.RoleNumber)); if (hasRolePermissions || hasUserRoles) { return new BaseResponse @@ -215,15 +233,13 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Role not found", "角色不存在")); } - // 软删除该角色现有有效的权限映射 + // 硬删除该角色现有有效授权映射(菜单 + 权限) var existing = rolePermissionRepository.AsQueryable() - .Where(x => x.RoleNumber == input.RoleNumber && x.IsDelete != 1) - .Select(x => new RolePermission { RoleNumber = x.RoleNumber, PermissionNumber = x.PermissionNumber, IsDelete = 1 }) + .Where(x => x.RoleNumber == input.RoleNumber) .ToList(); - - foreach (var rp in existing) + if (existing.Count > 0) { - rolePermissionRepository.SoftDelete(rp); + rolePermissionRepository.Delete(existing); } // 过滤去重并忽略空白权限码 @@ -233,14 +249,52 @@ namespace EOM.TSHotelManagement.Service .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - // 插入新的权限映射 - foreach (var perm in distinctPerms) + // 过滤去重并忽略非法菜单主键 + var distinctMenuIds = (input.MenuIds ?? new List()) + .Where(id => id > 0) + .Distinct() + .ToList(); + + // 仅保留系统中真实存在的权限编码和菜单主键 + var validPerms = distinctPerms.Count == 0 + ? new List() + : permissionRepository.AsQueryable() + .Where(p => distinctPerms.Contains(p.PermissionNumber)) + .Select(p => p.PermissionNumber) + .ToList() + .Where(p => !p.IsNullOrEmpty()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var validMenuIds = distinctMenuIds.Count == 0 + ? new List() + : menuRepository.AsQueryable() + .Where(m => m.IsDelete != 1 && distinctMenuIds.Contains(m.Id)) + .Select(m => m.Id) + .ToList() + .Distinct() + .ToList(); + + // 写入角色-权限映射 + foreach (var perm in validPerms) { var entity = new RolePermission { RoleNumber = input.RoleNumber, PermissionNumber = perm, - IsDelete = 0 + MenuId = null + }; + rolePermissionRepository.Insert(entity); + } + + // 写入角色-菜单映射 + foreach (var menuId in validMenuIds) + { + var entity = new RolePermission + { + RoleNumber = input.RoleNumber, + MenuId = menuId, + PermissionNumber = null }; rolePermissionRepository.Insert(entity); } @@ -272,7 +326,9 @@ namespace EOM.TSHotelManagement.Service try { var list = rolePermissionRepository.AsQueryable() - .Where(rp => rp.RoleNumber == roleNumber && rp.IsDelete != 1) + .Where(rp => rp.RoleNumber == roleNumber + && rp.PermissionNumber != null + && rp.PermissionNumber != "") .Select(rp => rp.PermissionNumber) .ToList() ?? new List(); @@ -299,6 +355,74 @@ namespace EOM.TSHotelManagement.Service } } + /// + /// 读取指定角色已授予的菜单与权限(菜单与权限独立) + /// + public SingleOutputDto ReadRoleGrants(string roleNumber) + { + if (roleNumber.IsNullOrEmpty()) + { + return new SingleOutputDto + { + Code = BusinessStatusCode.BadRequest, + Message = LocalizationHelper.GetLocalizedString("RoleNumber is required", "缺少角色编码"), + Data = new ReadRoleGrantOutputDto + { + RoleNumber = string.Empty, + PermissionNumbers = new List(), + MenuIds = new List() + } + }; + } + + try + { + var grants = rolePermissionRepository.AsQueryable() + .Where(rp => rp.RoleNumber == roleNumber) + .Select(rp => new { rp.PermissionNumber, rp.MenuId }) + .ToList(); + + var permissionNumbers = grants + .Where(x => !x.PermissionNumber.IsNullOrEmpty()) + .Select(x => x.PermissionNumber!) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var menuIds = grants + .Where(x => x.MenuId.HasValue && x.MenuId.Value > 0) + .Select(x => x.MenuId!.Value) + .Distinct() + .ToList(); + + return new SingleOutputDto + { + Code = BusinessStatusCode.Success, + Message = LocalizationHelper.GetLocalizedString("Query success", "查询成功"), + Data = new ReadRoleGrantOutputDto + { + RoleNumber = roleNumber, + PermissionNumbers = permissionNumbers, + MenuIds = menuIds + } + }; + } + catch (Exception ex) + { + logger.LogError(ex, "Error reading grants for role {RoleNumber}: {Message}", roleNumber, ex.Message); + return new SingleOutputDto + { + Code = BusinessStatusCode.InternalServerError, + Message = LocalizationHelper.GetLocalizedString($"Query failed: {ex.Message}", $"查询失败:{ex.Message}"), + Data = new ReadRoleGrantOutputDto + { + RoleNumber = roleNumber, + PermissionNumbers = new List(), + MenuIds = new List() + } + }; + } + } + /// /// 读取隶属于指定角色的管理员用户编码集合 /// @@ -317,7 +441,7 @@ namespace EOM.TSHotelManagement.Service try { var list = userRoleRepository.AsQueryable() - .Where(ur => ur.RoleNumber == roleNumber && ur.IsDelete != 1) + .Where(ur => ur.RoleNumber == roleNumber) .Select(ur => ur.UserNumber) .ToList() ?? new List(); @@ -365,8 +489,8 @@ namespace EOM.TSHotelManagement.Service // 软删除该角色当前所有有效的用户绑定 var existing = userRoleRepository.AsQueryable() - .Where(ur => ur.RoleNumber == input.RoleNumber && ur.IsDelete != 1) - .Select(ur => new UserRole { RoleNumber = ur.RoleNumber, UserNumber = ur.UserNumber, IsDelete = 1 }) + .Where(ur => ur.RoleNumber == input.RoleNumber) + .Select(ur => new UserRole { RoleNumber = ur.RoleNumber, UserNumber = ur.UserNumber }) .ToList(); foreach (var ur in existing) @@ -387,8 +511,7 @@ namespace EOM.TSHotelManagement.Service var entity = new UserRole { RoleNumber = input.RoleNumber, - UserNumber = u, - IsDelete = 0 + UserNumber = u }; userRoleRepository.Insert(entity); } diff --git a/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs b/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs index 1b370db4567c385fbfd1af5c233af6a8a2c52444..f7dd56cda4f5ee8a6c0ae222687d76f23196bc32 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs @@ -61,10 +61,19 @@ namespace EOM.TSHotelManagement.Service { List cif = new List(); - var where = SqlFilterBuilder.BuildExpression(readSupervisionStatisticsInputDto ?? new ReadSupervisionStatisticsInputDto()); + var where = SqlFilterBuilder.BuildExpression(readSupervisionStatisticsInputDto, nameof(SupervisionStatistics.SupervisionTime)); var count = 0; - cif = checkInfoRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readSupervisionStatisticsInputDto.Page, readSupervisionStatisticsInputDto.PageSize, ref count); + + if (!readSupervisionStatisticsInputDto.IgnorePaging) + { + cif = checkInfoRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readSupervisionStatisticsInputDto.Page, readSupervisionStatisticsInputDto.PageSize, ref count); + } + else + { + cif = checkInfoRepository.AsQueryable().Where(where.ToExpression()).ToList(); + } + var deptId = cif.Select(a => a.SupervisingDepartment).ToList(); var depts = checkInfoRepository.Change().GetList(a => deptId.Contains(a.DepartmentNumber)); cif.ForEach(c => @@ -120,6 +129,8 @@ namespace EOM.TSHotelManagement.Service supervisionStatistics.SupervisionAdvice = checkInfo.SupervisionAdvice; supervisionStatistics.SupervisionStatistician = checkInfo.SupervisionStatistician; supervisionStatistics.SupervisionProgress = checkInfo.SupervisionProgress; + supervisionStatistics.SupervisionTime = checkInfo.SupervisionTime ?? DateTime.MinValue; + supervisionStatistics.IsDelete = checkInfo.IsDelete; supervisionStatistics.RowVersion = checkInfo.RowVersion ?? 0; var updateResult = checkInfoRepository.Update(supervisionStatistics); diff --git a/EOM.TSHotelManagement.Service/SystemManagement/VipRule/VipRuleAppService.cs b/EOM.TSHotelManagement.Service/SystemManagement/VipRule/VipRuleAppService.cs index 2de9ecbc0a179364462ba2ff20184837917b5735..d7a7e2375643c91c900887ea46a8b6f012f049c0 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/VipRule/VipRuleAppService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/VipRule/VipRuleAppService.cs @@ -65,17 +65,25 @@ namespace EOM.TSHotelManagement.Service var where = SqlFilterBuilder.BuildExpression(readVipLevelRuleInputDto ?? new ReadVipLevelRuleInputDto()); var count = 0; - var Data = vipRuleRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readVipLevelRuleInputDto.Page, readVipLevelRuleInputDto.PageSize, ref count); + var data = new List(); + if (readVipLevelRuleInputDto.IgnorePaging) + { + data = vipRuleRepository.AsQueryable().Where(where.ToExpression()).ToList(); + } + else + { + data = vipRuleRepository.AsQueryable().Where(where.ToExpression()).ToPageList(readVipLevelRuleInputDto.Page, readVipLevelRuleInputDto.PageSize, ref count); + } var listUserType = custoTypeRepository.GetList(a => a.IsDelete != 1); - Data.ForEach(source => + data.ForEach(source => { var userType = listUserType.SingleOrDefault(a => a.CustomerType == source.VipLevelId); source.VipLevelName = userType == null ? "" : userType.CustomerTypeName; }); - var viprules = EntityMapper.MapList(Data); + var viprules = EntityMapper.MapList(data); return new ListOutputDto { diff --git a/EOM.TSHotelManagement.Service/Util/UtilService.cs b/EOM.TSHotelManagement.Service/Util/UtilService.cs index 1f04c1e665382f2f9780c89bbde0a7867c515f8c..f0d28876d5c8ff68ac6f3652486f2e63c2db2d2a 100644 --- a/EOM.TSHotelManagement.Service/Util/UtilService.cs +++ b/EOM.TSHotelManagement.Service/Util/UtilService.cs @@ -86,7 +86,7 @@ namespace EOM.TSHotelManagement.Service var where = SqlFilterBuilder.BuildExpression(readOperationLogInputDto, nameof(OperationLog.OperationTime)); var count = 0; - if (readOperationLogInputDto.Page != 0 && readOperationLogInputDto.PageSize != 0) + if (!readOperationLogInputDto.IgnorePaging) { operationLogs = operationLogRepository.AsQueryable().Where(where.ToExpression()).OrderByDescending(a => a.OperationTime).ToPageList(readOperationLogInputDto.Page, readOperationLogInputDto.PageSize, ref count); } @@ -125,7 +125,7 @@ namespace EOM.TSHotelManagement.Service var where = SqlFilterBuilder.BuildExpression(readRequestLogInputDto, nameof(RequestLog.RequestTime)); var count = 0; - if (readRequestLogInputDto.Page != 0 && readRequestLogInputDto.PageSize != 0) + if (!readRequestLogInputDto.IgnorePaging) { requestLogs = requestLogRepository.AsQueryable().Where(where.ToExpression()).OrderByDescending(a => a.RequestTime).ToPageList(readRequestLogInputDto.Page, readRequestLogInputDto.PageSize, ref count); } diff --git a/README.en.md b/README.en.md index 424be9cad3678ff58938caa3da145e12584776a5..1cf753e4b503e3c1453f5431d7b9cabad2cd7f87 100644 --- a/README.en.md +++ b/README.en.md @@ -8,6 +8,7 @@

## Project Overview @@ -48,7 +49,11 @@ Primarily designed to achieve front-end/back-end separation following the upgrad ### 4. Infrastructure and Security - **Multi-Database Support**: Enables seamless switching between mainstream relational databases via SqlSugar, with one-click database and table initialisation. - **Security Mechanisms**: - - **JWT**: Secure token authentication mechanism. + - **JWT Dual Token Mechanism**: Implements short-lived Access Token (15 minutes) + long-lived Refresh Token (7 days) for significantly enhanced security. + - Access Token used for API request authentication. + - Refresh Token stored in HttpOnly Cookie to prevent XSS attacks. + - Supports automatic token rotation and revocation; both tokens are cleared on logout. + - See `docs/frontend-jwt-integration-guide.md` for frontend integration details. - **CSRF**: Prevents cross-site request forgery attacks. - **Data Protection**: Sensitive data (e.g., ID numbers, contact details) encrypted and stored using ASP.NET Core Data Protection API. - **Request Logging**: Global request middleware records API call details. @@ -66,7 +71,6 @@ Primarily designed to achieve front-end/back-end separation following the upgrad - **Quartz .NET**: Scheduled task management (handling expired reservations). - **MailKit**: Email sending library. - **NSwag**: API documentation generation (Swagger UI). -- **Newtonsoft.Json**: JSON serialisation library. ## Project Structure @@ -92,7 +96,7 @@ This project utilises the SqlSugar framework to support one-click database and t | ---------- | ---------------- | :----------: | :--: | | MariaDB | 10.11.10+ | ✅ | ✅ | | PostgreSQL | 13+ | ✅ | ✅ | -| MySQL | 5.7+ | ✅ | ✅ | +| MySQL | 8.0+ | ✅ | ✅ | | SQL Server | 2022+ | ✅ | ✅ | | Oracle | - | ❌ | ❌ | | SQLite | - | ❌ | ❌ | @@ -125,7 +129,17 @@ This project utilises the SqlSugar framework to support one-click database and t ``` 3. **Configure Keys**: - Set `Jwt__Key` (for token generation) and `DataProtection`-related keys (for data encryption/decryption) in `appsettings.json`. + Configure JWT-related settings in `appsettings.json`: + ```json + { + "Jwt": { + "Key": "your-secret-key-must-be-long-enough", + "ExpiryMinutes": 15, // Access Token expiry time (minutes) + "RefreshTokenExpiryDays": 7 // Refresh Token expiry time (days) + } + } + ``` + Also configure `DataProtection`-related keys for sensitive data encryption/decryption. 4. **Running the Project**: Open `EOM.TSHotelManagement.Web.sln` in Visual Studio and start the `EOM.TSHotelManagement.API` project. @@ -173,7 +187,8 @@ docker run -d \ |ASPNETCORE_ENVIRONMENT|System Environment (determines Dataprotection Key generation location and environment detection)|Y|docker|docker| |{Default Database (e.g.: MariaDB/MySQL/SQL Server/PostgreSQL)}ConnectStr|Corresponding Database Connection String|Y|N/A|N/A| |Jwt__Key|JWT Key|Y|None, must be set|N/A| -|Jwt__ExpiryMinutes|Token Validity Period (Minutes)|Y|20|N/A| +|Jwt__ExpiryMinutes|Access Token Validity Period (Minutes)|Y|15|N/A| +|Jwt__RefreshTokenExpiryDays|Refresh Token Validity Period (Days)|Y|7|N/A| |Lsky__Enabled|Enable Lsky image hosting integration|Y|false|true/false| |Lsky__BaseAddress|Lsky image hosting base address|Y|N/A|N/A| |Lsky__Email|Lsky account email|Y|N/A|N/A| @@ -193,12 +208,20 @@ docker run -d \ |AllowedOrigins__0|Allowed Domain Sites (for Development Environments)|Y|http://localhost:8080|http://localhost:8080| |AllowedOrigins__1|Allowed domain sites for production environment|Y|https://www.yourdomain.com|https://www.yourdomain.com| |SoftwareVersion|Software version number for documentation purposes|N|N/A|N/A| -|JobKeys__0|Quartz Job 1|Y|ReservationExpirationCheckJob|ReservationExpirationCheckJob| -|JobKeys__1|Quartz Job 2|Y|MailServiceCheckJob|MailServiceCheckJob| -|JobKeys__2|Quartz Job 3|Y|RedisServiceCheckJob|RedisServiceCheckJob| +|JobKeys__0|Quartz Job 1|Y|ReservationExpirationCheckJob:0 0 1 * * ?|JobName:CronExpression| +|JobKeys__1|Quartz Job 2|Y|MailServiceCheckJob:0 */5 * * * ?|JobName:CronExpression| +|JobKeys__2|Quartz Job 3|Y|RedisServiceCheckJob:0 */5 * * * ?|JobName:CronExpression| |Redis__Enabled|Enable Redis|N|false|true/false| |Redis__ConnectionString|Redis ConnectString|N|N/A|N/A| |Redis__DefaultDatabase|Default Database of Redis|N|0|0| +|Redis__ConnectTimeoutMs|Redis connect timeout (ms)|N|5000|1000~30000| +|Redis__AsyncTimeoutMs|Redis async command timeout (ms)|N|2000|500~30000| +|Redis__SyncTimeoutMs|Redis sync command timeout (ms)|N|2000|500~30000| +|Redis__KeepAliveSeconds|Redis keepalive interval (seconds)|N|15|5~300| +|Redis__ConnectRetry|Redis connect retry count|N|3|1~10| +|Redis__ReconnectRetryBaseDelayMs|Redis reconnect exponential retry base delay (ms)|N|3000|500~30000| +|Redis__OperationTimeoutMs|JWT revocation check operation timeout (ms)|N|1200|200~5000| +|Redis__FailureCooldownSeconds|Fallback cooldown after Redis failure (seconds)|N|30|5~300| |Idempotency__Enabled|Enable Idempotency-Key middleware|N|true|true/false| |Idempotency__EnforceKey|Require Idempotency-Key for write requests|N|false|true/false| |Idempotency__MaxKeyLength|Maximum Idempotency-Key length|N|128|integer >= 16| diff --git a/README.md b/README.md index 79cae06b4a42029a9cf0b21e94cd9dea5308875d..74464f93d9692548addea0be28f9934ea4c7647d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@

## 项目简介 @@ -48,7 +49,11 @@ ### 4. 基础设施与安全 - **多数据库支持**:通过 SqlSugar 实现无缝切换主流关系型数据库,支持一键初始化建库建表。 - **安全机制**: - - **JWT**:安全的 Token 认证机制。 + - **JWT 双Token机制**:采用短效 Access Token(15分钟)+ 长效 Refresh Token(7天)的机制,大幅提升安全性。 + - Access Token 用于 API 请求认证。 + - Refresh Token 存储在 HttpOnly Cookie 中,防止 XSS 攻击。 + - 支持自动 Token 轮换和撤销,登出时同时清理两个 Token。 + - 详见 `docs/frontend-jwt-integration-guide.md` 前端对接文档。 - **CSRF**:防止跨站请求伪造攻击。 - **Data Protection**:敏感数据(如身份证号、联系方式)使用 ASP.NET Core Data Protection API 加密存储。 - **请求日志**:全局请求中间件,记录接口调用详情。 @@ -66,7 +71,6 @@ - **Quartz .NET**: 定时任务调度(处理过期预订)。 - **MailKit**: 邮件发送库。 - **NSwag**: API 文档生成(Swagger UI)。 -- **Newtonsoft.Json**: JSON 序列化库。 ## 项目结构 @@ -88,14 +92,14 @@ EOM.TSHotelManagement.Web 本项目已通过 SqlSugar 框架支持多数据库一键建库建表: -| 数据库 | 版本 | 支持建库建表 | 状态 | -| ---------- | ---------------- | :----------: | :--: | -| MariaDB | 10.11.10+ | ✅ | ✅ | -| PostgreSQL | 13+ | ✅ | ✅ | -| MySQL | 5.7+ | ✅ | ✅ | -| SQL Server | 2022+ | ✅ | ✅ | -| Oracle | - | ❌ | ❌ | -| SQLite | - | ❌ | ❌ | +| 数据库 | 版本 | 支持建库建表 | 状态 | +| ---------- | --------- | :----------: | :--: | +| MariaDB | 10.11.10+ | ✅ | ✅ | +| PostgreSQL | 13+ | ✅ | ✅ | +| MySQL | 8.0+ | ✅ | ✅ | +| SQL Server | 2022+ | ✅ | ✅ | +| Oracle | - | ❌ | ❌ | +| SQLite | - | ❌ | ❌ | ## 快速开始 @@ -125,7 +129,17 @@ EOM.TSHotelManagement.Web ``` 3. **配置密钥**: - 在 `appsettings.json` 中设置 `Jwt__Key` (用于生成 Token) 和 `DataProtection` 相关的 Key (用于数据加解密)。 + 在 `appsettings.json` 中设置 JWT 相关配置: + ```json + { + "Jwt": { + "Key": "your-secret-key-must-be-long-enough", + "ExpiryMinutes": 15, // Access Token 过期时间(分钟) + "RefreshTokenExpiryDays": 7 // Refresh Token 过期时间(天) + } + } + ``` + 同时配置 `DataProtection` 相关的 Key (用于敏感数据加解密)。 4. **运行项目**: 使用 Visual Studio 打开 `EOM.TSHotelManagement.Web.sln` 并启动 `EOM.TSHotelManagement.API` 项目。 @@ -173,7 +187,8 @@ docker run -d \ |ASPNETCORE_ENVIRONMENT|系统环境(决定Dataprotection Key的生成位置以及环境判断)|Y|docker|docker| |{默认数据库(e.g:MariaDB/MySql/SqlServer/PgSql)}ConnectStr|对应数据库链接字符串|Y|N/A|N/A| |Jwt__Key|JWT Key|Y|无,必须设置|N/A| -|Jwt__ExpiryMinutes|token有效时间/分钟|Y|20|N/A| +|Jwt__ExpiryMinutes|Access Token有效时间/分钟|Y|15|N/A| +|Jwt__RefreshTokenExpiryDays|Refresh Token有效时间/天|Y|7|N/A| |Lsky__Enabled|是否启用兰空图床集成|Y|false|true/false| |Lsky__BaseAddress|兰空图床基础地址|Y|N/A|N/A| |Lsky__Email|兰空图床账户邮箱|Y|N/A|N/A| @@ -193,12 +208,20 @@ docker run -d \ |AllowedOrigins__0|允许域站点,用于开发环境|Y|http://localhost:8080|http://localhost:8080| |AllowedOrigins__1|允许域站点,用于生产环境|Y|https://www.yourdomain.com|https://www.yourdomain.com| |SoftwareVersion|软件版本号,用于标记说明|N|N/A|N/A| -|JobKeys__0|定时任务1|Y|ReservationExpirationCheckJob|ReservationExpirationCheckJob| -|JobKeys__1|定时任务2|Y|MailServiceCheckJob|MailServiceCheckJob| -|JobKeys__2|定时任务3|Y|RedisServiceCheckJob|RedisServiceCheckJob| +|JobKeys__0|定时任务1|Y|ReservationExpirationCheckJob:0 0 1 * * ?|JobName:CronExpression| +|JobKeys__1|定时任务2|Y|MailServiceCheckJob:0 */5 * * * ?|JobName:CronExpression| +|JobKeys__2|定时任务3|Y|RedisServiceCheckJob:0 */5 * * * ?|JobName:CronExpression| |Redis__Enabled|是否启用Redis服务|N|false|true/false| |Redis__ConnectionString|Redis连接字符串|N|N/A|N/A| |Redis__DefaultDatabase|默认数据库|N|0|0| +|Redis__ConnectTimeoutMs|Redis 建连超时(毫秒)|N|5000|1000~30000| +|Redis__AsyncTimeoutMs|Redis 异步命令超时(毫秒)|N|2000|500~30000| +|Redis__SyncTimeoutMs|Redis 同步命令超时(毫秒)|N|2000|500~30000| +|Redis__KeepAliveSeconds|Redis KeepAlive 间隔(秒)|N|15|5~300| +|Redis__ConnectRetry|Redis 连接重试次数|N|3|1~10| +|Redis__ReconnectRetryBaseDelayMs|Redis 重连指数退避基准延迟(毫秒)|N|3000|500~30000| +|Redis__OperationTimeoutMs|JWT 吊销检查操作超时(毫秒)|N|1200|200~5000| +|Redis__FailureCooldownSeconds|Redis 失败后熔断冷却时间(秒)|N|30|5~300| |Idempotency__Enabled|是否启用幂等键中间件|N|true|true/false| |Idempotency__EnforceKey|是否强制写请求必须携带 Idempotency-Key|N|false|true/false| |Idempotency__MaxKeyLength|Idempotency-Key 最大长度|N|128|>=16 的整数|