From 76ca46ccb07538b706ca1bf42daa5eb1f4572426 Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Sat, 14 Mar 2026 07:29:07 +0000 Subject: [PATCH 1/7] =?UTF-8?q?!58=20=E6=96=B0=E5=A2=9E=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=20Wiki=20=E9=93=BE=E6=8E=A5=E8=87=B3=E4=B8=AD=E8=8B=B1?= =?UTF-8?q?=E6=96=87=20README=20=E9=A1=B6=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 头像上传接口及服务异步化,优化图片格式检测 * !59 收藏夹快照支持乐观锁,增强图片上传安全性 * 收藏夹快照支持乐观锁,增强图片上传安全性 * 新增项目 Wiki 链接至中英文 README 顶部 * 重构客户消费DTO与接口,统一版本号获取方式 * 统一人员字段抽象为Personnel基类,重构相关代码 * 新增个人中心功能及相关底层优化 * 新增职位导出权限配置 * 导出权限、分页优化及员工打卡业务重构 * 实现收藏夹功能,包括保存和获取用户收藏夹快照的接口及服务 * 添加定时任务列表接口。 * 增强Redis配置与定时任务,新增会员等级自动升级 * 优化预约房间业务流程。 * 修改表结构 * 将权限分配的数据,从软删除改为硬删除,节省表空间 * 剥离菜单与权限代码授权逻辑。 --- .env.example | 15 +- .gitignore | 1 + .../Authorization/PermissionsAuthorization.cs | 5 +- .../FavoriteCollectionController.cs | 44 ++ .../Application/Profile/ProfileController.cs | 78 +++ .../Business/Spend/SpendController.cs | 6 +- .../Quartz/QuartzController.cs | 27 + .../SystemManagement/Role/RoleController.cs | 10 + .../EOM.TSHotelManagement.API.csproj | 2 +- .../Extensions/ApplicationExtensions.cs | 2 +- .../Extensions/PermissionSyncExtensions.cs | 9 +- .../Extensions/ServiceExtensions.cs | 112 ++-- .../Filters/RequestLoggingMiddleware.cs | 30 +- .../appsettings.Application.json | 14 +- .../appsettings.Database.json | 10 +- .../Constant/CheckTypeConstant.cs | 22 + .../Constant/SpendTypeConstant.cs | 4 +- .../EOM.TSHotelManagement.Common.csproj | 10 +- .../Helper/ClaimsPrincipalExtensions.cs | 15 + .../Helper/DataProtectionHelper.cs | 6 +- .../Helper/EntityMapper.cs | 141 +++-- .../Helper/JwtTokenRevocationService.cs | 114 +++- .../Helper/LskyHelper.cs | 4 +- .../Helper/RedisHelper.cs | 66 +- .../Helper/SoftwareVersionHelper.cs | 51 ++ .../AutomaticallyUpgradeMembershipLevelJob.cs | 199 ++++++ .../ReservationExpirationCheckJob.cs | 7 +- .../Dto/ReadFavoriteCollectionOutputDto.cs | 23 + .../Dto/SaveFavoriteCollectionInputDto.cs | 44 ++ .../Dto/SaveFavoriteCollectionOutputDto.cs | 28 + .../Profile/Dto/ChangePasswordInputDto.cs | 19 + .../Profile/Dto/CurrentProfileOutputDto.cs | 57 ++ .../Profile/Dto/UploadAvatarInputDto.cs | 10 + .../Profile/Dto/UploadAvatarOutputDto.cs | 7 + .../Asset/Dto/Asset/ReadAssetOutputDto.cs | 4 +- .../Dto/Customer/CreateCustomerInputDto.cs | 10 +- .../Dto/Customer/ReadCustomerInputDto.cs | 8 +- .../Dto/Customer/ReadCustomerOutputDto.cs | 12 +- .../Dto/Customer/UpdateCustomerInputDto.cs | 10 +- .../Business/Reser/Dto/CreateReserInputDto.cs | 1 + .../Dto/Spend/AddCustomerSpendInputDto.cs | 11 +- .../Dto/Spend/UndoCustomerSpendInputDto.cs | 9 + .../Dto/Employee/CreateEmployeeInputDto.cs | 2 +- .../Dto/Employee/ReadEmployeeInputDto.cs | 4 +- .../Dto/Employee/ReadEmployeeOutputDto.cs | 4 +- .../Dto/Employee/UpdateEmployeeInputDto.cs | 2 +- .../CreateEmployeeCheckInputDto.cs | 24 - .../GrantRolePermissionsInputDto.cs | 4 +- .../Dto/Permission/ReadRoleGrantOutputDto.cs | 14 + .../Permission/UserRolePermissionOutputDto.cs | 4 +- .../Dto/Quartz/ReadQuartzJobOutputDto.cs | 26 + .../DatabaseInitializer.cs | 79 +-- .../EOM.TSHotelManagement.Data.csproj | 4 +- .../Repository/GenericRepository.cs | 16 +- .../UserFavoriteCollection.cs | 94 +++ .../Application/NavBar/NavBar.cs | 4 +- EOM.TSHotelManagement.Domain/BaseEntity.cs | 43 -- .../Business/Asset/Asset.cs | 6 +- .../Business/Customer/CustoSpend.cs | 42 -- .../Business/Customer/CustoType.cs | 4 +- .../Business/Customer/Customer.cs | 64 +- .../Business/Customer/CustomerAccount.cs | 2 +- .../Business/Customer/PassPortType.cs | 4 +- .../EnergyManagement/EnergyManagement.cs | 4 +- .../Business/News/News.cs | 4 +- .../PromotionContent/PromotionContent.cs | 4 +- .../Business/Reser/Reser.cs | 4 +- .../Business/Room/Room.cs | 4 +- .../Business/Room/RoomType.cs | 4 +- .../Business/Sellthing/SellThing.cs | 4 +- .../Business/Spend/Spend.cs | 4 +- .../Common/AuditEntity.cs | 29 + .../Common/BaseEntity.cs | 18 + .../Common/Personnel.cs | 30 + .../Common/SoftDeleteEntity.cs | 14 + .../EOM.TSHotelManagement.Domain.csproj | 2 +- .../Employee/Employee.cs | 106 +--- .../Employee/EmployeeCheck.cs | 4 +- .../Employee/EmployeeHistory.cs | 4 +- .../Employee/EmployeePhoto.cs | 4 +- .../Employee/EmployeeRewardPunishment.cs | 4 +- .../Employee/RewardPunishmentType.cs | 6 +- .../SystemManagement/Administrator.cs | 29 +- .../SystemManagement/AdministratorPhoto.cs | 20 + .../SystemManagement/AdministratorType.cs | 4 +- .../SystemManagement/AppointmentNotice.cs | 4 +- .../SystemManagement/AppointmentNoticeType.cs | 4 +- .../SystemManagement/Department.cs | 4 +- .../SystemManagement/Education.cs | 4 +- .../SystemManagement/Menu.cs | 6 +- .../SystemManagement/Nation.cs | 4 +- .../SystemManagement/Permission.cs | 4 +- .../SystemManagement/Position.cs | 4 +- .../SystemManagement/Role.cs | 6 +- .../SystemManagement/RolePermission.cs | 23 +- .../SystemManagement/SupervisionStatistics.cs | 4 +- .../SystemManagement/SystemInformation.cs | 53 -- .../SystemManagement/TwoFactorAuth.cs | 2 +- .../SystemManagement/TwoFactorRecoveryCode.cs | 2 +- .../SystemManagement/UserRole.cs | 6 +- .../SystemManagement/VipLevelRule.cs | 4 +- .../Util/OperationLog.cs | 4 +- .../Util/RequestLog.cs | 4 +- .../Config/MailConfig.cs | 3 +- .../Config/RedisConfig.cs | 8 + ...OM.TSHotelManagement.Infrastructure.csproj | 4 +- .../Factory/LskyConfigFactory.cs | 5 +- .../Factory/MailConfigFactory.cs | 5 +- .../Factory/RedisConfigFactory.cs | 11 +- .../EntityBuilder.cs | 507 ++++++++------- .../FavoriteCollectionService.cs | 433 +++++++++++++ .../IFavoriteCollectionService.cs | 23 + .../Application/Profile/IProfileService.cs | 15 + .../Application/Profile/ProfileService.cs | 586 ++++++++++++++++++ .../Business/Asset/AssetService.cs | 4 +- .../Account/CustomerAccountService.cs | 15 +- .../Business/Customer/CustomerService.cs | 59 +- .../Permission/CustomerPermissionService.cs | 95 ++- .../Business/Reser/ReserService.cs | 2 +- .../Business/Room/RoomService.cs | 58 +- .../Business/Spend/ISpendService.cs | 8 +- .../Business/Spend/SpendService.cs | 92 ++- .../Dashboard/DashboardService.cs | 8 +- .../EOM.TSHotelManagement.Service.csproj | 2 +- .../Employee/Check/EmployeeCheckService.cs | 89 ++- .../Employee/EmployeeService.cs | 61 +- .../Permission/EmployeePermissionService.cs | 91 ++- .../RewardPunishmentService.cs | 4 +- .../Administrator/AdminService.cs | 110 ++-- .../SystemManagement/Base/BaseService.cs | 12 +- .../SystemManagement/Menu/MenuService.cs | 213 ++++--- .../SystemManagement/Notice/NoticeService.cs | 12 +- .../Quartz/IQuartzAppService.cs | 10 + .../Quartz/QuartzAppService.cs | 100 +++ .../SystemManagement/Role/IRoleAppService.cs | 7 + .../SystemManagement/Role/RoleAppService.cs | 159 ++++- .../SupervisionStatisticsService.cs | 11 +- .../VipRule/VipRuleAppService.cs | 14 +- .../Util/UtilService.cs | 4 +- README.en.md | 15 +- README.md | 15 +- 141 files changed, 3712 insertions(+), 1309 deletions(-) create mode 100644 EOM.TSHotelManagement.API/Controllers/Application/FavoriteCollection/FavoriteCollectionController.cs create mode 100644 EOM.TSHotelManagement.API/Controllers/Application/Profile/ProfileController.cs create mode 100644 EOM.TSHotelManagement.API/Controllers/SystemManagement/Quartz/QuartzController.cs create mode 100644 EOM.TSHotelManagement.Common/Constant/CheckTypeConstant.cs create mode 100644 EOM.TSHotelManagement.Common/Helper/ClaimsPrincipalExtensions.cs create mode 100644 EOM.TSHotelManagement.Common/Helper/SoftwareVersionHelper.cs create mode 100644 EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs rename EOM.TSHotelManagement.Common/QuartzWorkspace/{Job => BusinessJob}/ReservationExpirationCheckJob.cs (94%) create mode 100644 EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/ReadFavoriteCollectionOutputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionInputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Application/FavoriteCollection/Dto/SaveFavoriteCollectionOutputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Application/Profile/Dto/ChangePasswordInputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Application/Profile/Dto/CurrentProfileOutputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarInputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Application/Profile/Dto/UploadAvatarOutputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UndoCustomerSpendInputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/ReadRoleGrantOutputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/SystemManagement/Dto/Quartz/ReadQuartzJobOutputDto.cs create mode 100644 EOM.TSHotelManagement.Domain/Application/FavoriteCollection/UserFavoriteCollection.cs delete mode 100644 EOM.TSHotelManagement.Domain/BaseEntity.cs delete mode 100644 EOM.TSHotelManagement.Domain/Business/Customer/CustoSpend.cs create mode 100644 EOM.TSHotelManagement.Domain/Common/AuditEntity.cs create mode 100644 EOM.TSHotelManagement.Domain/Common/BaseEntity.cs create mode 100644 EOM.TSHotelManagement.Domain/Common/Personnel.cs create mode 100644 EOM.TSHotelManagement.Domain/Common/SoftDeleteEntity.cs create mode 100644 EOM.TSHotelManagement.Domain/SystemManagement/AdministratorPhoto.cs delete mode 100644 EOM.TSHotelManagement.Domain/SystemManagement/SystemInformation.cs create mode 100644 EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs create mode 100644 EOM.TSHotelManagement.Service/Application/FavoriteCollection/IFavoriteCollectionService.cs create mode 100644 EOM.TSHotelManagement.Service/Application/Profile/IProfileService.cs create mode 100644 EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs create mode 100644 EOM.TSHotelManagement.Service/SystemManagement/Quartz/IQuartzAppService.cs create mode 100644 EOM.TSHotelManagement.Service/SystemManagement/Quartz/QuartzAppService.cs diff --git a/.env.example b/.env.example index 9920f34..203c787 100644 --- a/.env.example +++ b/.env.example @@ -25,9 +25,10 @@ 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 +53,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 4605860..aa1f521 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/PermissionsAuthorization.cs b/EOM.TSHotelManagement.API/Authorization/PermissionsAuthorization.cs index e37fa55..b2884f6 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 0000000..8093683 --- /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 0000000..02db303 --- /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/Spend/SpendController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs index 136169d..227cca3 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Spend/SpendController.cs @@ -67,13 +67,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/SystemManagement/Quartz/QuartzController.cs b/EOM.TSHotelManagement.API/Controllers/SystemManagement/Quartz/QuartzController.cs new file mode 100644 index 0000000..019999a --- /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 04c9eeb..7657333 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 4e8ca9d..5030be8 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 f3342a5..c08e4fc 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) diff --git a/EOM.TSHotelManagement.API/Extensions/PermissionSyncExtensions.cs b/EOM.TSHotelManagement.API/Extensions/PermissionSyncExtensions.cs index 53b4718..52efbb6 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 14b066a..083b0c1 100644 --- a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs @@ -20,6 +20,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 +60,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 +100,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 => diff --git a/EOM.TSHotelManagement.API/Filters/RequestLoggingMiddleware.cs b/EOM.TSHotelManagement.API/Filters/RequestLoggingMiddleware.cs index ddad22c..a930483 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/appsettings.Application.json b/EOM.TSHotelManagement.API/appsettings.Application.json index b77796a..f5f5b47 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 f4844c2..0c7186c 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.Common/Constant/CheckTypeConstant.cs b/EOM.TSHotelManagement.Common/Constant/CheckTypeConstant.cs new file mode 100644 index 0000000..9568ac6 --- /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 c03aee1..a1dca37 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 a0d5fec..1f09960 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 0000000..91e6dbc --- /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 8241879..267eb33 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 db5d868..0599119 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/JwtTokenRevocationService.cs b/EOM.TSHotelManagement.Common/Helper/JwtTokenRevocationService.cs index 500266b..ea69ddc 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 7fab1b3..27fa423 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 1238879..52b26ca 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 0000000..dca9932 --- /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 0000000..ffd37af --- /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 +{ + [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 94% rename from EOM.TSHotelManagement.Common/QuartzWorkspace/Job/ReservationExpirationCheckJob.cs rename to EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs index 6431906..04f1299 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/Job/ReservationExpirationCheckJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.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; @@ -105,7 +105,10 @@ namespace EOM.TSHotelManagement.Common .SetColumns(r => new Room { RoomStateId = (int)RoomState.Vacant }) .Where(r => shouldReleaseRooms.Contains(r.RoomNumber)) .ExecuteCommand(); - db.Deleteable(r => shouldDeleteReservations.Contains(r.Id)); + db.Updateable() + .SetColumns(r => new Reser { ReservationStatus = 2 }) + .Where(r => shouldDeleteReservations.Contains(r.Id)) + .ExecuteCommand(); } _logger.LogInformation("已为 {ItemName} 发送过期提醒", item.ReservationId); 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 0000000..36e670c --- /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 0000000..55ec5b6 --- /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 0000000..eaceb54 --- /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 0000000..f91dc4d --- /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 0000000..ea9e96d --- /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 0000000..52d50e2 --- /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 0000000..750e186 --- /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/ReadAssetOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetOutputDto.cs index 30a584e..c660305 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 5de097a..7faacf2 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 976b4c5..18d1b94 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 abd0fdd..9bb2609 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 e982f8f..f6a4348 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/Reser/Dto/CreateReserInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs index 187e6fe..26d2e7f 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs @@ -24,6 +24,7 @@ namespace EOM.TSHotelManagement.Contract [Required(ErrorMessage = "预约结束日期为必填字段")] public DateTime ReservationEndDate { get; set; } + public int ReservationStatus { get; set; } = 0; } } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs index 6b0e286..5894dd3 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs @@ -1,9 +1,12 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace EOM.TSHotelManagement.Contract { public class AddCustomerSpendInputDto { + [Required(ErrorMessage = "消费编号为必填字段")] + public string SpendNumber { get; set; } + [Required(ErrorMessage = "房间编号为必填字段")] public string RoomNumber { get; set; } @@ -14,12 +17,10 @@ namespace EOM.TSHotelManagement.Contract public string ProductName { get; set; } [Required(ErrorMessage = "数量为必填字段")] - public int Quantity { get; set; } + public int ConsumptionQuantity { get; set; } [Required(ErrorMessage = "价格为必填字段")] - public decimal Price { get; set; } - - public string WorkerNo { get; set; } + public decimal ProductPrice { get; set; } public string SoftwareVersion { 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 0000000..f9905c4 --- /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/Employee/Dto/Employee/CreateEmployeeInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/Employee/CreateEmployeeInputDto.cs index c8d99c4..5fb8e54 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 f465bf2..f527745 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 66b0108..ee59de2 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 f0364f5..7d12dc3 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 7f2e1f5..d0d7526 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/SystemManagement/Dto/Permission/GrantRolePermissionsInputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/Permission/GrantRolePermissionsInputDto.cs index f593d5a..769edea 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 0000000..7f5e87b --- /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 2e22503..59830c4 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 0000000..8c9d2ab --- /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.Data/DatabaseInitializer/DatabaseInitializer.cs b/EOM.TSHotelManagement.Data/DatabaseInitializer/DatabaseInitializer.cs index e86d9cf..5e931f8 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 bf70ab1..ba5ee0d 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 415449c..36992f4 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 0000000..3e8412a --- /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 b264880..d38eda2 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 68954f7..0000000 --- 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 7f79ea6..4435c37 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 b189e5b..0000000 --- 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 311987e..0f3f379 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 ec58653..6c49b2e 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 957fe62..79046f0 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 66a0717..79dccab 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 84bbe06..d12bc48 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) diff --git a/EOM.TSHotelManagement.Domain/Business/News/News.cs b/EOM.TSHotelManagement.Domain/Business/News/News.cs index fe1706e..57c66bb 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 d6eec3e..211ff73 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 cf497f2..8e27af8 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) diff --git a/EOM.TSHotelManagement.Domain/Business/Room/Room.cs b/EOM.TSHotelManagement.Domain/Business/Room/Room.cs index b161cef..7be37c6 100644 --- a/EOM.TSHotelManagement.Domain/Business/Room/Room.cs +++ b/EOM.TSHotelManagement.Domain/Business/Room/Room.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -32,7 +32,7 @@ namespace EOM.TSHotelManagement.Domain /// 酒店房间信息表 (Hotel Room Information) /// [SugarTable("room", "酒店房间信息表 (Hotel Room Information)")] - public class Room : BaseEntity + public class Room : SoftDeleteEntity { /// /// 编号 (ID) diff --git a/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs b/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs index 783f7c2..95c2f32 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) diff --git a/EOM.TSHotelManagement.Domain/Business/Sellthing/SellThing.cs b/EOM.TSHotelManagement.Domain/Business/Sellthing/SellThing.cs index 8039a86..80ebc57 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 514641f..c1ecda6 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; } diff --git a/EOM.TSHotelManagement.Domain/Common/AuditEntity.cs b/EOM.TSHotelManagement.Domain/Common/AuditEntity.cs new file mode 100644 index 0000000..b2e854e --- /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 0000000..7a0b7af --- /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 0000000..f383a26 --- /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 0000000..e8a8663 --- /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 befa5d9..9a50f0f 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 77db185..1b16746 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 92e40db..89e567b 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) diff --git a/EOM.TSHotelManagement.Domain/Employee/EmployeeHistory.cs b/EOM.TSHotelManagement.Domain/Employee/EmployeeHistory.cs index b1a93b0..56ccbbc 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 2a86f21..b216028 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 e88ee2e..38d311a 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 028cbaa..2e97cd3 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 fb52e73..1b52c3d 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 0000000..d0eebf4 --- /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 d8d04c5..b461216 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 d9acdd4..f639ed7 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 1afdeec..4bd65db 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 00e5a5e..98721a7 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 e96f210..6a7d087 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 ad8152c..c0468ce 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 3f3ce96..168efa1 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 9f96ad8..8bc0bad 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 de74ca0..2f1f310 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 faf2414..5dceac4 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 b2737df..38a7378 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 6359409..3bd6c0e 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 ec4876b..0000000 --- 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 a87512d..db92253 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 108125a..5ddc562 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 11e2f36..df93d59 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 31b9546..87d5c61 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 995bfba..01be2b3 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 c253165..03c2ad8 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/MailConfig.cs b/EOM.TSHotelManagement.Infrastructure/Config/MailConfig.cs index 078f1c0..ace9a51 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 967ad81..c50896b 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/EOM.TSHotelManagement.Infrastructure.csproj b/EOM.TSHotelManagement.Infrastructure/EOM.TSHotelManagement.Infrastructure.csproj index d6e07bf..a60e237 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/LskyConfigFactory.cs b/EOM.TSHotelManagement.Infrastructure/Factory/LskyConfigFactory.cs index f36a3d5..729e2e8 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 63a8bef..b2939eb 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 981eeaf..056ae5b 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 97c460d..4393fad 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,288 @@ 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.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 0000000..b78b673 --- /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 + } + } +} \ No newline at end of file diff --git a/EOM.TSHotelManagement.Service/Application/FavoriteCollection/IFavoriteCollectionService.cs b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/IFavoriteCollectionService.cs new file mode 100644 index 0000000..cdb5e39 --- /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 0000000..13c277d --- /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 0000000..6ce6888 --- /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(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(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 Employee Employee { get; set; } + } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs b/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs index a376301..969313a 100644 --- a/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs +++ b/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs @@ -107,7 +107,7 @@ namespace EOM.TSHotelManagement.Service 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 80cb10a..2f348bb 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; @@ -284,14 +284,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 +319,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 }); diff --git a/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs b/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs index ef10f0c..05603dc 100644 --- a/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs +++ b/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs @@ -110,9 +110,9 @@ 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)) @@ -145,13 +145,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,9 +173,9 @@ 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)) @@ -357,18 +356,18 @@ namespace EOM.TSHotelManagement.Service { 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), + 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.PassportId, out var passportName) ? passportName : "", - IdCardNumber = dataProtector.SafeDecryptCustomerData(source.IdCardNumber), - CustomerAddress = source.CustomerAddress ?? "", - DataInsUsr = source.DataInsUsr, + 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, @@ -387,17 +386,17 @@ namespace EOM.TSHotelManagement.Service { 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), + 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.PassportId, out var passportName) ? passportName : "", + PassportName = passPortTypeMap.TryGetValue(source.IdCardType, out var passportName) ? passportName : "", IdCardNumber = dataProtector.SafeDecryptCustomerData(source.IdCardNumber), - CustomerAddress = source.CustomerAddress ?? "", + Address = source.Address ?? "", DataInsUsr = source.DataInsUsr, DataInsDate = source.DataInsDate, DataChgUsr = source.DataChgUsr, @@ -455,17 +454,17 @@ namespace EOM.TSHotelManagement.Service return new SingleOutputDto { Code = BusinessStatusCode.InternalServerError, 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 02fa017..421b42a 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/Reser/ReserService.cs b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs index 888c7e0..23f86b8 100644 --- a/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs +++ b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs @@ -184,7 +184,7 @@ namespace EOM.TSHotelManagement.Service .ToList(); Reser res = null; - res = reserRepository.GetFirst(a => a.ReservationRoomNumber == readReserInputDt.ReservationRoomNumber && a.IsDelete != 1); + res = reserRepository.GetFirst(a => a.ReservationRoomNumber == readReserInputDt.ReservationRoomNumber && a.ReservationStatus == 0 && a.IsDelete != 1); //解密联系方式 var sourceTelStr = dataProtector.SafeDecryptReserData(res.ReservationPhoneNumber); res.ReservationPhoneNumber = sourceTelStr; diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs index c32440a..9fff45f 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs @@ -192,7 +192,7 @@ namespace EOM.TSHotelManagement.Service .Where(a => a.IsDelete != 1 && customerNumbers.Contains(a.CustomerNumber)) .ToList() .GroupBy(a => a.CustomerNumber) - .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.CustomerName ?? "", StringComparer.OrdinalIgnoreCase); + .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.Name ?? "", StringComparer.OrdinalIgnoreCase); } var helper = new EnumHelper(); @@ -705,7 +705,7 @@ namespace EOM.TSHotelManagement.Service }, a => a.CustomerNumber == transferRoomDto.CustomerNumber); } - var customerType = custoTypeRepository.GetFirst(a => a.CustomerType == customer.CustomerType); + var customerType = custoTypeRepository.GetFirst(a => a.CustomerType == customer.CustomerType && a.IsDelete != 1); if (customerType.IsNullOrEmpty()) return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The customer type does not exist", "客户类型不存在"), Code = BusinessStatusCode.InternalServerError }; @@ -801,9 +801,14 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("The customer does not exist", "客户不存在"), Code = BusinessStatusCode.InternalServerError }; var room = roomRepository.GetFirst(a => a.RoomNumber == checkoutRoomDto.RoomNumber); + + var checkinDate = room.LastCheckInTime; + var checkoutDate = room.LastCheckOutTime; + //更新房间状态 room.CustomerNumber = string.Empty; - room.LastCheckOutTime = DateOnly.FromDateTime(DateTime.Now); + room.LastCheckInTime = DateOnly.MinValue; + room.LastCheckOutTime = DateOnly.MinValue; room.RoomStateId = (int)RoomState.Dirty; var roomUpdateResult = roomRepository.Update(room); if (!roomUpdateResult) @@ -815,14 +820,14 @@ namespace EOM.TSHotelManagement.Service var energy = new EnergyManagement { InformationId = uniqueCode.GetNewId("EM-"), - StartDate = (DateOnly)room.LastCheckInTime, - EndDate = DateOnly.FromDateTime((DateTime)checkoutRoomDto.DataChgDate), + StartDate = checkinDate ?? DateOnly.MinValue, + EndDate = DateOnly.FromDateTime(DateTime.Today), WaterUsage = checkoutRoomDto.WaterUsage, PowerUsage = checkoutRoomDto.ElectricityUsage, - Recorder = checkoutRoomDto.DataChgUsr, + Recorder = "System", CustomerNumber = room.CustomerNumber, RoomNumber = checkoutRoomDto.RoomNumber, - IsDelete = 0 + IsDelete = 0, }; energyRepository.Insert(energy); @@ -846,6 +851,34 @@ namespace EOM.TSHotelManagement.Service } } + // 插入住房消费记录 + var staySpan = DateTime.Now - room.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue); + var stayDays = Math.Max((int)Math.Ceiling(staySpan.TotalDays), 1); + var customerType = custoTypeRepository.GetSingle(a => a.CustomerType == customer.First().CustomerType && a.IsDelete != 1); + 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 roomBill = room.RoomRent * stayDays * discount; + var bill = new Spend + { + SpendNumber = uniqueCode.GetNewId("SP-"), + ProductName = $"居住 {checkoutRoomDto.RoomNumber} 共 {stayDays} 天", + SettlementStatus = ConsumptionConstant.Settled.Code, + ConsumptionType = SpendTypeConstant.Room.Code, + ConsumptionQuantity = stayDays, + ConsumptionTime = checkinDate.Value.ToDateTime(TimeOnly.MinValue), + ProductNumber = room.RoomNumber, + ProductPrice = room.RoomRent, + ConsumptionAmount = roomBill, + CustomerNumber = room.CustomerNumber, + RoomNumber = checkoutRoomDto.RoomNumber, + IsDelete = 0, + }; + spendRepository.Insert(bill); + scope.Complete(); } } @@ -871,12 +904,12 @@ namespace EOM.TSHotelManagement.Service var customer = new Customer { CustomerNumber = checkinRoomByReservationDto.CustomerNumber, - CustomerName = checkinRoomByReservationDto.CustomerName, - CustomerGender = checkinRoomByReservationDto.CustomerGender, - CustomerPhoneNumber = checkinRoomByReservationDto.CustomerPhoneNumber, - PassportId = checkinRoomByReservationDto.PassportId, + Name = checkinRoomByReservationDto.CustomerName, + Gender = checkinRoomByReservationDto.CustomerGender ?? 0, + PhoneNumber = checkinRoomByReservationDto.CustomerPhoneNumber, + IdCardType = checkinRoomByReservationDto.PassportId, IdCardNumber = checkinRoomByReservationDto.IdCardNumber, - CustomerAddress = checkinRoomByReservationDto.CustomerAddress, + Address = checkinRoomByReservationDto.CustomerAddress, DateOfBirth = checkinRoomByReservationDto.DateOfBirth, CustomerType = checkinRoomByReservationDto.CustomerType, IsDelete = 0, @@ -901,6 +934,7 @@ namespace EOM.TSHotelManagement.Service } var reser = reserRepository.GetFirst(a => a.ReservationId == checkinRoomByReservationDto.ReservationId && a.IsDelete != 1); + reser.ReservationStatus = 1; reser.IsDelete = 1; var reserUpdateResult = reserRepository.Update(reser); diff --git a/EOM.TSHotelManagement.Service/Business/Spend/ISpendService.cs b/EOM.TSHotelManagement.Service/Business/Spend/ISpendService.cs index bd9d152..e94eb72 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 1fb1e46..65eb0d6 100644 --- a/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs +++ b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -276,24 +276,76 @@ namespace EOM.TSHotelManagement.Service /// /// 撤回客户消费信息 /// - /// + /// /// - public BaseResponse UndoCustomerSpend(UpdateSpendInputDto updateSpendInputDto) + public BaseResponse UndoCustomerSpend(UndoCustomerSpendInputDto undoCustomerSpendInputDto) { + var httpContext = _httpContextAccessor.HttpContext; + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + try { - var existingSpend = spendRepository.GetFirst(a => a.SpendNumber == updateSpendInputDto.SpendNumber && a.IsDelete != 1); + var existingSpend = spendRepository.GetFirst(a => a.Id == undoCustomerSpendInputDto.Id && a.IsDelete != 1); if (existingSpend == null) { return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Spend record not found", "消费记录不存在")); } + + if (existingSpend.ConsumptionType != SpendTypeConstant.Product.Code) + { + return new BaseResponse(BusinessStatusCode.BadRequest, LocalizationHelper.GetLocalizedString("Cancellation of non-commodity consumption records is not allowed", "不允许撤销非商品消费记录")); + } + + var isProductSpend = string.Equals(existingSpend.ConsumptionType, SpendTypeConstant.Product.Code, StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrWhiteSpace(existingSpend.ProductNumber) + && existingSpend.ConsumptionQuantity > 0; + + if (isProductSpend) + { + var product = sellThingRepository.GetFirst(a => a.ProductNumber == existingSpend.ProductNumber && a.IsDelete != 1); + if (product == null) + { + return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Product not found", "商品不存在")); + } + + product.Stock += existingSpend.ConsumptionQuantity; + var productUpdateResult = sellThingRepository.Update(product); + if (!productUpdateResult) + { + return BaseResponseFactory.ConcurrencyConflict(); + } + } + existingSpend.IsDelete = 1; - existingSpend.RowVersion = updateSpendInputDto.RowVersion ?? 0; + existingSpend.RowVersion = undoCustomerSpendInputDto.RowVersion ?? 0; var updateResult = spendRepository.Update(existingSpend); if (!updateResult) { return BaseResponseFactory.ConcurrencyConflict(); } + + var logContent = $"{ClaimsPrincipalExtensions.GetUserNumber(httpContext.User)} 撤销了消费记录: " + + $"房间 {existingSpend.RoomNumber}, " + + $"商品 {existingSpend.ProductName}, " + + $"数量 {existingSpend.ConsumptionQuantity}, " + + $"金额 {existingSpend.ConsumptionAmount.ToString("#,##0.00")}"; + + var context = _httpContextAccessor.HttpContext; + + var log = new OperationLog + { + OperationId = new UniqueCode().GetNewId("OP-"), + OperationTime = Convert.ToDateTime(DateTime.Now), + LogContent = logContent, + LoginIpAddress = context.Connection.RemoteIpAddress?.ToString() ?? string.Empty, + OperationAccount = ClaimsPrincipalExtensions.GetUserNumber(httpContext.User), + LogLevel = (int)Common.LogLevel.Warning, + SoftwareVersion = SoftwareVersionHelper.GetSoftwareVersion(), + }; + operationLogRepository.Insert(log); + + + scope.Complete(); } catch (Exception ex) { @@ -310,7 +362,8 @@ namespace EOM.TSHotelManagement.Service /// public BaseResponse AddCustomerSpend(AddCustomerSpendInputDto addCustomerSpendInputDto) { - if (addCustomerSpendInputDto?.Quantity <= 0 || addCustomerSpendInputDto.Price <= 0) + var httpContext = _httpContextAccessor.HttpContext; + if (addCustomerSpendInputDto?.ConsumptionQuantity <= 0 || addCustomerSpendInputDto.ProductPrice <= 0) { return new BaseResponse() { Message = "商品数量和价格必须大于零", Code = BusinessStatusCode.BadRequest }; } @@ -336,17 +389,15 @@ namespace EOM.TSHotelManagement.Service ? customerType.Discount / 100M : 1M; - decimal realAmount = addCustomerSpendInputDto.Price * addCustomerSpendInputDto.Quantity * discount; + decimal realAmount = addCustomerSpendInputDto.ProductPrice * addCustomerSpendInputDto.ConsumptionQuantity * discount; var existingSpend = spendRepository.AsQueryable().Single(a => a.RoomNumber == addCustomerSpendInputDto.RoomNumber && a.ProductNumber == addCustomerSpendInputDto.ProductNumber && a.IsDelete != 1 && a.SettlementStatus == ConsumptionConstant.UnSettle.Code); if (existingSpend != null) { 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) @@ -362,15 +413,13 @@ namespace EOM.TSHotelManagement.Service RoomNumber = addCustomerSpendInputDto.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); @@ -381,17 +430,17 @@ namespace EOM.TSHotelManagement.Service } var product = sellThingRepository.AsQueryable().Single(a => a.ProductNumber == addCustomerSpendInputDto.ProductNumber); - product.Stock = product.Stock - addCustomerSpendInputDto.Quantity; + product.Stock = product.Stock - addCustomerSpendInputDto.ConsumptionQuantity; var updateResult = sellThingRepository.Update(product); if (!updateResult) { return BaseResponseFactory.ConcurrencyConflict(); } - var logContent = $"{addCustomerSpendInputDto.WorkerNo} 添加了消费记录: " + + var logContent = $"{ClaimsPrincipalExtensions.GetUserNumber(httpContext.User)} 添加了消费记录: " + $"房间 {addCustomerSpendInputDto.RoomNumber}, " + $"商品 {addCustomerSpendInputDto.ProductName}, " + - $"数量 {addCustomerSpendInputDto.Quantity}, " + + $"数量 {addCustomerSpendInputDto.ConsumptionQuantity}, " + $"金额 {realAmount.ToString("#,##0.00")}"; var context = _httpContextAccessor.HttpContext; @@ -402,12 +451,9 @@ namespace EOM.TSHotelManagement.Service OperationTime = Convert.ToDateTime(DateTime.Now), LogContent = logContent, LoginIpAddress = context.Connection.RemoteIpAddress?.ToString() ?? string.Empty, - OperationAccount = addCustomerSpendInputDto.WorkerNo, + OperationAccount = ClaimsPrincipalExtensions.GetUserNumber(httpContext.User), LogLevel = (int)Common.LogLevel.Warning, - SoftwareVersion = addCustomerSpendInputDto.SoftwareVersion, - IsDelete = 0, - DataInsUsr = addCustomerSpendInputDto.WorkerNo, - DataInsDate = Convert.ToDateTime(DateTime.Now) + SoftwareVersion = SoftwareVersionHelper.GetSoftwareVersion(), }; operationLogRepository.Insert(log); diff --git a/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs b/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs index 1517faa..5248f94 100644 --- a/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs +++ b/EOM.TSHotelManagement.Service/Dashboard/DashboardService.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; @@ -116,7 +116,7 @@ namespace EOM.TSHotelManagement.Service 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(); @@ -207,8 +207,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 be64ba7..0a7c44b 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 82e0d9a..13f6eae 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) @@ -172,18 +176,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 205f189..59ed299 100644 --- a/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs +++ b/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs @@ -194,7 +194,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) { @@ -284,7 +284,7 @@ namespace EOM.TSHotelManagement.Service { Id = source.Id, EmployeeId = source.EmployeeId, - EmployeeName = source.EmployeeName, + Name = source.Name, Gender = source.Gender, GenderName = genderMap.TryGetValue(source.Gender, out var genderName) ? genderName : "", DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), @@ -328,7 +328,7 @@ namespace EOM.TSHotelManagement.Service { Id = source.Id, EmployeeId = source.EmployeeId, - EmployeeName = source.EmployeeName, + Name = source.Name, Gender = source.Gender, GenderName = genderMap.TryGetValue(source.Gender, out var genderName) ? genderName : "", DateOfBirth = source.DateOfBirth.ToDateTime(TimeOnly.MinValue), @@ -392,33 +392,34 @@ namespace EOM.TSHotelManagement.Service }) .ToList(); 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 = new EnumHelper().GetDescriptionByName(w.PoliticalAffiliation); var employeePhoto = photoRepository.GetFirst(a => a.EmployeeId.Equals(source.EmployeeId)); if (employeePhoto != null && !string.IsNullOrEmpty(employeePhoto.PhotoPath)) @@ -470,7 +471,7 @@ namespace EOM.TSHotelManagement.Service Data = new ReadEmployeeOutputDto { EmployeeId = w.EmployeeId, - EmployeeName = w.EmployeeName, + Name = w.Name, RequiresTwoFactor = true } }; @@ -486,7 +487,7 @@ namespace EOM.TSHotelManagement.Service Data = new ReadEmployeeOutputDto { EmployeeId = w.EmployeeId, - EmployeeName = w.EmployeeName, + Name = w.Name, RequiresTwoFactor = true } }; @@ -494,33 +495,35 @@ 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.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 +624,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 +667,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/Permission/EmployeePermissionService.cs b/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs index c59a2a8..3ca136e 100644 --- a/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs +++ b/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.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 EmployeePermissionService( @@ -26,12 +27,14 @@ namespace EOM.TSHotelManagement.Service 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 2854668..079de15 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/SystemManagement/Administrator/AdminService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs index 6ad6476..bce0291 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs @@ -63,6 +63,11 @@ namespace EOM.TSHotelManagement.Service /// private readonly GenericRepository permissionRepository; + /// + /// 菜单 + /// + private readonly GenericRepository menuRepository; + /// /// 角色 /// @@ -90,13 +95,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; @@ -374,7 +380,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 }; @@ -639,7 +645,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 +681,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 +711,7 @@ namespace EOM.TSHotelManagement.Service var entity = new UserRole { UserNumber = input.UserNumber, - RoleNumber = role, - IsDelete = 0 + RoleNumber = role }; userRoleRepository.Insert(entity); } @@ -740,7 +745,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 +798,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 +821,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 +836,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 +934,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 +943,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 +956,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 +976,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 +985,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 +1030,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 23dd1d2..3a33895 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs @@ -76,11 +76,6 @@ namespace EOM.TSHotelManagement.Service /// private readonly GenericRepository goodbadTypeRepository; - /// - /// 基础URL - /// - private readonly GenericRepository baseRepository; - /// /// 公告类型 /// @@ -94,7 +89,7 @@ namespace EOM.TSHotelManagement.Service 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) + public BaseService(GenericRepository workerRepository, GenericRepository educationRepository, GenericRepository nationRepository, GenericRepository deptRepository, GenericRepository positionRepository, GenericRepository passPortTypeRepository, GenericRepository custoTypeRepository, GenericRepository goodbadTypeRepository, GenericRepository appointmentNoticeTypeRepository, GenericRepository employeeRepository, GenericRepository customerRepository, GenericRepository appointmentNoticeRepository, ILogger logger) { this.workerRepository = workerRepository; this.educationRepository = educationRepository; @@ -104,7 +99,6 @@ namespace EOM.TSHotelManagement.Service this.passPortTypeRepository = passPortTypeRepository; this.custoTypeRepository = custoTypeRepository; this.goodbadTypeRepository = goodbadTypeRepository; - this.baseRepository = baseRepository; this.appointmentNoticeTypeRepository = appointmentNoticeTypeRepository; this.employeeRepository = employeeRepository; this.customerRepository = customerRepository; @@ -729,7 +723,7 @@ namespace EOM.TSHotelManagement.Service source.ParentDepartmentName = parentDepartment.IsNullOrEmpty() ? "" : parentDepartment.DepartmentName; var departmentLeader = departmentLeaders.SingleOrDefault(a => a.EmployeeId.Equals(source.DepartmentLeader)); - source.LeaderName = departmentLeader.IsNullOrEmpty() ? "" : departmentLeader.EmployeeName; + source.LeaderName = departmentLeader.IsNullOrEmpty() ? "" : departmentLeader.Name; }); var result = EntityMapper.MapList(depts); return new ListOutputDto @@ -1138,7 +1132,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 e774e30..1c4b022 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 4d11897..260897d 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 0000000..7e20009 --- /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 0000000..9a33109 --- /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 cc5218d..49eb25b 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 390b67c..3863db7 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 1b370db..95e446b 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs @@ -64,7 +64,16 @@ namespace EOM.TSHotelManagement.Service var where = SqlFilterBuilder.BuildExpression(readSupervisionStatisticsInputDto ?? new ReadSupervisionStatisticsInputDto()); 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 => diff --git a/EOM.TSHotelManagement.Service/SystemManagement/VipRule/VipRuleAppService.cs b/EOM.TSHotelManagement.Service/SystemManagement/VipRule/VipRuleAppService.cs index 2de9ecb..d7a7e23 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 1f04c1e..f0d2887 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 424be9c..88d4a10 100644 --- a/README.en.md +++ b/README.en.md @@ -8,6 +8,7 @@

## Project Overview @@ -193,12 +194,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 79cae06..ba8b8cb 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@

## 项目简介 @@ -193,12 +194,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 的整数| -- Gitee From 274418f8989c13894910d86f00817b35afdf178e Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Sat, 21 Mar 2026 11:05:15 +0000 Subject: [PATCH 2/7] =?UTF-8?q?!60=20=E6=B7=BB=E5=8A=A0=E5=91=98=E5=B7=A5?= =?UTF-8?q?=E5=B1=A5=E5=8E=86=E6=96=B0=E5=A2=9E=E3=80=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复阻断项 * 修复阻断项 * 修复阻断项 * 添加swagger版本标签进行区分。 * 添加员工履历新增、更新接口。 * 房间管理应增加区域或楼层,不同区域的房间号可能是重复的,房态中应按区域或楼层区分排列,前台客服可高效定位到房间. * merge from remote. * 房间唯一标识升级:支持RoomId/分区/楼层定位 * 新增项目 Wiki 链接至中英文 README 顶部 * 重构客户消费DTO与接口,统一版本号获取方式 * 统一人员字段抽象为Personnel基类,重构相关代码 * 新增个人中心功能及相关底层优化 * 新增职位导出权限配置 * 导出权限、分页优化及员工打卡业务重构 * 实现收藏夹功能,包括保存和获取用户收藏夹快照的接口及服务 * 添加定时任务列表接口。 * 增强Redis配置与定时任务,新增会员等级自动升级 * 优化预约房间业务流程。 * 修改表结构 * 将权限分配的数据,从软删除改为硬删除,节省表空间 * 剥离菜单与权限代码授权逻辑。 --- .../Customer/CustomerAccountController.cs | 1 + .../Business/News/NewsController.cs | 1 + .../Business/Room/RoomController.cs | 7 + .../History/EmployeeHistoryController.cs | 13 +- .../Extensions/ApplicationExtensions.cs | 1 + .../Extensions/ClientApiGroupConvention.cs | 110 ++ .../Extensions/ServiceExtensions.cs | 71 + .../Filters/BusinessOperationAuditFilter.cs | 450 +++++ .../Dto/CreateEnergyManagementInputDto.cs | 20 +- .../Dto/ReadEnergyManagementInputDto.cs | 6 +- .../Dto/ReadEnergyManagementOutputDto.cs | 4 + .../Dto/UpdateEnergyManagementInputDto.cs | 20 +- .../Business/Reser/Dto/CreateReserInputDto.cs | 19 +- .../Business/Reser/Dto/ReadReserInputDto.cs | 1 + .../Business/Reser/Dto/ReadReserOutputDto.cs | 7 + .../Business/Reser/Dto/UpdateReserInputDto.cs | 18 +- .../Room/Dto/CheckinRoomByReservationDto.cs | 8 +- .../Business/Room/Dto/CheckoutRoomDto.cs | 6 +- .../Room/Dto/Room/CreateRoomInputDto.cs | 2 + .../Room/Dto/Room/ReadRoomInputDto.cs | 6 +- .../Room/Dto/Room/ReadRoomOutputDto.cs | 15 + .../Room/Dto/Room/ReadRoomPricingOutputDto.cs | 21 + .../Room/Dto/Room/UpdateRoomInputDto.cs | 10 +- .../Dto/RoomType/CreateRoomTypeInputDto.cs | 1 + .../Room/Dto/RoomType/ReadRoomTypeInputDto.cs | 3 + .../Dto/RoomType/ReadRoomTypeOutputDto.cs | 1 + .../Dto/RoomType/RoomTypePricingItemDto.cs | 13 + .../Dto/RoomType/UpdateRoomTypeInputDto.cs | 1 + .../Business/Room/Dto/TransferRoomDto.cs | 12 +- .../Dto/Spend/AddCustomerSpendInputDto.cs | 14 +- .../Spend/Dto/Spend/CreateSpendInputDto.cs | 16 +- .../Spend/Dto/Spend/ReadSpendInputDto.cs | 2 + .../Spend/Dto/Spend/ReadSpendOutputDto.cs | 7 + .../Spend/Dto/Spend/UpdateSpendInputDto.cs | 14 +- .../ReadEmployeeCheckOutputDto.cs | 12 +- .../CreateEmployeeHistoryInputDto.cs | 31 +- .../UpdateEmployeeHistoryInputDto.cs | 33 +- .../EnergyManagement/EnergyManagement.cs | 7 +- .../Business/Reser/Reser.cs | 3 + .../Business/Room/Room.cs | 180 +- .../Business/Room/RoomType.cs | 8 + .../Business/Spend/Spend.cs | 3 + .../Employee/EmployeeCheck.cs | 6 - .../Constant/CodeConstantBase.cs | 53 +- .../EntityBuilder.cs | 1 + .../EnergyManagementService.cs | 249 +-- .../Business/Reser/ReserService.cs | 526 +++--- .../Business/Room/IRoomService.cs | 9 +- .../Business/Room/RoomLocatorHelper.cs | 101 ++ .../Business/Room/RoomPricingEvaluation.cs | 14 + .../Business/Room/RoomPricingHelper.cs | 117 ++ .../Business/Room/RoomReferenceHelper.cs | 64 + .../Business/Room/RoomService.cs | 1564 +++++++++-------- .../Business/Room/RoomTypeService.cs | 92 +- .../Business/Spend/SpendService.cs | 567 +++--- .../Dashboard/DashboardService.cs | 2 +- .../Employee/Check/EmployeeCheckService.cs | 7 +- .../History/EmployeeHistoryService.cs | 29 +- .../History/IEmployeeHistoryService.cs | 11 +- 59 files changed, 2943 insertions(+), 1647 deletions(-) create mode 100644 EOM.TSHotelManagement.API/Extensions/ClientApiGroupConvention.cs create mode 100644 EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs create mode 100644 EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs create mode 100644 EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs create mode 100644 EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs create mode 100644 EOM.TSHotelManagement.Service/Business/Room/RoomPricingEvaluation.cs create mode 100644 EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs create mode 100644 EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs index 15b12f0..dbe5e2f 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs @@ -6,6 +6,7 @@ using System.Security.Claims; namespace EOM.TSHotelManagement.WebApi.Controllers { + [ApiExplorerSettings(GroupName = "v1_Mobile")] public class CustomerAccountController : ControllerBase { private readonly ICustomerAccountService _customerAccountService; diff --git a/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs b/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs index 072e3e3..6eecfe0 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/Room/RoomController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs index e2530ce..c9adfd3 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs @@ -145,6 +145,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/Employee/History/EmployeeHistoryController.cs b/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs index a74afed..d8e0890 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/Extensions/ApplicationExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs index c08e4fc..66972cb 100644 --- a/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs @@ -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 0000000..53cdafe --- /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/ServiceExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs index 083b0c1..0a79b85 100644 --- a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs @@ -255,10 +255,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 => @@ -302,6 +306,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 0000000..d357abf --- /dev/null +++ b/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs @@ -0,0 +1,450 @@ +using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Contract; +using EOM.TSHotelManagement.Data; +using EOM.TSHotelManagement.Domain; +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) + { + 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.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs index 6451a26..2cdb089 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 8cb9fe0..a23f97f 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 9f179e3..7774867 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 9dce1db..7d6029b 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 26d2e7f..cf82876 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs @@ -4,27 +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 7dae661..0466e2b 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 9ce46d1..8816cc4 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 c0ed9a9..cb69d7b 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 e24472d..2c634c6 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 96c653a..8453d24 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 12adeb5..5fa473a 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 508d6bd..d7fc935 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 281357c..1d7a3da 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 0000000..3175e3a --- /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 e5caef7..a9e3142 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 0b43976..51c33bb 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 4e21263..4329350 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 cdf0356..cb72734 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 0000000..d30fb97 --- /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 075b67d..18896fd 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 4a4d3d5..52519aa 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 5894dd3..e6cc288 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs @@ -4,22 +4,24 @@ namespace EOM.TSHotelManagement.Contract { public class AddCustomerSpendInputDto { - [Required(ErrorMessage = "消费编号为必填字段")] + [Required(ErrorMessage = "SpendNumber is required.")] public string SpendNumber { get; set; } - [Required(ErrorMessage = "房间编号为必填字段")] 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 = "数量为必填字段")] + [Required(ErrorMessage = "ConsumptionQuantity is required.")] public int ConsumptionQuantity { get; set; } - [Required(ErrorMessage = "价格为必填字段")] + [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 c9fd6f0..a1eb1d8 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 9a87c94..7467751 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 4c92a75..801a9f5 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/UpdateSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs index 60413f8..f75fd45 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/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs index 0b2ec8c..792fc39 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 5a6f6f4..ff36650 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 84c425b..94db020 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.Domain/Business/EnergyManagement/EnergyManagement.cs b/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs index d12bc48..32c2891 100644 --- a/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs +++ b/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs @@ -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/Reser/Reser.cs b/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs index 8e27af8..93bbf61 100644 --- a/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs +++ b/EOM.TSHotelManagement.Domain/Business/Reser/Reser.cs @@ -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 7be37c6..00ca7b2 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)")] + [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 95c2f32..3812a5f 100644 --- a/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs +++ b/EOM.TSHotelManagement.Domain/Business/Room/RoomType.cs @@ -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/Spend/Spend.cs b/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs index c1ecda6..412fd44 100644 --- a/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs +++ b/EOM.TSHotelManagement.Domain/Business/Spend/Spend.cs @@ -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/Employee/EmployeeCheck.cs b/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs index 89e567b..dcb79fc 100644 --- a/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs +++ b/EOM.TSHotelManagement.Domain/Employee/EmployeeCheck.cs @@ -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.Infrastructure/Constant/CodeConstantBase.cs b/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs index 9da8006..f9e2bd6 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.Migration/EntityBuilder.cs b/EOM.TSHotelManagement.Migration/EntityBuilder.cs index 4393fad..01aa42a 100644 --- a/EOM.TSHotelManagement.Migration/EntityBuilder.cs +++ b/EOM.TSHotelManagement.Migration/EntityBuilder.cs @@ -866,6 +866,7 @@ namespace EOM.TSHotelManagement.Migration // 员工履历管理 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 = "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 }, diff --git a/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs b/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs index 09b7d43..f01101f 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().Where(a => a.IsDelete != 1); + 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/Reser/ReserService.cs b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs index 23f86b8..6b2b8e9 100644 --- a/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs +++ b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs @@ -1,59 +1,27 @@ -/* - * 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 Microsoft.Extensions.Logging; -using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; using System.Transactions; namespace EOM.TSHotelManagement.Service { - /// - /// 预约信息接口实现类 - /// public class ReserService : 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) + public ReserService( + GenericRepository reserRepository, + GenericRepository roomRepository, + DataProtectionHelper dataProtector, + ILogger logger) { this.reserRepository = reserRepository; this.roomRepository = roomRepository; @@ -61,24 +29,22 @@ namespace EOM.TSHotelManagement.Service 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 => helper.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.ReservationStartDateEnd), nameof(Reser.ReservationEndDate) } }); + var query = reserRepository.AsQueryable().Where(a => a.IsDelete != 1); var whereExpression = where.ToExpression(); if (whereExpression != null) @@ -86,337 +52,271 @@ namespace EOM.TSHotelManagement.Service 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 => helper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); - Reser res = null; - res = reserRepository.GetFirst(a => a.ReservationRoomNumber == readReserInputDt.ReservationRoomNumber && a.ReservationStatus == 0 && 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 => + var hasOtherActiveReservation = remainingReservations.Any(a => IsSameRoom(a, room)); + if (!hasOtherActiveReservation) { - a.RoomStateId = (int)RoomState.Vacant; - return a; - }).ToList(); - - var roomUpdateResult = roomRepository.UpdateRange(rooms); - if (!roomUpdateResult) - { - 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); - - if (originalReser == null) - { - return new BaseResponse(BusinessStatusCode.NotFound, - LocalizationHelper.GetLocalizedString("Reservation not found", "预约信息不存在")); - } + using var scope = new TransactionScope(); - 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 room = roomRepository.GetFirst(a => a.RoomNumber == r.ReservationRoomNumber); - room.RoomStateId = new EnumHelper().GetEnumValue(RoomState.Reserved); - var updateResult = roomRepository.Update(room); + var entity = new 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 + }; + + reserRepository.Insert(entity); - if (!updateResult) + roomResult.Room.RoomStateId = new 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 = helper.GetEnumDescription(a) }) .ToList(); @@ -430,5 +330,73 @@ namespace EOM.TSHotelManagement.Service }; } + private ReadReserOutputDto MapReservationToOutput(Reser source, 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(Reser reservation, 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 8930b2a..9ec1546 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 0000000..68679c3 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs @@ -0,0 +1,101 @@ +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, + bool includeDeleted = false) + { + if (roomId.HasValue && roomId.Value > 0) + { + var room = includeDeleted + ? repository.GetFirst(a => a.Id == roomId.Value) + : repository.GetFirst(a => a.Id == roomId.Value && a.IsDelete != 1); + + return new RoomResolveResult { Room = room, IsAmbiguous = false }; + } + + if (string.IsNullOrWhiteSpace(roomNumber)) + { + return new RoomResolveResult(); + } + + var normalizedNumber = roomNumber.Trim(); + var normalizedArea = NormalizeArea(roomArea); + var candidates = includeDeleted + ? repository.GetList(a => a.RoomNumber == normalizedNumber) + : repository.GetList(a => a.RoomNumber == normalizedNumber && a.IsDelete != 1); + + 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 0000000..210b138 --- /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 0000000..bbb8b3d --- /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 0000000..0bd758e --- /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, bool includeDeleted = false) + { + return RoomLocatorHelper.Resolve(repository, roomId, null, null, null, includeDeleted); + } + + 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 9fff45f..7694296 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs @@ -1,26 +1,3 @@ -/* - * 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; @@ -29,63 +6,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 { - /// - /// 客房信息接口实现类 - /// public class RoomService : IRoomService { - /// - /// 客房信息 - /// private readonly GenericRepository roomRepository; - - /// - /// 消费记录 - /// private readonly GenericRepository spendRepository; - - /// - /// 客房类型 - /// private readonly GenericRepository roomTypeRepository; - - /// - /// 能耗管理 - /// private readonly GenericRepository energyRepository; - - /// - /// 客户信息 - /// private readonly GenericRepository custoRepository; - - /// - /// 客户类型 - /// private readonly GenericRepository custoTypeRepository; - - /// - /// 会员等级规则 - /// private readonly GenericRepository vipLevelRuleRepository; - - /// - /// 预约信息 - /// 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 +50,351 @@ 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 page = readRoomInputDto.Page > 0 ? readRoomInputDto.Page : 1; - var pageSize = readRoomInputDto.PageSize > 0 ? readRoomInputDto.PageSize : 15; - rooms = query.ToPageList(page, pageSize, ref count); - } - else + var roomResult = ResolveRoom(readRoomInputDto); + if (roomResult.Room == null) { - 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(); - - var customerMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (customerNumbers.Count > 0) - { - customerMap = 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); - } - - 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 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 - }); - }); - } - - return new ListOutputDto - { - Data = new PagedData - { - Items = result, - TotalCount = count - } - }; + return new SingleOutputDto { Data = data }; } - /// - /// 根据房间编号查询房间信息 - /// - /// - /// - public SingleOutputDto SelectRoomByRoomNo(ReadRoomInputDto readRoomInputDto) + public SingleOutputDto DayByRoomNo(ReadRoomInputDto roomInputDto) { - 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 + var roomResult = ResolveRoom(roomInputDto); + if (roomResult.Room == null) { - room = new Room(); + return CreateRoomLookupFailureOutput(roomResult, roomInputDto?.RoomNumber, roomInputDto?.RoomArea, roomInputDto?.RoomFloor); } - var Data = EntityMapper.Map(room); - - return new SingleOutputDto() { Data = Data }; - } - - /// - /// 根据房间编号查询截止到今天住了多少天 - /// - /// - /// - public SingleOutputDto DayByRoomNo(ReadRoomInputDto roomInputDto) - { - var room = roomRepository.GetFirst(a => a.RoomNumber == roomInputDto.RoomNumber); - if (room?.LastCheckInTime != null) + var lastCheckInTime = NormalizeStayDateTime(roomResult.Room.LastCheckInTime); + if (lastCheckInTime.HasValue) { - var days = Math.Abs((room.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue) - DateTime.Now).Days); + var days = CalculateStayDays(lastCheckInTime.Value, DateTime.Now); return new SingleOutputDto { Data = new ReadRoomOutputDto { StayDays = days } }; } + return new SingleOutputDto { Data = new ReadRoomOutputDto { StayDays = 0 } }; } - /// - /// 根据房间编号修改房间信息(入住) - /// - /// - /// public BaseResponse UpdateRoomInfo(UpdateRoomInputDto r) { try { - var room = this.roomRepository.GetFirst(a => a.RoomNumber == r.RoomNumber); + var roomResult = ResolveRoom(r); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, r?.RoomNumber, r?.RoomArea, r?.RoomFloor); + } + + var room = roomResult.Room; room.RoomStateId = r.RoomStateId; room.CustomerNumber = r.CustomerNumber; - room.LastCheckInTime = r.LastCheckInTime; + 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 for room number {RoomNumber}", r.RoomNumber); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + return ErrorResponse(ex); } - return new BaseResponse(); } - /// - /// 根据房间编号修改房间信息(预约) - /// - /// - /// public BaseResponse UpdateRoomInfoWithReser(UpdateRoomInputDto r) { try { - var room = this.roomRepository.GetFirst(a => a.RoomNumber == r.RoomNumber); + var roomResult = ResolveRoom(r); + if (roomResult.Room == null) + { + return CreateRoomLookupFailure(roomResult, r?.RoomNumber, r?.RoomArea, r?.RoomFloor); + } + + var room = roomResult.Room; room.RoomStateId = r.RoomStateId; 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 }; + 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 } - }; - } - catch (Exception ex) - { - return new SingleOutputDto - { - Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), - Code = BusinessStatusCode.InternalServerError - }; + return 0M; } - } - /// - /// 查询已入住房间数量 - /// - /// - 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); + 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) + .ToList() + .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase) + && a.RoomFloor == rn.RoomFloor); + + if (duplicateExists) + { + return new BaseResponse { Message = "This room already exists.", Code = BusinessStatusCode.InternalServerError }; + } var entity = EntityMapper.Map(rn); - entity.LastCheckInTime = DateOnly.MinValue; - entity.LastCheckOutTime = DateOnly.MinValue; + entity.RoomNumber = normalizedRoomNumber; + entity.RoomArea = string.IsNullOrWhiteSpace(normalizedRoomArea) ? null : 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 entity = EntityMapper.Map(rn); - var updateResult = roomRepository.Update(entity); - if (!updateResult) + 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) + .ToList() + .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase) + && a.RoomFloor == rn.RoomFloor); + + 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; + 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,329 +402,718 @@ 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 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 && a.IsDelete != 1); - 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) + { + return new BaseResponse { Message = "RoomId is required", Code = BusinessStatusCode.BadRequest }; + } + + using var scope = CreateTransactionScope(); + + var customer = custoRepository.AsQueryable().Where(a => a.CustomerNumber == checkoutRoomDto.CustomerNumber && a.IsDelete != 1); + if (!customer.Any()) { - 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 = "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 room = roomRepository.GetFirst(a => a.RoomNumber == checkoutRoomDto.RoomNumber); + 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 checkinDate = room.LastCheckInTime; - var checkoutDate = room.LastCheckOutTime; + 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); - //更新房间状态 - room.CustomerNumber = string.Empty; - room.LastCheckInTime = DateOnly.MinValue; - room.LastCheckOutTime = DateOnly.MinValue; - room.RoomStateId = (int)RoomState.Dirty; - var roomUpdateResult = roomRepository.Update(room); - if (!roomUpdateResult) + if (unsettledSpends.Count > 0) + { + unsettledSpends.ForEach(spend => spend.SettlementStatus = ConsumptionConstant.Settled.Code); + if (!spendRepository.UpdateRange(unsettledSpends)) { return BaseResponseFactory.ConcurrencyConflict(); } + } - //添加能源使用记录 - var energy = new EnergyManagement - { - InformationId = uniqueCode.GetNewId("EM-"), - StartDate = checkinDate ?? DateOnly.MinValue, - EndDate = DateOnly.FromDateTime(DateTime.Today), - WaterUsage = checkoutRoomDto.WaterUsage, - PowerUsage = checkoutRoomDto.ElectricityUsage, - Recorder = "System", - CustomerNumber = room.CustomerNumber, - RoomNumber = checkoutRoomDto.RoomNumber, - IsDelete = 0, - }; - energyRepository.Insert(energy); + 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 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) - { - 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(); - } - } + var discount = customerType.Discount > 0 && customerType.Discount < 100 ? customerType.Discount / 100M : 1M; + var roomBill = effectiveRoomRent * stayDays * discount; - // 插入住房消费记录 - var staySpan = DateTime.Now - room.LastCheckInTime.Value.ToDateTime(TimeOnly.MinValue); - var stayDays = Math.Max((int)Math.Ceiling(staySpan.TotalDays), 1); - var customerType = custoTypeRepository.GetSingle(a => a.CustomerType == customer.First().CustomerType && a.IsDelete != 1); - 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 roomBill = room.RoomRent * stayDays * discount; - var bill = new Spend - { - SpendNumber = uniqueCode.GetNewId("SP-"), - ProductName = $"居住 {checkoutRoomDto.RoomNumber} 共 {stayDays} 天", - SettlementStatus = ConsumptionConstant.Settled.Code, - ConsumptionType = SpendTypeConstant.Room.Code, - ConsumptionQuantity = stayDays, - ConsumptionTime = checkinDate.Value.ToDateTime(TimeOnly.MinValue), - ProductNumber = room.RoomNumber, - ProductPrice = room.RoomRent, - ConsumptionAmount = roomBill, - CustomerNumber = room.CustomerNumber, - RoomNumber = checkoutRoomDto.RoomNumber, - IsDelete = 0, - }; - spendRepository.Insert(bill); + 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(); - } + 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, - 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 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 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.ReservationStatus = 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); + } - scope.Complete(); + var room = roomResult.Room; + room.LastCheckInTime = DateTime.Now; + room.CustomerNumber = customer.CustomerNumber; + room.RoomStateId = new 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 number {RoomNumber} by reservation ID {ReservationId}", checkinRoomByReservationDto.RoomNumber, checkinRoomByReservationDto.ReservationId); - return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + logger.LogError(ex, "Error checking in room by reservation"); + return ErrorResponse(ex); } - return new BaseResponse(); + } + + private ListOutputDto BuildRoomList(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(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(); + } + + 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) + { + return new SingleOutputDto { Code = BusinessStatusCode.InternalServerError, Message = ex.Message }; + } + } + + 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(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(Room.RoomArea), + nameof(Room.RoomFloor), + nameof(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() + { + var helper = new EnumHelper(); + return Enum.GetValues(typeof(RoomState)).Cast().ToDictionary(e => (int)e, e => helper.GetEnumDescription(e) ?? ""); + } + + private static ReadRoomOutputDto MapRoomToOutput(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(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(Room room) + { + return EvaluateRoomPricing(room).EffectiveRoomRent; + } + + private static decimal GetEffectiveRoomDeposit(Room room) + { + return EvaluateRoomPricing(room).EffectiveRoomDeposit; + } + + private static RoomPricingEvaluation EvaluateRoomPricing(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(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 ebfadc0..a304bee 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs @@ -99,6 +99,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, @@ -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) && a.IsDelete != 1); + 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(); @@ -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/SpendService.cs b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs index 65eb0d6..c0d1fa0 100644 --- a/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs +++ b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs @@ -1,286 +1,74 @@ -/* - * 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); - } - - 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); - } - - return new ListOutputDto - { - Data = new PagedData - { - Items = result, - TotalCount = count - } - }; - } - #endregion - - #region 根据房间编号查询消费信息 - /// - /// 根据房间编号查询消费信息 - /// - /// - /// - public ListOutputDto SelectSpendByRoomNo(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); - } + 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 SelectSpendInfoAll(ReadSpendInputDto readSpendInputDto) + public SingleOutputDto SumConsumptionAmount(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 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) - { - var page = readSpendInputDto.Page > 0 ? readSpendInputDto.Page : 1; - var pageSize = readSpendInputDto.PageSize > 0 ? readSpendInputDto.PageSize : 15; - spends = query.ToPageList(page, pageSize, ref count); - } - else + if (readSpendInputDto.RoomId.HasValue && readSpendInputDto.RoomId.Value > 0) { - 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 - - 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; - } - - #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(UndoCustomerSpendInputDto undoCustomerSpendInputDto) { - var httpContext = _httpContextAccessor.HttpContext; using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); try @@ -288,29 +76,24 @@ namespace EOM.TSHotelManagement.Service var existingSpend = spendRepository.GetFirst(a => a.Id == undoCustomerSpendInputDto.Id && a.IsDelete != 1); if (existingSpend == null) { - return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Spend record not found", "消费记录不存在")); + return new BaseResponse(BusinessStatusCode.NotFound, "Spend record not found."); } if (existingSpend.ConsumptionType != SpendTypeConstant.Product.Code) { - return new BaseResponse(BusinessStatusCode.BadRequest, LocalizationHelper.GetLocalizedString("Cancellation of non-commodity consumption records is not allowed", "不允许撤销非商品消费记录")); + return new BaseResponse(BusinessStatusCode.BadRequest, "Only product spends can be canceled."); } - var isProductSpend = string.Equals(existingSpend.ConsumptionType, SpendTypeConstant.Product.Code, StringComparison.OrdinalIgnoreCase) - && !string.IsNullOrWhiteSpace(existingSpend.ProductNumber) - && existingSpend.ConsumptionQuantity > 0; - - if (isProductSpend) + if (!string.IsNullOrWhiteSpace(existingSpend.ProductNumber) && existingSpend.ConsumptionQuantity > 0) { var product = sellThingRepository.GetFirst(a => a.ProductNumber == existingSpend.ProductNumber && a.IsDelete != 1); if (product == null) { - return new BaseResponse(BusinessStatusCode.NotFound, LocalizationHelper.GetLocalizedString("Product not found", "商品不存在")); + return new BaseResponse(BusinessStatusCode.NotFound, "Product not found."); } product.Stock += existingSpend.ConsumptionQuantity; - var productUpdateResult = sellThingRepository.Update(product); - if (!productUpdateResult) + if (!sellThingRepository.Update(product)) { return BaseResponseFactory.ConcurrencyConflict(); } @@ -318,89 +101,65 @@ namespace EOM.TSHotelManagement.Service existingSpend.IsDelete = 1; existingSpend.RowVersion = undoCustomerSpendInputDto.RowVersion ?? 0; - var updateResult = spendRepository.Update(existingSpend); - if (!updateResult) + if (!spendRepository.Update(existingSpend)) { return BaseResponseFactory.ConcurrencyConflict(); } - var logContent = $"{ClaimsPrincipalExtensions.GetUserNumber(httpContext.User)} 撤销了消费记录: " + - $"房间 {existingSpend.RoomNumber}, " + - $"商品 {existingSpend.ProductName}, " + - $"数量 {existingSpend.ConsumptionQuantity}, " + - $"金额 {existingSpend.ConsumptionAmount.ToString("#,##0.00")}"; - - var context = _httpContextAccessor.HttpContext; - - var log = new OperationLog - { - OperationId = new UniqueCode().GetNewId("OP-"), - OperationTime = Convert.ToDateTime(DateTime.Now), - LogContent = logContent, - LoginIpAddress = context.Connection.RemoteIpAddress?.ToString() ?? string.Empty, - OperationAccount = ClaimsPrincipalExtensions.GetUserNumber(httpContext.User), - LogLevel = (int)Common.LogLevel.Warning, - SoftwareVersion = SoftwareVersionHelper.GetSoftwareVersion(), - }; - operationLogRepository.Insert(log); - - 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) { - var httpContext = _httpContextAccessor.HttpContext; 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; + 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; - decimal realAmount = addCustomerSpendInputDto.ProductPrice * addCustomerSpendInputDto.ConsumptionQuantity * discount; - - 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.ConsumptionQuantity; existingSpend.ConsumptionAmount += realAmount; - - var result = spendRepository.Update(existingSpend); - if (!result) + if (!spendRepository.Update(existingSpend)) { return BaseResponseFactory.ConcurrencyConflict(); } @@ -410,7 +169,8 @@ 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.ConsumptionQuantity, @@ -422,57 +182,34 @@ namespace EOM.TSHotelManagement.Service 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.ConsumptionQuantity; - 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 = $"{ClaimsPrincipalExtensions.GetUserNumber(httpContext.User)} 添加了消费记录: " + - $"房间 {addCustomerSpendInputDto.RoomNumber}, " + - $"商品 {addCustomerSpendInputDto.ProductName}, " + - $"数量 {addCustomerSpendInputDto.ConsumptionQuantity}, " + - $"金额 {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 = ClaimsPrincipalExtensions.GetUserNumber(httpContext.User), - LogLevel = (int)Common.LogLevel.Warning, - SoftwareVersion = SoftwareVersionHelper.GetSoftwareVersion(), - }; - 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 @@ -480,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().Where(a => a.IsDelete != 1); + 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 5248f94..180c573 100644 --- a/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs +++ b/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs @@ -145,7 +145,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 { diff --git a/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs b/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs index 13f6eae..4d69711 100644 --- a/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs +++ b/EOM.TSHotelManagement.Service/Employee/Check/EmployeeCheckService.cs @@ -78,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 diff --git a/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs b/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs index de93a8e..15edd02 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 bbcc6f5..6f3fced 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 +} -- Gitee From 4e67e6170b6f468110d507be03979d27b7266917 Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Sun, 5 Apr 2026 06:43:17 +0000 Subject: [PATCH 3/7] !61 update README. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复阻断项 * 修复阻断项 * 修复阻断项。 * update README. * 增强JWT安全性。 * 审查并优化代码结构。 * 修复业务操作日志记录逻辑。 --- .env.example | 3 +- .../BusinessOperationAuditAttribute.cs | 9 + .../Business/Asset/AssetController.cs | 1 + .../Customer/CustomerAccountController.cs | 18 +- .../Business/Customer/CustomerController.cs | 1 + .../EnergyManagementController.cs | 1 + .../PromotionContentController.cs | 1 + .../Business/Reser/ReserController.cs | 1 + .../Business/Room/RoomController.cs | 1 + .../Business/Room/RoomTypeController.cs | 3 +- .../Business/Sellthing/SellthingController.cs | 2 +- .../Business/Spend/SpendController.cs | 1 + .../Employee/EmployeeController.cs | 24 +- .../Controllers/LoginController.cs | 221 ++++++++++++++---- .../Administrator/AdminController.cs | 18 +- ...ler.cs => EmployeePermissionController.cs} | 0 .../Extensions/ServiceExtensions.cs | 2 + .../Filters/BusinessOperationAuditFilter.cs | 6 + .../Filters/ValidationFilter.cs | 17 +- .../appsettings.Services.json | 3 +- .../Helper/EnumHelper.cs | 17 +- .../Helper/JWTHelper.cs | 44 +++- .../AutomaticallyUpgradeMembershipLevelJob.cs | 2 +- .../ReservationExpirationCheckJob.cs | 31 ++- .../ImageHostingServiceCheckJob.cs | 4 +- .../Mail/MailServiceCheckJob.cs | 4 +- .../Redis/RedisServiceCheckJob.cs | 4 +- .../Common/Dto/BaseDto.cs | 6 +- .../TokenRefreshRequestDto.cs | 7 + .../TokenRefreshResponseDto.cs | 8 + .../Config/JwtConfig.cs | 1 + .../Factory/JwtConfigFactory.cs | 12 +- .../FavoriteCollectionService.cs | 6 +- .../Application/Profile/ProfileService.cs | 10 +- .../Business/Asset/AssetService.cs | 6 +- .../Account/CustomerAccountService.cs | 35 ++- .../Business/Customer/CustomerService.cs | 200 +++++----------- .../Business/News/NewsService.cs | 33 +-- .../Business/Reser/ReserService.cs | 52 ++--- .../Business/Room/RoomService.cs | 117 +++++----- .../Dashboard/DashboardService.cs | 91 +------- .../Employee/EmployeeService.cs | 218 ++++++----------- .../Permission/EmployeePermissionService.cs | 4 +- .../Security/TwoFactorAuthService.cs | 4 +- .../Administrator/AdminService.cs | 11 +- .../SystemManagement/Base/BaseService.cs | 110 ++------- README.en.md | 23 +- README.md | 37 ++- 48 files changed, 691 insertions(+), 739 deletions(-) create mode 100644 EOM.TSHotelManagement.API/Authorization/BusinessOperationAuditAttribute.cs rename EOM.TSHotelManagement.API/Controllers/SystemManagement/EmployeePermission/{EmployeeController.cs => EmployeePermissionController.cs} (100%) create mode 100644 EOM.TSHotelManagement.Contract/TokenRefreshRequestDto.cs create mode 100644 EOM.TSHotelManagement.Contract/TokenRefreshResponseDto.cs diff --git a/.env.example b/.env.example index 203c787..98a85a2 100644 --- a/.env.example +++ b/.env.example @@ -18,7 +18,8 @@ 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 diff --git a/EOM.TSHotelManagement.API/Authorization/BusinessOperationAuditAttribute.cs b/EOM.TSHotelManagement.API/Authorization/BusinessOperationAuditAttribute.cs new file mode 100644 index 0000000..addd766 --- /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/Controllers/Business/Asset/AssetController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Asset/AssetController.cs index 08d903c..1fc76e1 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 dbe5e2f..eb178c0 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs @@ -1,7 +1,9 @@ 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 @@ -26,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 78aef59..6b7c98c 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 297d362..78eb4fe 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/PromotionContent/PromotionContentController.cs b/EOM.TSHotelManagement.API/Controllers/Business/PromotionContent/PromotionContentController.cs index 8e00131..b1f5394 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 950862e..79ccfe6 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 c9adfd3..0e90d4b 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; diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomTypeController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomTypeController.cs index c870e6b..cc01fd7 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 1492774..db0a1eb 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 227cca3..9a9f4a5 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; diff --git a/EOM.TSHotelManagement.API/Controllers/Employee/EmployeeController.cs b/EOM.TSHotelManagement.API/Controllers/Employee/EmployeeController.cs index bb4a27f..91d3f34 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/LoginController.cs b/EOM.TSHotelManagement.API/Controllers/LoginController.cs index e3f6415..3cca240 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 f160b3b..2244739 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/Extensions/ServiceExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs index 0a79b85..59f3101 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; diff --git a/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs b/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs index d357abf..4479486 100644 --- a/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs +++ b/EOM.TSHotelManagement.API/Filters/BusinessOperationAuditFilter.cs @@ -2,6 +2,7 @@ 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; @@ -108,6 +109,11 @@ namespace EOM.TSHotelManagement.WebApi.Filters 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); } diff --git a/EOM.TSHotelManagement.API/Filters/ValidationFilter.cs b/EOM.TSHotelManagement.API/Filters/ValidationFilter.cs index 989b0e6..7cadfb5 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.Services.json b/EOM.TSHotelManagement.API/appsettings.Services.json index 98919c7..33e25ae 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/Helper/EnumHelper.cs b/EOM.TSHotelManagement.Common/Helper/EnumHelper.cs index 00854d4..63765c7 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 5714c4d..9ca28d6 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/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs index ffd37af..36f5724 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/AutomaticallyUpgradeMembershipLevelJob.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace EOM.TSHotelManagement.Common +namespace EOM.TSHotelManagement.Common.QuartzWorkspace.BusinessJob { [DisallowConcurrentExecution] public class AutomaticallyUpgradeMembershipLevelJob : IJob diff --git a/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs index 04f1299..0d6105c 100644 --- a/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs +++ b/EOM.TSHotelManagement.Common/QuartzWorkspace/BusinessJob/ReservationExpirationCheckJob.cs @@ -1,3 +1,4 @@ +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Domain; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -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,19 +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.Updateable() - .SetColumns(r => new Reser { ReservationStatus = 2 }) - .Where(r => shouldDeleteReservations.Contains(r.Id)) - .ExecuteCommand(); - } - _logger.LogInformation("已为 {ItemName} 发送过期提醒", item.ReservationId); } catch (Exception ex) @@ -119,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 7ce390c..16b8cd0 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 91e1090..d1cc134 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 1257fca..98a68b2 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/Common/Dto/BaseDto.cs b/EOM.TSHotelManagement.Contract/Common/Dto/BaseDto.cs index fefb777..8c8132e 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/TokenRefreshRequestDto.cs b/EOM.TSHotelManagement.Contract/TokenRefreshRequestDto.cs new file mode 100644 index 0000000..af02dc9 --- /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 0000000..d880f7c --- /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.Infrastructure/Config/JwtConfig.cs b/EOM.TSHotelManagement.Infrastructure/Config/JwtConfig.cs index 9f402a8..7a96adb 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/Factory/JwtConfigFactory.cs b/EOM.TSHotelManagement.Infrastructure/Factory/JwtConfigFactory.cs index 216268a..b70d2c1 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.Service/Application/FavoriteCollection/FavoriteCollectionService.cs b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs index b78b673..643d7ba 100644 --- a/EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs +++ b/EOM.TSHotelManagement.Service/Application/FavoriteCollection/FavoriteCollectionService.cs @@ -18,7 +18,7 @@ namespace EOM.TSHotelManagement.Service private readonly GenericRepository _favoriteCollectionRepository; private readonly GenericRepository _administratorRepository; - private readonly GenericRepository _employeeRepository; + private readonly GenericRepository _employeeRepository; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; @@ -33,7 +33,7 @@ namespace EOM.TSHotelManagement.Service public FavoriteCollectionService( GenericRepository favoriteCollectionRepository, GenericRepository administratorRepository, - GenericRepository employeeRepository, + GenericRepository employeeRepository, IHttpContextAccessor httpContextAccessor, ILogger logger) { @@ -430,4 +430,4 @@ namespace EOM.TSHotelManagement.Service Failed } } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs b/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs index 6ce6888..351ea56 100644 --- a/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs +++ b/EOM.TSHotelManagement.Service/Application/Profile/ProfileService.cs @@ -16,7 +16,7 @@ namespace EOM.TSHotelManagement.Service GenericRepository adminRepository, GenericRepository adminPhotoRepository, GenericRepository adminTypeRepository, - GenericRepository employeeRepository, + GenericRepository employeeRepository, GenericRepository employeePhotoRepository, GenericRepository departmentRepository, GenericRepository positionRepository, @@ -30,7 +30,7 @@ namespace EOM.TSHotelManagement.Service private readonly GenericRepository _adminRepository = adminRepository; private readonly GenericRepository _adminPhotoRepository = adminPhotoRepository; private readonly GenericRepository _adminTypeRepository = adminTypeRepository; - private readonly GenericRepository _employeeRepository = employeeRepository; + private readonly GenericRepository _employeeRepository = employeeRepository; private readonly GenericRepository _employeePhotoRepository = employeePhotoRepository; private readonly GenericRepository _departmentRepository = departmentRepository; private readonly GenericRepository _positionRepository = positionRepository; @@ -228,7 +228,7 @@ namespace EOM.TSHotelManagement.Service }; } - private CurrentProfileOutputDto BuildEmployeeCurrentProfile(Employee employee, string photoUrl) + private CurrentProfileOutputDto BuildEmployeeCurrentProfile(Domain.Employee employee, string photoUrl) { var account = FirstNonEmpty(employee.EmailAddress, employee.EmployeeId); var displayName = FirstNonEmpty(employee.Name, employee.EmployeeId); @@ -283,7 +283,7 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse(); } - private BaseResponse ChangeEmployeePassword(Employee employee, ChangePasswordInputDto inputDto) + private BaseResponse ChangeEmployeePassword(Domain.Employee employee, ChangePasswordInputDto inputDto) { var currentPassword = _dataProtectionHelper.SafeDecryptEmployeeData(employee.Password); if (!string.Equals(inputDto.OldPassword, currentPassword, StringComparison.Ordinal)) @@ -580,7 +580,7 @@ namespace EOM.TSHotelManagement.Service public string DisplayName { get; set; } public string PhotoUrl { get; set; } public Administrator Admin { get; set; } - public Employee Employee { 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 969313a..3fd08df 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,7 +99,7 @@ namespace EOM.TSHotelManagement.Service //查询所有部门信息 List depts = deptRepository.GetList(); //查询所有员工信息 - List employees = employeeRepository.GetList(); + List employees = employeeRepository.GetList(); var where = SqlFilterBuilder.BuildExpression(asset); diff --git a/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs b/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs index 2f348bb..2962c7a 100644 --- a/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs +++ b/EOM.TSHotelManagement.Service/Business/Customer/Account/CustomerAccountService.cs @@ -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, }; } @@ -335,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 05603dc..9677245 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; - } /// /// 添加客户信息 @@ -119,7 +54,7 @@ namespace EOM.TSHotelManagement.Service { 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) { @@ -182,7 +117,7 @@ namespace EOM.TSHotelManagement.Service { 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) { @@ -232,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)); } // 批量软删除 @@ -252,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", "客户信息删除失败")); } } @@ -301,7 +238,7 @@ namespace EOM.TSHotelManagement.Service { readCustomerInputDto ??= new ReadCustomerInputDto(); - var where = SqlFilterBuilder.BuildExpression(readCustomerInputDto, nameof(Customer.DateOfBirth)); + var where = SqlFilterBuilder.BuildExpression(readCustomerInputDto, nameof(Domain.Customer.DateOfBirth)); var query = custoRepository.AsQueryable().Where(a => a.IsDelete != 1); var whereExpression = where.ToExpression(); if (whereExpression != null) @@ -312,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; @@ -333,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 ?? ""); @@ -351,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, - 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 - }; - }); + 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, - 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 - }); - }); + customerOutputDtos = custos.Select(source => MapToCustomerOutputDto(source, genderMap, custoTypeMap, passPortTypeMap)).ToList(); } return new ListOutputDto @@ -417,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 + }; + } + /// /// 查询指定客户信息 /// @@ -424,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 ?? ""); //查询出所有证件类型 @@ -445,16 +359,16 @@ 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); + singleOutputDto.Data = EntityMapper.Map(customer); //解密身份证号码/联系方式(失败时回退原值) singleOutputDto.Data.IdCardNumber = dataProtector.SafeDecryptCustomerData(customer.IdCardNumber); diff --git a/EOM.TSHotelManagement.Service/Business/News/NewsService.cs b/EOM.TSHotelManagement.Service/Business/News/NewsService.cs index fa5322c..bd998d1 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/Reser/ReserService.cs b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs index 6b2b8e9..1be97af 100644 --- a/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs +++ b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs @@ -1,4 +1,5 @@ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; @@ -8,41 +9,28 @@ 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(a => a.ToString(), a => helper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); + .ToDictionary(a => a.ToString(), a => EnumHelper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var where = SqlFilterBuilder.BuildExpression(filterInput, 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); @@ -58,7 +46,7 @@ namespace EOM.TSHotelManagement.Service } var count = 0; - List reservations; + List reservations; if (readReserInputDto.IgnorePaging) { reservations = query.ToList(); @@ -88,10 +76,9 @@ namespace EOM.TSHotelManagement.Service public SingleOutputDto SelectReserInfoByRoomNo(ReadReserInputDto readReserInputDt) { - var helper = new EnumHelper(); var reserTypeMap = Enum.GetValues(typeof(ReserType)) .Cast() - .ToDictionary(a => a.ToString(), a => helper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); + .ToDictionary(a => a.ToString(), a => EnumHelper.GetEnumDescription(a) ?? string.Empty, StringComparer.OrdinalIgnoreCase); var query = reserRepository.AsQueryable().Where(a => a.ReservationStatus == 0 && a.IsDelete != 1); if (readReserInputDt?.RoomId > 0) @@ -160,7 +147,7 @@ namespace EOM.TSHotelManagement.Service .Where(a => a.IsDelete != 1 && a.ReservationStatus == 0) .ToList(); - var changedRooms = new List(); + var changedRooms = new List(); foreach (var room in rooms) { var hasOtherActiveReservation = remainingReservations.Any(a => IsSameRoom(a, room)); @@ -275,7 +262,7 @@ namespace EOM.TSHotelManagement.Service return CreateRoomLookupFailure(roomResult, input?.RoomId, input?.ReservationRoomNumber); } - var entity = new Reser + var entity = new Domain.Reser { ReservationId = input.ReservationId, CustomerName = input.CustomerName, @@ -292,7 +279,7 @@ namespace EOM.TSHotelManagement.Service reserRepository.Insert(entity); - roomResult.Room.RoomStateId = new EnumHelper().GetEnumValue(RoomState.Reserved); + roomResult.Room.RoomStateId = EnumHelper.GetEnumValue(RoomState.Reserved); if (!roomRepository.Update(roomResult.Room)) { return new BaseResponse(BusinessStatusCode.InternalServerError, "Insert reservation failed."); @@ -309,14 +296,13 @@ namespace EOM.TSHotelManagement.Service public ListOutputDto SelectReserTypeAll() { - var helper = new EnumHelper(); var enumList = Enum.GetValues(typeof(ReserType)) .Cast() .Select(a => new EnumDto { Id = (int)a, Name = a.ToString(), - Description = helper.GetEnumDescription(a) + Description = EnumHelper.GetEnumDescription(a) }) .ToList(); @@ -330,7 +316,7 @@ namespace EOM.TSHotelManagement.Service }; } - private ReadReserOutputDto MapReservationToOutput(Reser source, Room room, Dictionary reserTypeMap) + private ReadReserOutputDto MapReservationToOutput(Domain.Reser source, Domain.Room room, Dictionary reserTypeMap) { return new ReadReserOutputDto { @@ -356,7 +342,7 @@ namespace EOM.TSHotelManagement.Service }; } - private static bool IsSameRoom(Reser reservation, Room room) + private static bool IsSameRoom(Domain.Reser reservation, Domain.Room room) { if (reservation == null || room == null || !reservation.RoomId.HasValue || reservation.RoomId.Value <= 0) { diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs index 7694296..d77e4ff 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs @@ -1,4 +1,5 @@ using EOM.TSHotelManagement.Common; +using EOM.TSHotelManagement.Common.Helper; using EOM.TSHotelManagement.Contract; using EOM.TSHotelManagement.Data; using EOM.TSHotelManagement.Domain; @@ -11,30 +12,30 @@ 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 roomRepository, GenericRepository spendRepository, GenericRepository roomTypeRepository, GenericRepository energyRepository, - GenericRepository custoRepository, + GenericRepository custoRepository, GenericRepository custoTypeRepository, GenericRepository vipLevelRuleRepository, - GenericRepository reserRepository, + GenericRepository reserRepository, UniqueCode uniqueCode, ILogger logger) { @@ -74,7 +75,7 @@ namespace EOM.TSHotelManagement.Service var data = MapRoomToOutput( roomResult.Room, BuildRoomTypeMap(), - BuildCustomerMap(new List { roomResult.Room }), + BuildCustomerMap(new List { roomResult.Room }), BuildRoomStateMap()); return new SingleOutputDto { Data = data }; @@ -100,36 +101,15 @@ namespace EOM.TSHotelManagement.Service public BaseResponse UpdateRoomInfo(UpdateRoomInputDto r) { - try - { - var roomResult = ResolveRoom(r); - if (roomResult.Room == null) - { - return CreateRoomLookupFailure(roomResult, r?.RoomNumber, r?.RoomArea, r?.RoomFloor); - } - - var room = roomResult.Room; - room.RoomStateId = r.RoomStateId; - 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; - return roomRepository.Update(room) ? new BaseResponse() : BaseResponseFactory.ConcurrencyConflict(); - } - catch (Exception ex) - { - logger.LogError(ex, "Error updating room info for room number {RoomNumber}", r.RoomNumber); - return ErrorResponse(ex); - } + return UpdateRoomCore(r, true); } public BaseResponse UpdateRoomInfoWithReser(UpdateRoomInputDto r) + { + return UpdateRoomCore(r, false); + } + + private BaseResponse UpdateRoomCore(UpdateRoomInputDto r, bool isFullUpdate) { try { @@ -141,14 +121,28 @@ namespace EOM.TSHotelManagement.Service 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; + 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); + var updateType = isFullUpdate ? "full" : "with reservation"; + logger.LogError(ex, "Error updating room info ({UpdateType}) for room number {RoomNumber}", updateType, r.RoomNumber); return ErrorResponse(ex); } } @@ -183,7 +177,7 @@ namespace EOM.TSHotelManagement.Service public SingleOutputDto SelectRoomPricingOptions(ReadRoomInputDto readRoomInputDto) { var roomResult = ResolveRoom(readRoomInputDto); - Room room = null; + Domain.Room room = null; RoomType roomType = null; if (roomResult.Room != null) @@ -300,17 +294,16 @@ namespace EOM.TSHotelManagement.Service var normalizedRoomNumber = rn.RoomNumber?.Trim(); var normalizedRoomArea = RoomLocatorHelper.NormalizeArea(rn.RoomArea); var duplicateExists = roomRepository.AsQueryable() - .Where(a => a.IsDelete != 1 && a.RoomNumber == normalizedRoomNumber) + .Where(a => a.IsDelete != 1 && a.RoomNumber == normalizedRoomNumber && a.RoomFloor == rn.RoomFloor) .ToList() - .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase) - && a.RoomFloor == rn.RoomFloor); + .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase)); if (duplicateExists) { return new BaseResponse { Message = "This room already exists.", Code = BusinessStatusCode.InternalServerError }; } - var entity = EntityMapper.Map(rn); + var entity = EntityMapper.Map(rn); entity.RoomNumber = normalizedRoomNumber; entity.RoomArea = string.IsNullOrWhiteSpace(normalizedRoomArea) ? null : normalizedRoomArea; entity.LastCheckInTime = null; @@ -346,10 +339,9 @@ namespace EOM.TSHotelManagement.Service 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) + .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) - && a.RoomFloor == rn.RoomFloor); + .Any(a => string.Equals(RoomLocatorHelper.NormalizeArea(a.RoomArea), normalizedRoomArea, StringComparison.OrdinalIgnoreCase)); if (duplicateExists) { @@ -491,7 +483,7 @@ namespace EOM.TSHotelManagement.Service if (newLevelId != 0) { - custoRepository.Update(a => new Customer { CustomerType = newLevelId }, a => a.CustomerNumber == transferRoomDto.CustomerNumber); + custoRepository.Update(a => new Domain.Customer { CustomerType = newLevelId }, a => a.CustomerNumber == transferRoomDto.CustomerNumber); } var customerType = custoTypeRepository.GetFirst(a => a.CustomerType == customer.CustomerType && a.IsDelete != 1); @@ -700,7 +692,7 @@ namespace EOM.TSHotelManagement.Service using var scope = CreateTransactionScope(); - var customer = new Customer + var customer = new Domain.Customer { CustomerNumber = checkinRoomByReservationDto.CustomerNumber, Name = checkinRoomByReservationDto.CustomerName, @@ -730,7 +722,7 @@ namespace EOM.TSHotelManagement.Service var room = roomResult.Room; room.LastCheckInTime = DateTime.Now; room.CustomerNumber = customer.CustomerNumber; - room.RoomStateId = new EnumHelper().GetEnumValue(RoomState.Occupied); + room.RoomStateId = EnumHelper.GetEnumValue(RoomState.Occupied); var pricingResponse = ApplyPricingSelection(room, checkinRoomByReservationDto.PricingCode); if (pricingResponse != null) { @@ -763,7 +755,7 @@ namespace EOM.TSHotelManagement.Service { readRoomInputDto ??= new ReadRoomInputDto(); - var where = SqlFilterBuilder.BuildExpression(readRoomInputDto); + var where = SqlFilterBuilder.BuildExpression(readRoomInputDto); var query = roomRepository.AsQueryable().Where(a => a.IsDelete != 1); var whereExpression = where.ToExpression(); if (whereExpression != null) @@ -774,7 +766,7 @@ namespace EOM.TSHotelManagement.Service query = query.OrderBy(BuildRoomListOrderByClause()); var count = 0; - List rooms; + List rooms; if (!readRoomInputDto.IgnorePaging) { var page = readRoomInputDto.Page > 0 ? readRoomInputDto.Page : 1; @@ -843,7 +835,7 @@ namespace EOM.TSHotelManagement.Service private RoomResolveResult ResolveTargetRoom(TransferRoomDto inputDto) => RoomLocatorHelper.Resolve(roomRepository, inputDto?.TargetRoomId, null, null, null); - private static void NormalizeRoom(Room room) + private static void NormalizeRoom(Domain.Room room) { if (room == null) { @@ -879,12 +871,12 @@ namespace EOM.TSHotelManagement.Service private string BuildRoomListOrderByClause() { - var entityInfo = roomRepository.Context.EntityMaintenance.GetEntityInfo(); + var entityInfo = roomRepository.Context.EntityMaintenance.GetEntityInfo(); var orderedColumns = new[] { - nameof(Room.RoomArea), - nameof(Room.RoomFloor), - nameof(Room.RoomNumber) + nameof(Domain.Room.RoomArea), + nameof(Domain.Room.RoomFloor), + nameof(Domain.Room.RoomNumber) }; return string.Join(", ", orderedColumns.Select(propertyName => @@ -904,7 +896,7 @@ namespace EOM.TSHotelManagement.Service .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.RoomTypeName ?? ""); } - private Dictionary BuildCustomerMap(List rooms) + private Dictionary BuildCustomerMap(List rooms) { var customerNumbers = rooms .Select(a => a.CustomerNumber) @@ -926,11 +918,10 @@ namespace EOM.TSHotelManagement.Service private static Dictionary BuildRoomStateMap() { - var helper = new EnumHelper(); - return Enum.GetValues(typeof(RoomState)).Cast().ToDictionary(e => (int)e, e => helper.GetEnumDescription(e) ?? ""); + return Enum.GetValues(typeof(RoomState)).Cast().ToDictionary(e => (int)e, e => EnumHelper.GetEnumDescription(e) ?? ""); } - private static ReadRoomOutputDto MapRoomToOutput(Room source, Dictionary roomTypeMap, Dictionary customerMap, Dictionary roomStateMap) + private static ReadRoomOutputDto MapRoomToOutput(Domain.Room source, Dictionary roomTypeMap, Dictionary customerMap, Dictionary roomStateMap) { var pricingEvaluation = EvaluateRoomPricing(source); return new ReadRoomOutputDto @@ -977,7 +968,7 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse { Message = ex.Message, Code = BusinessStatusCode.InternalServerError }; } - private BaseResponse ApplyPricingSelection(Room room, string pricingCode) + private BaseResponse ApplyPricingSelection(Domain.Room room, string pricingCode) { if (room == null || string.IsNullOrWhiteSpace(pricingCode)) { @@ -1018,17 +1009,17 @@ namespace EOM.TSHotelManagement.Service return null; } - private static decimal GetEffectiveRoomRent(Room room) + private static decimal GetEffectiveRoomRent(Domain.Room room) { return EvaluateRoomPricing(room).EffectiveRoomRent; } - private static decimal GetEffectiveRoomDeposit(Room room) + private static decimal GetEffectiveRoomDeposit(Domain.Room room) { return EvaluateRoomPricing(room).EffectiveRoomDeposit; } - private static RoomPricingEvaluation EvaluateRoomPricing(Room room) + private static RoomPricingEvaluation EvaluateRoomPricing(Domain.Room room) { if (room == null) { @@ -1076,7 +1067,7 @@ namespace EOM.TSHotelManagement.Service }; } - private static bool IsPricingTimedOut(Room room) + private static bool IsPricingTimedOut(Domain.Room room) { if (room?.PricingStayHours is not > 0) { diff --git a/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs b/EOM.TSHotelManagement.Service/Dashboard/DashboardService.cs index 180c573..e7af58e 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.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,14 +21,13 @@ 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(); diff --git a/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs b/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs index 59ed299..3c4c32d 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); @@ -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,7 +170,7 @@ namespace EOM.TSHotelManagement.Service { readEmployeeInputDto ??= new ReadEmployeeInputDto(); - var where = SqlFilterBuilder.BuildExpression(readEmployeeInputDto, nameof(Employee.HireDate)); + var where = SqlFilterBuilder.BuildExpression(readEmployeeInputDto, nameof(Domain.Employee.HireDate)); var query = workerRepository.AsQueryable().Where(a => a.IsDelete != 1); 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, - 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 - }; + 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, - 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 - }); - }); + 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,20 +290,18 @@ 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 source = EntityMapper.Map(w); //解密身份证号码 var sourceStr = w.IdCardNumber.IsNullOrEmpty() ? "" : dataProtector.SafeDecryptEmployeeData(w.IdCardNumber); @@ -419,7 +327,7 @@ namespace EOM.TSHotelManagement.Service var passport = passportTypeRepository.GetFirst(a => a.PassportId == w.IdCardType); source.IdCardTypeName = passport.IsNullOrEmpty() ? "" : passport.PassportName; //面貌 - source.PoliticalAffiliationName = new EnumHelper().GetDescriptionByName(w.PoliticalAffiliation); + source.PoliticalAffiliationName = EnumHelper.GetDescriptionByName(w.PoliticalAffiliation); var employeePhoto = photoRepository.GetFirst(a => a.EmployeeId.Equals(source.EmployeeId)); if (employeePhoto != null && !string.IsNullOrEmpty(employeePhoto.PhotoPath)) @@ -435,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); @@ -496,7 +403,7 @@ namespace EOM.TSHotelManagement.Service w.Password = ""; - var output = EntityMapper.Map(w); + var output = EntityMapper.Map(w); //性别类型 var genderType = genders.SingleOrDefault(a => a.Id == w.Gender); @@ -519,6 +426,11 @@ namespace EOM.TSHotelManagement.Service new Claim(ClaimTypes.Name, w.Name), new Claim(ClaimTypes.SerialNumber, w.EmployeeId) })); + 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) diff --git a/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs b/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs index 3ca136e..987f951 100644 --- a/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs +++ b/EOM.TSHotelManagement.Service/Employee/Permission/EmployeePermissionService.cs @@ -15,7 +15,7 @@ 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; @@ -23,7 +23,7 @@ namespace EOM.TSHotelManagement.Service private readonly GenericRepository roleRepository; public EmployeePermissionService( - GenericRepository employeeRepository, + GenericRepository employeeRepository, GenericRepository userRoleRepository, GenericRepository rolePermissionRepository, GenericRepository permissionRepository, diff --git a/EOM.TSHotelManagement.Service/Security/TwoFactorAuthService.cs b/EOM.TSHotelManagement.Service/Security/TwoFactorAuthService.cs index 72b8645..eaca844 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 bce0291..6279196 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; @@ -197,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) diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Base/BaseService.cs index 3a33895..29cd032 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,82 +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; - - /// - /// 公告类型 - /// - 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 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.appointmentNoticeTypeRepository = appointmentNoticeTypeRepository; - this.employeeRepository = employeeRepository; - this.customerRepository = customerRepository; - this.appointmentNoticeRepository = appointmentNoticeRepository; - this.logger = logger; - } #region 预约类型模块 @@ -114,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(); @@ -145,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(); @@ -176,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(); @@ -206,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(); @@ -713,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.Name; + 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 diff --git a/README.en.md b/README.en.md index 88d4a10..948ab97 100644 --- a/README.en.md +++ b/README.en.md @@ -49,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. @@ -93,7 +97,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 | - | ❌ | ❌ | @@ -126,7 +130,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. @@ -174,7 +188,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| diff --git a/README.md b/README.md index ba8b8cb..3aa1eb3 100644 --- a/README.md +++ b/README.md @@ -49,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 加密存储。 - **请求日志**:全局请求中间件,记录接口调用详情。 @@ -89,14 +93,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 | - | ❌ | ❌ | ## 快速开始 @@ -126,7 +130,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` 项目。 @@ -174,7 +188,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| -- Gitee From a136c5e83f269a9735a114f9052edc0270c9c669 Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Mon, 6 Apr 2026 13:48:10 +0800 Subject: [PATCH 4/7] remove Newtonsoft.Json package. --- EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs | 1 + .../Util/Dto/Dashboard/LogisticsDataOutputDto.cs | 1 - README.en.md | 1 - README.md | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs index 59f3101..a4ff3ea 100644 --- a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs +++ b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs @@ -273,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 => diff --git a/EOM.TSHotelManagement.Contract/Util/Dto/Dashboard/LogisticsDataOutputDto.cs b/EOM.TSHotelManagement.Contract/Util/Dto/Dashboard/LogisticsDataOutputDto.cs index 1e88175..8f6b74e 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/README.en.md b/README.en.md index 948ab97..1cf753e 100644 --- a/README.en.md +++ b/README.en.md @@ -71,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 diff --git a/README.md b/README.md index 3aa1eb3..74464f9 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ - **Quartz .NET**: 定时任务调度(处理过期预订)。 - **MailKit**: 邮件发送库。 - **NSwag**: API 文档生成(Swagger UI)。 -- **Newtonsoft.Json**: JSON 序列化库。 ## 项目结构 -- Gitee From 4266e3a67b48fe5704294b934d8c9524902c07cd Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Sun, 12 Apr 2026 13:04:00 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/Asset/Dto/Asset/ReadAssetInputDto.cs | 8 +++++--- .../CreateSupervisionStatisticsInputDto.cs | 3 +++ .../ReadSupervisionStatisticsInputDto.cs | 6 +++++- .../ReadSupervisionStatisticsOutputDto.cs | 4 +++- .../UpdateSupervisionStatisticsInputDto.cs | 3 +++ .../Business/Asset/AssetService.cs | 2 +- .../SupervisionStatistics/SupervisionStatisticsService.cs | 4 +++- 7 files changed, 23 insertions(+), 7 deletions(-) diff --git a/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Asset/Dto/Asset/ReadAssetInputDto.cs index 1970ca5..2ea99b4 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/SystemManagement/Dto/SupervisionStatistics/CreateSupervisionStatisticsInputDto.cs b/EOM.TSHotelManagement.Contract/SystemManagement/Dto/SupervisionStatistics/CreateSupervisionStatisticsInputDto.cs index 0d1c33a..7ad8125 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 4cd6a93..7560771 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 3d4b66c..b6ebf80 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 eaf48a9..f4c8027 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.Service/Business/Asset/AssetService.cs b/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs index 3fd08df..fb0480e 100644 --- a/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs +++ b/EOM.TSHotelManagement.Service/Business/Asset/AssetService.cs @@ -101,7 +101,7 @@ namespace EOM.TSHotelManagement.Service //查询所有员工信息 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); diff --git a/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs b/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs index 95e446b..f7dd56c 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/SupervisionStatistics/SupervisionStatisticsService.cs @@ -61,7 +61,7 @@ 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; @@ -129,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); -- Gitee From 9d3e73afeab4325f9b8583246647bad15d87cf29 Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Sat, 18 Apr 2026 14:06:35 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/Room/RoomLocatorHelper.cs | 11 +++-------- .../Business/Room/RoomReferenceHelper.cs | 4 ++-- .../Business/Room/RoomService.cs | 9 +++++---- .../Business/Room/RoomTypeService.cs | 8 ++++---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs index 68679c3..e14d6b8 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs @@ -20,14 +20,11 @@ namespace EOM.TSHotelManagement.Service int? roomId, string roomNumber, string roomArea, - int? roomFloor, - bool includeDeleted = false) + int? roomFloor) { if (roomId.HasValue && roomId.Value > 0) { - var room = includeDeleted - ? repository.GetFirst(a => a.Id == roomId.Value) - : repository.GetFirst(a => a.Id == roomId.Value && a.IsDelete != 1); + var room = repository.GetFirst(a => a.Id == roomId.Value); return new RoomResolveResult { Room = room, IsAmbiguous = false }; } @@ -39,9 +36,7 @@ namespace EOM.TSHotelManagement.Service var normalizedNumber = roomNumber.Trim(); var normalizedArea = NormalizeArea(roomArea); - var candidates = includeDeleted - ? repository.GetList(a => a.RoomNumber == normalizedNumber) - : repository.GetList(a => a.RoomNumber == normalizedNumber && a.IsDelete != 1); + var candidates = repository.GetList(a => a.RoomNumber == normalizedNumber); if (!candidates.Any()) { diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs index 0bd758e..5ee47ea 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs @@ -8,9 +8,9 @@ namespace EOM.TSHotelManagement.Service { internal static class RoomReferenceHelper { - public static RoomResolveResult Resolve(GenericRepository repository, int? roomId, string roomNumber, bool includeDeleted = false) + public static RoomResolveResult Resolve(GenericRepository repository, int? roomId, string roomNumber) { - return RoomLocatorHelper.Resolve(repository, roomId, null, null, null, includeDeleted); + return RoomLocatorHelper.Resolve(repository, roomId, null, null, null); } public static List LoadRooms(GenericRepository repository, IEnumerable roomIds, IEnumerable roomNumbers) diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs index d77e4ff..996d0da 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomService.cs @@ -300,12 +300,12 @@ namespace EOM.TSHotelManagement.Service.Business.Room if (duplicateExists) { - return new BaseResponse { Message = "This room already exists.", Code = BusinessStatusCode.InternalServerError }; + return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("This room already exists.", "房间已存在"), Code = BusinessStatusCode.InternalServerError }; } var entity = EntityMapper.Map(rn); - entity.RoomNumber = normalizedRoomNumber; - entity.RoomArea = string.IsNullOrWhiteSpace(normalizedRoomArea) ? null : normalizedRoomArea; + entity.RoomNumber = normalizedRoomNumber ?? string.Empty; + entity.RoomArea = string.IsNullOrWhiteSpace(normalizedRoomArea) ? string.Empty : normalizedRoomArea; entity.LastCheckInTime = null; entity.LastCheckOutTime = null; entity.AppliedRoomRent = 0M; @@ -362,6 +362,7 @@ namespace EOM.TSHotelManagement.Service.Business.Room 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(); @@ -756,7 +757,7 @@ namespace EOM.TSHotelManagement.Service.Business.Room readRoomInputDto ??= new ReadRoomInputDto(); var where = SqlFilterBuilder.BuildExpression(readRoomInputDto); - var query = roomRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = roomRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomTypeService.cs index a304bee..b3d2d7b 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) { @@ -105,7 +105,7 @@ namespace EOM.TSHotelManagement.Service DataChgUsr = source.DataChgUsr, DataChgDate = source.DataChgDate, RowVersion = source.RowVersion, - IsDelete = source.IsDelete + IsDelete = source.IsDelete, }; }); mapped = dtoArray.ToList(); @@ -210,7 +210,7 @@ namespace EOM.TSHotelManagement.Service { try { - var targetRoomType = roomTypeRepository.GetFirst(a => a.Id == (roomType.Id ?? 0) && a.IsDelete != 1); + var targetRoomType = roomTypeRepository.GetFirst(a => a.Id == (roomType.Id ?? 0)); if (targetRoomType == null) { return new BaseResponse @@ -284,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 { -- Gitee From 643a06294345966568e212e4695c7559f6247fcc Mon Sep 17 00:00:00 2001 From: ck_yeun9 Date: Sat, 25 Apr 2026 16:10:05 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/Customer/CustomerService.cs | 2 +- .../Business/EnergyManagement/EnergyManagementService.cs | 2 +- .../Business/PromotionContent/PromotionContentService.cs | 2 +- EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs | 2 +- EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs | 2 +- EOM.TSHotelManagement.Service/Employee/EmployeeService.cs | 2 +- .../SystemManagement/Administrator/AdminService.cs | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs b/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs index 9677245..3660572 100644 --- a/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs +++ b/EOM.TSHotelManagement.Service/Business/Customer/CustomerService.cs @@ -239,7 +239,7 @@ namespace EOM.TSHotelManagement.Service.Business.Customer readCustomerInputDto ??= new ReadCustomerInputDto(); var where = SqlFilterBuilder.BuildExpression(readCustomerInputDto, nameof(Domain.Customer.DateOfBirth)); - var query = custoRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = custoRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { diff --git a/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs b/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs index f01101f..1e21a0e 100644 --- a/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs +++ b/EOM.TSHotelManagement.Service/Business/EnergyManagement/EnergyManagementService.cs @@ -32,7 +32,7 @@ namespace EOM.TSHotelManagement.Service var filterInput = CreateEnergyFilter(readEnergyManagementInputDto); var where = SqlFilterBuilder.BuildExpression(filterInput); - var query = wtiRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = wtiRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { diff --git a/EOM.TSHotelManagement.Service/Business/PromotionContent/PromotionContentService.cs b/EOM.TSHotelManagement.Service/Business/PromotionContent/PromotionContentService.cs index 025f1ba..99ac719 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 1be97af..4a6d4bd 100644 --- a/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs +++ b/EOM.TSHotelManagement.Service/Business/Reser/ReserService.cs @@ -33,7 +33,7 @@ namespace EOM.TSHotelManagement.Service.Business.Reser { 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) { diff --git a/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs index c0d1fa0..a64e4e6 100644 --- a/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs +++ b/EOM.TSHotelManagement.Service/Business/Spend/SpendService.cs @@ -266,7 +266,7 @@ namespace EOM.TSHotelManagement.Service ? SqlFilterBuilder.BuildExpression(filterInput) : SqlFilterBuilder.BuildExpression(filterInput, dateFieldName); - var query = spendRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = spendRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { diff --git a/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs b/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs index 3c4c32d..2177cbf 100644 --- a/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs +++ b/EOM.TSHotelManagement.Service/Employee/EmployeeService.cs @@ -171,7 +171,7 @@ namespace EOM.TSHotelManagement.Service.Employee readEmployeeInputDto ??= new ReadEmployeeInputDto(); var where = SqlFilterBuilder.BuildExpression(readEmployeeInputDto, nameof(Domain.Employee.HireDate)); - var query = workerRepository.AsQueryable().Where(a => a.IsDelete != 1); + var query = workerRepository.AsQueryable(); var whereExpression = where.ToExpression(); if (whereExpression != null) { diff --git a/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs b/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs index 6279196..e9f9e44 100644 --- a/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs +++ b/EOM.TSHotelManagement.Service/SystemManagement/Administrator/AdminService.cs @@ -283,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) { @@ -503,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) { -- Gitee